Configurando React com Typescript sem CRA

dfirmino

Davi Firmino

Posted on October 26, 2020

Configurando React com Typescript sem CRA

Escrevo esse post pois não é fácil encontrar algum material que explique de forma eficiente como se montar um boilerplate com react e typescript sem o CRA (Create-React-App).
Embora a grande maioria dos casos o cra atenda perfeitamente, pode existir um determinado caso que em função da sua arquitetura você queira fazer um setup manual.
O cra é muito útil e longe de mim querer criticá-lo porém ele pode deixar sua arquitetura um pouco engessada.
Esse post irá explicar de forma detalhada cada passo do projeto, caso você não queira ler pule para o final do arquivo com o link do repositório.

Padrões

Antes de começarmos de fato, vamos definir alguns padrões para que o projeto não vire a casa da mãe joanna.
Para os commits utilizaremos o conventional commits

Começando

Primeiro vamos criar a nossa pasta para iniciar o projeto

mkdir boilerplate && cd boilerplate
Enter fullscreen mode Exit fullscreen mode

Agora vamos iniciar o package.json

npm init -y
Enter fullscreen mode Exit fullscreen mode

Vamos iniciar o git

git init
Enter fullscreen mode Exit fullscreen mode

Vamos adicionar um biblioteca para ajudar a gente a manter o padrão das mensagens de commit.
git-commit-msg-linter

npm i -D git-commit-msg-linter
Enter fullscreen mode Exit fullscreen mode

Vamos agora criar o .gitignore

echo "node_modules\ncoverage\npublic/js" > .gitignore
Enter fullscreen mode Exit fullscreen mode

Vamos instalar o typescript

npm i -D typescript
Enter fullscreen mode Exit fullscreen mode

Agora o types do node (para garantir a tipagem do node)

npm i -D @types/node
Enter fullscreen mode Exit fullscreen mode

Vamos agora criar o arquivo de configuração do typescript

touch tsconfig.json
Enter fullscreen mode Exit fullscreen mode

dentro dele vamos digitar o seguinte:

{
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react",
    "rootDir": "src",
    "baseUrl": "src",
    "allowJs": true,
    "resolveJsonModule": true,
    "isolatedModules": false,
  },
  "include": [
    "src"
  ],
  "exclude": []
}
Enter fullscreen mode Exit fullscreen mode

Caso você não entenda essas configurações você pode consultar aqui.

Agora vamos configurar o eslint, existem algumas formas para fazer isso eu vou escolher a que eu considero a mais fácil.

npx eslint --init
Enter fullscreen mode Exit fullscreen mode

Vamos marcar a opção:
To check syntax and find problems
o code style será feito pelo prettier daqui a pouco
Depois marcamos:
JavaScript modules (import/export)
React
yes
Browser
JSON
yes

Vamos adicionar um plugin para que nosso lint consiga trabalhar com hooks:

npm i -D eslint-plugin-react-hooks
Enter fullscreen mode Exit fullscreen mode

Agora vamos configurar o prettier:

npm i -D prettier eslint-config-prettier eslint-plugin-prettier
Enter fullscreen mode Exit fullscreen mode

Agora vamos editar nosso .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": 12,
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint",
        "react-hooks"
    ],
    "rules": {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react/prop-types": "off",
      "@typescript-eslint/explicit-function-return-type": "error",
      "react/jsx-uses-react": "error",
      "react/jsx-uses-vars": "error"
    }
}
Enter fullscreen mode Exit fullscreen mode

referências aqui

Vamos criar nosso .eslintignore

echo "node_modules\njest.config.js\ncoverage\npublic\nwebpack.config.js\n*.scss" > .eslintignore   
Enter fullscreen mode Exit fullscreen mode

Vamos criar nosso .prettierrc:

touch .prettierrc
Enter fullscreen mode Exit fullscreen mode

Dentro dele vamos colocar:

{
  "trailingComma": "none",
  "semi": false,
  "singleQuote": true
}
Enter fullscreen mode Exit fullscreen mode

referências aqui

Vamos adicionar um script de lint no nosso package.json para facilitar o trabalho:

"lint": "eslint src"
Enter fullscreen mode Exit fullscreen mode

Vamos agora adicionar o lint-staged para poder executar ações em nossos arquivos que estão na staged area do git

npm i -D lint-staged
Enter fullscreen mode Exit fullscreen mode

Vamos adicionar também o husky para criarmos hooks no git

npm i -D husky
Enter fullscreen mode Exit fullscreen mode

vamos criar nosso arquivo .lintstagedrc

touch .lintstagedrc.json
Enter fullscreen mode Exit fullscreen mode

dentro dele vamos colocar

{
  "*.{ts,tsx}" : [
      "eslint 'src/**' --fix ",
      "npm run test:staged"
  ]
}
Enter fullscreen mode Exit fullscreen mode

vamos criar agora nosso huskyrc:

touch .huskyrc.json
Enter fullscreen mode Exit fullscreen mode

dentro dele vamos colocar:

{
    "hooks": {
        "pre-commit": "lint-staged && npm run test:ci",
    }
}
Enter fullscreen mode Exit fullscreen mode

Vamos agora configurar o jest para nossos testes

npm i -D jest @types/jest ts-jest
Enter fullscreen mode Exit fullscreen mode

Vamos criar o arquivo de configuração do jest

touch jest.config.js
Enter fullscreen mode Exit fullscreen mode

dentro dele

module.exports = {
  roots: ['<rootDir>/src'],
  collectCoverageFrom: ['<rootDir>/src/**/*.{ts,tsx}', '!**/*.d.ts'],
  coverageDirectory: 'coverage',
  testEnvironment: 'jsdom',
  transform: {
    '.+\\.(ts|tsx)$': 'ts-jest'
  },
  moduleNameMapper: {
    '\\.scss$': 'identity-obj-proxy'
  }
}
Enter fullscreen mode Exit fullscreen mode

