Skip to content

mauricio-mautec/dotMindBoilerplate

Repository files navigation

Boilerplate do grupo de desenvolvimento dotMind

dotMind é um grupo formado para o desenvolvimento de sistemas. Este repositório tem por finalidade facilitar a criação de um ambiente de desenvolvimento baseado nas tecnologias NextJS, TypeScript, Strapi, Postgres dentre outras. Este boilerplate foi testado em máquinas com chips Apple M1

{ INSERIR REFERENCIA AO BOILERPLATE DO WILLIAN JUSTEN}


Utilização

yarn create next-app -e https://github.com/<repo boilerplate> <nome projeto>

Docker, Nodejs, Strapi e Postgres

Criar conta para uso do Docker - gratuito

Criar conta no Docker

  • Download do Docker para Mac with Apple Silicon

Docker Mac M1

  • Estrutura do diretório para utilização do container
  • Arquivo docker composer para configuração do container
  • Teste do container do postgres
cd ~/MeuProjeto
mkdir data

docker-compose.yml:

version: "3"
services:
  postgres:
    image: postgres
    environment:
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapi123
    volumes:
      - ./data:/var/lib/postgresql/data
    ports:
      - "5432:5432”

docker-compose up -d

Instalação do Nodejs e yarn

  • Download NVM como versionador
  • Habilitar base do XCode
  • Instalar yarn

Instruções de instalação NVM

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
sudo Xcode-select --install
nvm install 16.14.0
npm install --global yarn
  • Comandos NVM úteis:
nvm current
nvm ls
nvm use 16.14.0
nvm uninstall 13.17.6

Instalação Strapi com Postgres

  • Instalar Utilizando:
    • installation type Custom
    • default database client postgres
    • database name strapi
    • host 127.0.0.1
    • port 5432
    • username strapi
    • password strapi123
    • SSL connection No

yarn create strapi-app api

NOTAS IMPORTANTES

  • Diretório de dados do Postgres é data. Caso queira apaagar a base de Dados, basta remover esse diretório e reiniciar o container
  • Após a instalação do strapi, entre no diretório api e execute o strapi para configurar o acesso do usuario administrador
  • Utilize para First/LastName strapi / strapi e para senha Strapi123

Configuração Nodejs VsCode NextJS Brew Git

VSCode

wget https://code.visualstudio.com/sha/download?build=stable&os=darwin-arm64
git clone https://github.com/React-Avancado/reactavancado-extension-pack
cd reactavancado-extension-pack
npm install -g vsce
vsce package
  • Adicionar VSCode ao path abrindo o Command Palette (Cmd+Shift+P)
    • Procure por 'shell command'
    • Opção Install 'code' command in PATH

Brew e Git

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install git
cd ~/MeuProjeto
git init

Projeto NextJs

  • Criar um projeto boilerplate
  • Sinalizar utilização de TypeScript
  • Verificar e instalar dependencias para TypeScript
  • Teste, tipagem e configuração do typescript
cd ~/MeuProjeto
yarn create next-app frontpage
cd frontpage
touch tsconfig.json
yarn dev
yarn add --dev typescript@4.5.5 @types/react @types/node
yarn dev

tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "incremental": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

Organização diretórios e suporte de jsx em javascript

  • Criar diretório src e mover pasta pages para dentro de src
  • renomear extensão da index de .ts para .tsx
cd ~/MeuProjeto/frontpage
mkdir src
mv pages/index.js pages/index.jsx
mv pages src

Configurar VSCode

  • Configurar a consistência dos arquivos no VSCode via editor config

.editorconfig:

# editorconfig.org
root = true

[*]
indent_style = spaces
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

ESLINT

  • Instalar o ESLint escolhendo:
    • checar sintaxe e encontrar problemas
    • javascript modules - import / export
    • react
    • typescript
    • browser
    • json
    • yarn -> será apresentado uma relação de plugins para ser instalada manualmente.
npx eslint --init
yarn add --dev eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest eslint@latest

Plugins e Configurações .eslintrc.json

  • Instalar ESLINT-PLUGIN-REACT-HOOKS
  • Configurar Plugins
    • ESLINT-PLUGIN-REACT-HOOKS: melhora o desenvolvimento com relação à utilização de hooks e dependências
    • Desligar PROP-TYPES: para não sermos lembrados a todo momento de que não há PROP TYPES
    • Desligar REACT-IN-JSX-SCOPE: o Nextjs já importa React globalmente
    • Desligar EXPLICT-MODULE-BOUNDARY-TYPES: evita a necessidade de tipar todo retorno de função aumentando assim a verbosidade
    • Observar configuração customizada separada para "plugins" e "rules"
    • Configurar a versão do react em "settings" para o correto funcionamento do REACT-PLUGIN
  • Instalar ESLINT-PLUGIN-NEXT para funcionamento adequado do modo PRODUÇÃO (NODE_ENV=production; yarn build)

Migrating Existing Config: eslint-plugin-next

Configuração dos plugins: typescript-eslint

Eslint-Plugin-React-Hooks

yarn add eslint-plugin-react-hooks --dev
yarn add @next/eslint-plugin-next --dev

.eslintrc.json:

{
    "root": true,
    "env": {
        "browser": true,
        "es2021": true
    },
   "settings": {
       "react": {
          "version": "detect"
        }
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
       "plugin:@next/next/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "react-hooks",
        "@typescript-eslint"
    ],
    "rules": {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react/prop-types": "off",
      "react/react-in-jsx-scope": "off",
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "@typescript-eslint/no-non-null-assertion": "off"
    }
}

Configurar package.json, VSCode extensions e teste

  • Habilitar o plugin do eslint dentro do VSCode Extensions
  • Configurar "scripts" de package.json para chamar o ESLINT

~/MeuProjeto/frontpage/package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{js,jsx}'"
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "typescript": "4.5.5"
  }
}
  • Testar comando eslint no terminal

yarn lint


Prettier

  • Instalar e configurar

yarn add --dev --exact prettier

~/MeuProjeto/frontpage/.prettierrc:

{
"trailingComma": "none",
"semi": false,
"singleQuote": true
}
  • Instalar plugins de integração com eslint: prettier como uma regra do eslint

yarn add --dev eslint-plugin-prettier eslint-config-prettier

  • Configurar regra para execução do prettier pelo eslint em .eslintrc.json

~/MeuProjeto/frontpage/.eslintrc.json:

{
    "env": {
        "browser": true,
        "es2021": true
    },
    "settings": {
        "react": {
          "version": "detect"
        }
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:prettier/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "react-hooks",
        "@typescript-eslint",
        "prettier"
    ],
    "rules": {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react/prop-types": "off",
      "react/react-in-jsx-scope": "off",
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "@typescript-eslint/no-non-null-assertion": "off"
    }
}

VSCode e Prettier

  • Configurar VSCode para executar eslint/prettier antes de salvar
cd ~/MeuProjeto/frontpage
mkdir .vscode
cd .vscode
touch settings.json

settings.json`

{
 "window.zoomLevel": 2,
 "editor.fontSize": 10,
 "terminal.integrated.fontSize": 10,
 "editor.formatOnSave": false,
 "editor.codeActionsOnSave": {
     "source.fixAll.eslint": true
  }
}
  • Reload do plugin ESLINT para ativar alterações no VSCode

Git Hooks: Husky e Lint-Staged

  • Instalar e configurar Husky e Lint-staged
yarn add husky lint-staged --dev
yarn husky install
yarn husky add .husky/pre-commit "yarn lint-staged"

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint  'src/**/*.{js,jsx}' --max-warnings=0"
  },
  "lint-staged": {
    "src/**/*": [ "yarn lint --fix" ]
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "husky": "^7.0.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}
  • Caso o diretório .git seja removido, instalar novamente o husky e adicionar novamento o hook para o pre-commit
git init
yarn husky install
yarn husky add .husky/pre-commit "yarn lint-staged"

Jest com Babel e TypeScript

TypeScript

  • Configurar
    • "node": true adicionado ao "env" de .eslintrc.json pelo fato de que jest utiliza module.exports e a falta desta configuração faria o eslint gerar avisos

.eslintrc.json:

{
    "env": {
        "browser": true,
        "es2021": true,
        "jest": true,
        "node": true
    },
    "settings": {
        "react": {
          "version": "detect"
        }
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:prettier/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "react-hooks",
        "@typescript-eslint",
        "prettier"
    ],
    "rules": {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react/prop-types": "off",
      "react/react-in-jsx-scope": "off",
      "@typescript-eslint/explicit-module-boundary-types": "off",
      "@typescript-eslint/no-non-null-assertion": "off"
    }
}
  • Instalar e configurar Jest

yarn add jest @babel/preset-typescript @types/jest --dev

~/MeuProjeto/frontpage/jest.config.js:

module.exports = {
 testEnvironment: 'jsdom',
 testPathIgnorePatterns: ['/node_modules/','/.next/'],
 collectCoverage: true,
 collectCoverageFrom: ['src/**/*.ts(x)?', '!src/**/*.stories.tsx'],
 setupFilesAfterEnv: ['<rootDir>/.jest/setup.ts'],
  moduleNameMapper: {
    '^styled-components': '<rootDir>/node_modules/styled-components/dist/styled-components.browser.cjs.js'
  }
}

Babel

  • Configurar
    • next/babel para escrever os códigos de teste no Jest utilizando novidades de JavaScript
    • @babel/preset-typescript pelo fato de estarem escritos em typescript

~/MeuProjeto/frontpage/.babelrc`