referências aqui

Vamos adicionar agora alguns scripts pra teste no package.json

"test": "jest --passWithNoTests --no-cache --verbose --runInBand",
"test:watch": "npm run test -- --watch",
"test:staged": "npm run test -- --findRelatedTests",
"test:ci": "npm run test -- --coverage",
Enter fullscreen mode Exit fullscreen mode

Finalmente React \o/

Antes de prosseguir vamos certificar que seu projeto está igual ao meu:
folder

Vamos começar instalando o react e o react dom

npm i react react-dom
Enter fullscreen mode Exit fullscreen mode

e o types deles

npm i -D @types/react @types/react-dom
Enter fullscreen mode Exit fullscreen mode

na raiz do projeto vamos criar uma pasta com o nome de public

mkdir public
Enter fullscreen mode Exit fullscreen mode

dentro dessa pasta vamos criar um index.html com o seguinte conteudo

<!DOCTYPE html>
<html lang="pt-br">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="js/bundle.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

dentro de public vamos criar uma pasta js

vamos instalar e configurar o webpack agora

npm i -D webpack webpack-cli webpack-dev-server
Enter fullscreen mode Exit fullscreen mode

vamos criar o arquivo do webpack.config.js na raiz do projeto
e dentro dele vamos adicionar o seguinte

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = env => {
  const mode = env.development ? 'development' : 'production'
  return {
    mode,
    entry: path.join(__dirname, 'src', 'index.tsx'),
    output: {
      path: path.join(__dirname, 'public', 'js'),
      publicPath: path.join('public', 'js'),
      filename: 'bundle.js'
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.scss', '.css'],
    },
    module: {
      rules: [{
        test: /\.ts(x?)$/,
        loader: 'ts-loader',
        exclude: /node_modules/
      }, {
        test: /\.(s)css$/,
        use: [{
          loader: 'style-loader'
        }, {
          loader: 'css-loader',
          options: {
            modules: true
          }
        }, {
          loader: 'sass-loader'
        }]
      }]
    },
    devServer: {
      contentBase: path.join(__dirname, 'public'),
      writeToDisk: true,
      historyApiFallback: true,
      open: true
    },
    plugins: [
      new CleanWebpackPlugin(),
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

vamos adicionar os plugins necessários pra fazer o webpack funcionar:

npm i -D clean-webpack-plugin node-sass sass-loader css-loader style-loader ts-loader
Enter fullscreen mode Exit fullscreen mode

Analisando dependências:

  • clean-webpack-plugin - Plugin para limpar a pasta de build (ajuda com o cache).
  • node-sass - Para conseguir usar sass dentro do node
  • css-loader - Para que o webpack entenda algumas coisas como: @import, url()...
  • style-loader - para o webpack consiga colocar o style no DOM.
  • sass-loader - Loader para que o webpack consiga trabalhar com sass
  • ts-loader - Para o webpack entender o typescript

Vamos criar uma pasta src e dentro dela um arquivo sass-module.d.ts com o seguinte:

declare module '*.scss' {
  const content: { [className: string]: string }
  export = content
}
Enter fullscreen mode Exit fullscreen mode

dentro de src vamos criar o arquivo index.tsx com o seguinte conteúdo:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'

ReactDOM.render(<App />, document.getElementById('app'))

Enter fullscreen mode Exit fullscreen mode

dentro de src vamos criar a pasta components e criar o App.tsx
com o seguinte conteúdo:

import React from 'react'
import Styles from './App-styles.scss'

const App: React.FC = () => {
  return (
    <h1 data-testid="teste" className={Styles.h1}>
      Glória a Deuxxx
    </h1>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

ainda dentro de components vamos criar o App-styles.scss:

.h1 {
  color: tomato;
}
Enter fullscreen mode Exit fullscreen mode

E vamos criar um arquivo App.spec.tsx vazio por enquanto.
Por fim adicionamos o script de start e build no package.json:

 "start": "webpack serve --env development",
 "build": "webpack --env production",
Enter fullscreen mode Exit fullscreen mode

Nossa estrutura até aqui:
estrutura

Preparando os testes no react

antes da gente começar a configurar os testes temos que instalar um pacote identity-obj-proxy para que o jest não "encrenque"com o sass.

npm i -D identity-obj-proxy
Enter fullscreen mode Exit fullscreen mode

Nosso conf do jest já está configurado para utilizar essa dependências você pode ir lá rever o arquivo caso ache necessário.

Vamos adicionar o testing-library

npm i -D @testing-library/react
Enter fullscreen mode Exit fullscreen mode

Agora dentro do App.spec.tsx vamos adicionar:

import React from 'react'
import { render } from '@testing-library/react'
import App from './App'

describe('teste do boilerPlate', () => {
  test('App', () => {
    const { getByTestId } = render(<App />)
    const h1 = getByTestId('teste')
    expect(h1.innerHTML).toBe('Glória a Deuxxx')
  })
})

Enter fullscreen mode Exit fullscreen mode

Tudo pronto

Foi um post longo acho que ninguém vai ler tudo mas a minha intenção foi mais explicar em detalhes do que simplesmente um código pra copiar e colar.
Esse é o respositório do boilerplate:
https://github.com/dfirmino/react-boilerplate
Qualquer dúvida, sugestão ou xingamento é só mandar lá no git.
Valeu Falou

Dicas

Podemos configurar o lint para ser executado após o save do arquivo, vou colocar um exemplo do meu settings do vscode mas aconselho da uma pesquisada na internet e ajustar no seu:
Settings

💖 💪 🙅 🚩
dfirmino
Davi Firmino

Posted on October 26, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related