{
 "presets": ["next/babel", "@babel/preset-typescript"]
}

~/MeuProjeto/frontpage/package.json`

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{js,jsx}' --max-warnings=0",
    "test": "jest"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@babel/preset-typescript": "^7.16.7",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "lint-staged": "^12.3.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}
  • Jest Setup
    • .jest/setup.ts contém as informações do Jest tais como imports de assets, expects e outros artefatos para trabalhar com jsdom
cd ~/MeuProjeto/frontpage
mkdir .jest
cd .jest
touch setup.ts

React Testing Library

  • Instalar React Testing Library e Matcher do Jest (jest-dom)
  • Criar arquivos para teste e testar
cd ~/MeuProjeto/frontpage
yarn add --dev @testing-library/react @testing-library/jest-dom
echo "import '@testing-library/jest-dom' >> .jest/setup.ts"
cd src
mkdir components components/Main
cd components/Main
touch index.tsx test.tsx

index.tsx:

const Main = () => (
 <main>
  <h1>React</h1>
 </main>
)

export default Main
  • Baixar e imprimir o cheat sheet para funções de teste Cheat Sheet

test.tsx:

import { render, screen } from '@testing-library/react'

import Main from '.'

describe ('<Main />', () => {
 it ('should render the heading', () => {
   render(<Main />)
   expect(
    screen.getByRole('heading', { name: /react avançado/i })
   ).toBeInTheDocument()
 })
})
  • Rodar o teste e verificar ocorrência intencional da falha
yarn test
yarn run v1.22.17
$ jest
 FAIL  src/components/Main/test.tsx
  <Main />
    ✕ should render the heading (40 ms)

  ● <Main /> › should render the heading

    TestingLibraryElementError: Unable to find an accessible element with the role "heading" and name `/react avançado/i`

    Here are the accessible roles:

      main:

      Name "":
      <main />

      --------------------------------------------------
      heading:

      Name "Avançado":
      <h1 />

      --------------------------------------------------

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <main>
          <h1>
            Avançado
          </h1>
        </main>
      </div>
    </body>

       6 |   it('should render the heading', () => {
       7 |     render(<Main />)
    >  8 |     expect(screen.getByRole('heading', { name: /react avançado/i }))
         |                   ^
       9 |       .toBeInTheDocument
      10 |   })
      11 | })

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:38:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByRole (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/components/Main/test.tsx:8:19)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:404:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:320:7)
      at runCLI (node_modules/@jest/core/build/cli/index.js:173:3)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |
 index.tsx |     100 |      100 |     100 |     100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.855 s, estimated 1 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
  • Alterar index.tsx para passar no teste e novamente verificar

~/MeuProjeto/frontpage/src/components/Main/index.tsx:

const Main = () => (
 <main>
  <h1>React Avançado</h1>
 </main>
)

export default Main
yarn test
yarn run v1.22.17
$ jest
 PASS  src/components/Main/test.tsx
  <Main />
    ✓ should render the heading (29 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |
 index.tsx |     100 |      100 |     100 |     100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.828 s, estimated 1 s
Ran all test suites.
✨  Done in 1.99s.
  • Configurar script para teste assistido, "test:watch"

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{js,jsx}' --max-warnings=0",
    "test": "jest",
    "test:watch": "yarn test --watch"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@babel/preset-typescript": "^7.16.7",
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "lint-staged": "^12.3.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}
  • Configurar snapshot no arquivo de teste

src/components/Main/test.tsx:

import { render, screen } from '@testing-library/react'

import Main from '.'

describe('<Main />', () => {
  it('should render the heading', () => {
    const { container } = render(<Main />)
    expect(screen.getByRole('heading', { name: /react avançado/i }))
      .toBeInTheDocument

    expect(container.firstChild).toMatchSnapshot()
  })
})
  • Para testar o snapshot, rodar o teste uma vez e depois alter o heading de h1 para h2 em main.tsx, de forma que passe no primeiro test mas falhe no snapshot
  • Notas:
    • Se estiver utilizando "test:watch", ao digitar "u" teremos a atualização do snapshot
    • No teste manual o snapshot é atulizado com yarn test -u
    • O snapshot faz parte do repositório

Lint-Staged

O Lint Staged é executado no momento do commit e evita que bugs entrem no versionamento

  • Adicionar testes ao LINT-STAGED
    • "yarn test --findRelatedTests --bail":
    • bail serve para que tudo pare ao primeiro teste que não passe
    • findRelatedTests serve para que modificações em arquivos que não sejam relacionadas com os testes não quebrem o processo informando que não há testes para testar

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{js,jsx}' --max-warnings=0",
    "test": "jest",
    "test:watch": "yarn test --watch"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix",
      "yarn test --findRelatedTests --bail"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@babel/preset-typescript": "^7.16.7",
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "lint-staged": "^12.3.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}

Styled Components e Server Side Rendering

  • Instalar Styled-Components
  • Instalar dependencias de desenvolvimento e configurar Babel
  • Instalar integração de Styled Components com JEST

Jest Integration

yarn add styled-components
yarn add --dev @types/styled-components babel-plugin-styled-components jest-styled-components

.jest/setup.ts:

import '@testing-library/jest-dom'
import 'jest-styled-components'

.babelrc:

{
 "presets": ["next/babel", "@babel/preset-typescript"],
 "plugins": [
   [  "babel-plugin-styled-components",
     {
      "ssr": true
    }
   ]
 ]
}
  • Configurar _document.tsx, arquivo padrão do Nextjs, para passar informações ao Nextjs sobre a renderização das páginas
  • Disponibilizar a função Render para permitir que se edite a linguagem HTML e outros detalhes sem que o nextjs faça uma renderização padrão das paginas, o que ocasionaria em conflitos com o Styled Components

Custom Document _document.tsx

pages/_document.tsx:

import Document, {
  DocumentContext,
  Html,
  Head,
  Main,
  NextScript
} from 'next/document'

import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }

  render(): JSX.Element {
    return (
      <Html lang="pt-BR">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
yarn dev
yarn run v1.22.17
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc" https://nextjs.org/docs/messages/swc-disabled
event - compiled client and server successfully in 198 ms (144 modules)
wait  - compiling /_error (client and server)...
event - compiled client and server successfully in 41 ms (145 modules)
wait  - compiling / (client and server)...
event - compiled client and server successfully in 66 ms (168 modules)

createGlobalStyle

  • Criando estilos globais com o helper createGlobalStyle
    • Estruturar diretório e arquivo global.ts
    • Organizar bordas, marges, box-sizing, fonts padrão, temas e etc
    • Organizar src/pages/index.tsx
    • Criar src/pages/_app.tsx para importar o global style
    • _app.tsx tem a função de permitir as seguintes funcionalidades:
      • Persistência de layout durante mudança de páginas
      • persistência de estado durante a navegação
      • injeção de dados adicionais
      • global css
      • path alias e absolute imports (tsconfig.json)

Modelo _app.tsx

cd ~/MeuProjeto/frontpage/src/
mkdir styles
touch styles/global.ts

~/MeuProjeto/frontpage/tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "incremental": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

styles/global.ts:

import { createGlobalStyle } from 'styled-components'

const GlobalStyles = createGlobalStyle`
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    html {
        font-size: 62.5%;
    }

    html, body, #__next {
        height: 100%;
    }

    body {
       font-family; -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
    }
`

export default GlobalStyles

pages/_app.tsx:

import type { AppProps } from 'next/app'
import Head from 'next/head'

import GlobalStyles from 'styles/global'

function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>React Avançado - Boilerplate</title>
        <link rel="shortcut icon" href="/img/icon-512.png" />
        <link rel="apple-touch-icon" href="/img/icon-512.png" />
        <meta
          name="description"
          content="A simple project starter to work with TypeScript, React, NextJS and Styled Components"
        />
      </Head>
     <GlobalStyles />
      <Component {...pageProps} />
    </>
  )
}

export default App

pages/index.tsx:

import Main from 'components/Main'

export default function Home() {
  return <Main />
}

Criando Estilos em Componentes

  • Criar para cada componente um arquivo styles.ts e configurar os estilos
  • No componente, index.tsx, importar todos os estilos como 'S' para facilitar a identificação entre estilos e componentes
  • Editar testes para incluir a verificação de estilos

cd ~/MeuProjeto/frontpage/src/components/Main

styles.ts:

import styled from 'styled-components'

export const Wrapper = styled.main`
  background-color: #06092b;
  color: #fff;
  width: 100%;
  height: 100%;
  padding: 3rem;
  display: flex;
  flex-direction: column;
  text-align: center;
  align-items: center;
  justify-content: center;
`

export const Logo = styled.img`
  width: 25rem;
  margin-bottom: 2rem;
`

export const Title = styled.h1`
  font-size: 2.5rem;
`

export const Description = styled.h2`
  font-size: 2rem;
  font-weight: 400;
`

export const Illustration = styled.img`
  margin-top: 3rem;
  width: min(30rem, 100%);
`

index.tsx:

import * as S from './styles'

const Main = () => (
  <S.Wrapper>
    <S.Logo
      src="/img/logo.svg"
      alt="Imagem de um átomo e React Avançado escrito ao lado."
    />
    <S.Title>React Avançado</S.Title>
    <S.Description>
      TypeScript, ReactJS, NextJs e Styled Components
    </S.Description>
    <S.Illustration
      src="img/hero-illustration.svg"
      alt="Um desenvolvedor de frente para uma tela com código."
    />
  </S.Wrapper>
)

export default Main

test.tsx:

import { render, screen } from '@testing-library/react'

import Main from '.'

describe('<Main />', () => {
  it('should render the heading', () => {
    const { container } = render(<Main />)
    expect(screen.getByRole('heading', { name: /react avançado/i }))
      .toBeInTheDocument()

    expect(container.firstChild).toMatchSnapshot()
  })

  it('should render styles correctly', () => {
    const { container } = render(<Main />)

    expect(container.firstChild).toHaveStyle(`
      background-color: #06092b;
      display: flex;
      flex-direction: column;
    `)
  })
})

Storybook

  • Instalar e configurar para utilizar global styles
  • Instalar http-server para teste de staticos
  • Instalar plugin de integração com o ESLint
  • Instalar extension Storybook helper (Riccardo Forina) no VSCode
  • Configurar stories para serem encontrados junto da pasta de cada componente
  • Nomenclatura
    • Iniciar named story exports com Letra Maiúcula
  • Configurar diretórios e ajustar para que a pasta public seja utilizada pelos stories
  • Alterar component Main para receber parâmetros

Context for mocking

Write stories

Configuração

cd ~/MeuProjeto/frontpage
yarn add --dev eslint-plugin-storybook
npx sb init
rm -rf stories
touch src/components/Main/stories.txs
brew install http-server

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{js,jsx}' --max-warnings=0",
    "test": "jest",
    "test:watch": "yarn test --watch",
    "storybook": "start-storybook -s ./public -p 6006",
    "build-storybook": "build-storybook -s ./public"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix",
      "yarn test --findRelatedTests --bail"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "styled-components": "^5.3.3"
  },
  "devDependencies": {
    "@babel/core": "^7.17.5",
    "@babel/preset-typescript": "^7.16.7",
    "@storybook/addon-essentials": "^6.4.19",
    "@storybook/react": "^6.4.19",
    "@storybook/testing-library": "^0.0.9",
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@types/styled-components": "^5.1.24",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "babel-loader": "^8.2.3",
    "babel-plugin-styled-components": "^2.0.6",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "eslint-plugin-storybook": "^0.5.7",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "jest-styled-components": "^7.0.8",
    "lint-staged": "^12.3.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}

.storybook/preview.js:

import GlobalStyles from '../src/styles/global'

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

export const decorators = [
  (Story) => (
    <>
      <GlobalStyles />
      <Story />
    </>
  ),
]

.storybook/main.js:

module.exports = {
  "stories": [
    "../src/components/**/*.stories.mdx",
    "../src/components/**/stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [ "@storybook/addon-essentials" ],
  "framework": "@storybook/react"
}
  • Configurar Componente Main e Executar Stories

src/components/Main/index.tsx:

import * as S from './styles'

type mainProps = {
  title: string
  description: string
}
const Main = ({
  title = 'React Avançado',
  description = 'TypeScript, ReactJS, NextJs e Styled Components'
}: mainProps) => (
  <S.Wrapper>
    <S.Logo
      src="/img/logo.svg"
      alt="Imagem de um átomo e React Avançado escrito ao lado."
    />
    <S.Title>{title}</S.Title>
    <S.Description>{description}</S.Description>
    <S.Illustration
      src="img/hero-illustration.svg"
      alt="Um desenvolvedor de frente para uma tela com código."
    />
  </S.Wrapper>
)

export default Main

src/components/Main/stories.tsx:

import { ComponentStory, ComponentMeta } from '@storybook/react'

import Main from '.'

export default {
  title: 'Main',
  component: Main,
    args: {
    title: 'Valores Padrão',
    description: 'Descrição Padrão'
  }
} as ComponentMeta<typeof Main>

const Template: ComponentStory<typeof Main> = (args) => <Main {...args} />

export const Basic = Template.bind({})
export const Advanced = Template.bind({})
export const Complex = Template.bind({})

Advanced.args = {
  title: 'React Advanced Args',
  description: 'TypeScript, ReactJS, NextJs e Styled Components'
}

Complex.args = {
  title: 'React Complex Args',
  description: 'TypeScript, ReactJS, NextJs e Styled Components'
}

Execução

yarn storybook

webpack built preview d8347124f2dac1508f80 in 4091ms
╭───────────────────────────────────────────────────╮
│                                                   │
│   Storybook 6.4.19 for React started              │
│   4.24 s for preview                              │
│                                                   │
│    Local:            http://localhost:6006/       │
│    On your network:  http://192.168.1.43:6006/    │
│                                                   │
╰───────────────────────────────────────────────────╯

Execução Estático

yarn build-storybook
cd ~/MeuProjeto/frontpage/storybook-static

http-server

Starting up http-server, serving ./

http-server version: 14.1.0

http-server settings:
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
  http://127.0.0.1:8080
  http://192.168.1.43:8080
Hit CTRL-C to stop the server

PWA - Progressive Web App

  • Funcionamento off-line
  • Instalar e Configurar puglins next-pwa
  • Configurar PWA ativo apenas em Produção
  • Configurar Manifest e Head Meta

Site Next-PWA

cd  ~/MeuProjeto/frontpage
yarn add next-pwa

next.config.js:

const withPWA = require('next-pwa')
const isProd = process.env.NODE_ENV === 'production'

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: !isProd,
  pwa: {
    dest: 'public',
    disable: !isProd
  }
}
module.exports = withPWA(nextConfig)

public/manifest.json:

{
  "name": "React Avançado - Boilerplate",
  "short_name": "React Avançado",
  "icons": [
    {
      "src": "/img/icon-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/img/icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "background_color": "#06092B",
  "description": "Boilerplate utilizando Typescript, React, NextJS e Styled Components!",
  "display": "fullscreen",
  "start_url": "/",
  "theme_color": "#06092B"
}

src/pages/_app.tsx:

import type { AppProps } from 'next/app'
import Head from 'next/head'

import GlobalStyles from 'styles/global'

function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>React Avançado - Boilerplate</title>
        <link rel="shortcut icon" href="/img/icon-512.png" />
        <link rel="apple-touch-icon" href="/img/icon-512.png" />
        <link rel="manifest" href="/static/manifest.json" />
        <meta
          name="description"
          content="A simple project starter to work with TypeScript, React, NextJS and Styled Components"
        />
      </Head>
      <GlobalStyles />
      <Component {...pageProps} />
    </>
  )
}

export default App

Teste PWA

  • Testar PWA colocando no ambiente de Produção
NODE_ENV=production
yarn build
yarn start

GitHub CLI & Pull Requests com DependaBot

  • Criar repositório no github
  • Fazer caching de github credentials
    • Instalar Github CLI gh
      • github.com
      • https
      • Autenticate to your GitHub credentias
      • Login with web browser
cd  ~/MeuProjeto/frontpage
brew install gh
gh auth login
git remote add origin https://github.com/<seu usuario git>/<boilerplate name>.git
git branch -M main
git push -u origin main
  • Habilitar dependabot no github
    • abrir repositório no github e procurar por settings/security/code security and analysis
    • habilitar dependabot alerts e dependabot security updates
  • Configurar diretório github e arquivos
cd ~/MeuProjeto/frontpage
mkdir .github
touch .github/dependabot.yml
cd .github

dependabot.yml:

version: 2
updates:
- package-ecosystem: yarn
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10
  • Atualizar repositório
cd ~/MeuProjeto/frontpage
git status
git add .
git status
git commit -m "INTEGRAÇÃO DEPENDABOT"
git push origin main
  • Entrar no github do projeto e ver os dependabot alerts

Workflow / Continous Integration no GitHub

  • Configurar diretórios e arquivos para integração
cd ~/MeuProjeto/frontpage/.github
mkdir workflows
touch workflows/ci.yml

Configuração Integração

workflows/ci.yml:

name: ci
on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 14.19.x

      - uses: actions/cache@v2
        id: yarn-cache
        with:
          path: |
            ~/cache
            !~/cache/exclude
            **/node_modules
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: Install dependencies
        run: yarn install

      - name: Linting
        run: yarn lint

      - name: Test
        run: yarn test:ci

      - name: Build
        run: yarn build

cd ~/MeuProjeto/frontpage

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0",
    "test": "jest",
    "test:ci":"jest",
    "test:watch": "yarn test --watch",
    "storybook": "start-storybook -s ./public -p 6006",
    "build-storybook": "build-storybook -s ./public"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix",
      "yarn test --findRelatedTests --bail"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "next-pwa": "^5.4.5",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "styled-components": "^5.3.3"
  },
  "resolutions": {
    "**/trim": "^1.0.0",
    "**/glob-parent": "^5.1.2"
  },
  "devDependencies": {
    "@babel/core": "^7.17.5",
    "@babel/preset-typescript": "^7.16.7",
    "@next/eslint-plugin-next": "^12.1.0",
    "@storybook/addon-essentials": "^6.4.19",
    "@storybook/react": "^6.4.19",
    "@storybook/testing-library": "^0.0.9",
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@types/styled-components": "^5.1.24",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "babel-loader": "^8.2.3",
    "babel-plugin-styled-components": "^2.0.6",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "eslint-plugin-storybook": "^0.5.7",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "jest-styled-components": "^7.0.8",
    "lint-staged": "^12.3.4",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}
  • Verificar funcionamento no github pull requests após enviar um push

Integração com Plop

Plop facilita a criação de componentes pela criação dos arquivos necessários: index.tsx, stories.tsx, styles.ts e test.tsx

  • Plop case modifiers

    • camelCase: changeFormatToThis
    • snakeCase: change_format_to_this
    • dashCase/kebabCase: change-format-to-this
    • dotCase: change.format.to.this
    • pathCase: change/format/to/this
    • properCase/pascalCase: ChangeFormatToThis
    • lowerCase: change format to this
    • sentenceCase: Change format to this,
    • constantCase: CHANGE_FORMAT_TO_THIS
    • titleCase: Change Format To This
  • Instalar e Configurar

PlopJS

cd ~/MeuProjeto/frontpage
yarn add --dev plop
mkdir generators
mkdir generators/templates
touch generators/plopfile.js
touch generators/templates/index.tsx.hbs

generators/plopfile.js:

module.exports = function (plop) {
  // create your generators here
  plop.setGenerator('component', {
    description: 'application component logic',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'component name please'
      }
    ],
    actions: [
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/index.tsx',
        templateFile: 'templates/index.tsx.hbs'
      },
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/stories.tsx',
        templateFile: 'templates/stories.tsx.hbs'
      },
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/styles.ts',
        templateFile: 'templates/styles.ts.hbs'
      },
      {
        type: 'add',
        path: '../src/components/{{pascalCase name}}/test.tsx',
        templateFile: 'templates/test.tsx.hbs'
      }
    ]
  })
}

generators/templates/index.tsx.hbs:

import * as S from './styles'

const {{pascalCase name}} = ({}) => (
  <S.Wrapper>
    <h1>{{pascalCase name}}</h1>
  </S.Wrapper>
)
export default {{pascalCase name}}

generators/templates/stories.tsx.hbs`

import { ComponentStory, ComponentMeta } from '@storybook/react'

import {{pascalCase name}} from '.'

export default {
  title: '{{pascalCase name}}',
  component: {{pascalCase name}}
} as ComponentMeta<typeof {{pascalCase name}}>

const Template: ComponentStory<typeof {{pascalCase name}}> = (args) => <{{pascalCase name}} {...args} />

export const Basic = Template.bind({})

generators/templates/styles.ts.hbs`

import styled from 'styled-components'

export const Wrapper = styled.main``

generators/templates/test.tsx.hbs`

import { render, screen } from '@testing-library/react'

import {{pascalCase name}} from '.'

describe('<{{pascalCase name}} />', () => {
  it('should render the heading', () => {
    // const { container } =
    render(<{{pascalCase name}} />)
    expect(screen.getByRole('heading', { name: /{{pascalCase name}}/i })).toBeInTheDocument

    //expect(container.firstChild).toMatchSnapshot()
  })
})

package.json:

{
  "name": "frontpage",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0",
    "test": "jest",
    "test:ci": "jest",
    "test:watch": "yarn test --watch",
    "storybook": "start-storybook -s ./public -p 6006",
    "build-storybook": "build-storybook -s ./public",
    "generate": "yarn plop --plopfile ./generators/plopfile.js"
  },
  "lint-staged": {
    "src/**/*": [
      "yarn lint --fix",
      "yarn test --findRelatedTests --bail"
    ]
  },
  "dependencies": {
    "next": "12.1.0",
    "next-pwa": "^5.4.5",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "styled-components": "^5.3.3"
  },
  "resolutions": {
    "**/trim": "^1.0.0",
    "**/glob-parent": "^5.1.2"
  },
  "devDependencies": {
    "@babel/core": "^7.17.5",
    "@babel/preset-typescript": "^7.16.7",
    "@next/eslint-plugin-next": "^12.1.0",
    "@storybook/addon-essentials": "^6.4.19",
    "@storybook/react": "^6.4.19",
    "@storybook/testing-library": "^0.0.9",
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@types/jest": "^27.4.1",
    "@types/node": "^17.0.21",
    "@types/react": "^17.0.39",
    "@types/styled-components": "^5.1.24",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "babel-loader": "^8.2.3",
    "babel-plugin-styled-components": "^2.0.6",
    "eslint": "^8.10.0",
    "eslint-config-next": "12.1.0",
    "eslint-config-prettier": "^8.4.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.2",
    "eslint-plugin-react-hooks": "^4.3.0",
    "eslint-plugin-storybook": "^0.5.7",
    "husky": "^7.0.4",
    "jest": "^27.5.1",
    "jest-styled-components": "^7.0.8",
    "lint-staged": "^12.3.4",
    "plop": "^3.0.5",
    "prettier": "2.5.1",
    "typescript": "4.5.5"
  }
}
  • Testar Plop na criação de um novo componente

yarn generate NomeComponente

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors