[Tutorial] - Como criar um boilerplate para projetos ReactJS utilizando o Vite
Vou mostrar o passo a passo de como realizar todas as configurações que utilizo nos meus projetos ReactJS utilizando o Vite
Baseado no boilerplate-apps-router do curso React Avançado
Passo a Passo
Escrevi o artigo denominado Como criar um boilerplate para projetos com Next.js, esse tutorial segue o mesmo modelo, contento partes iguais ao o texto anterior.
Inicializar o projeto:
Acesse o diretório desejado e execute os comandos abaixo:
mkdir vite-boilerplate
cd vite-boilerplate
npm init
git init
Dessa forma, foi criado a pasta do projeto, inicializado o git
e criado o arquivo package.json
, onde vamos deixar no seguinte molde:
{
"name": "vite-boilerplate",
"version": "1.0.0",
"description": "Boilerplate para projetos React utilizando o Vite",
"repository": "https://github.com/diasjoaovitor/next-boilerplate.git",
"author": "João Vitor <[email protected]>",
"license": "MIT"
}
Especificar a versão do Node:
Crie o arquivo .nvmrc
e adicione a versão a ser utilizada:
lts/iron
É obrigatório deixar uma linha em branco ao final do arquivo para que essa configuração funcione corretamente. Dessa forma, ao acessar o diretório do projeto e executar o comando nvm use
, o NVM
carrega automaticamente a versão especificada no arquivo.
Ensino como instalar o NVM e versões especificas do Node
no artigo Como configurar um ambiente de desenvolvimento com o WSL
Configurar o commit linter
Instale o git-commit-msg-linter:
npm i -D git-commit-msg-linter
Essa biblioteca verifica se a mensagem de um commit
contém um prefixo semântico como feat
, fix
, refactor
e demais convenções.
Crie o arquivo .gitignore
e adicione a pasta node_modules
Instalar o React, o Vite e configurar o Typescript
npm i react react-dom
Instale o vite o @vitejs/plugin-react-swc, o typescript e demais dependências:
npm i -D vite @vitejs/plugin-react-swc typescript @types/react @types/react-dom
Crie o diretório src
e adicione os arquivos App.tsx
, main.tsx
e vite-env.d.ts
:
export const App = () => {
return <div>App</div>
}
export default App
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
/// <reference types="vite/client" />
Crie o arquivo vite.config.ts
e index.html
na raiz do projeto:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})
<!doctype html>
<html lang="pt-br">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite Boilerplate</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Crie a pasta public
e adicione o ícone do projeto, no caso acima, o arquivo vite.svg
Crie os arquivos tsconfig.app.json
, tsconfig.node.json
e tsconfig.json
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmit": true
},
"include": ["vite.config.ts"]
}
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
-
composite
: Quando definido comotrue
, habilita o modo de projeto composto. Isso permite que o TypeScript otimize a compilação em projetos com dependências entre subprojetos. Por exemplo, se você tem um projeto com vários subdiretórios, o modo composto permite que o TypeScript recompile apenas os arquivos afetados em vez de todo o projeto. -
tsBuildInfoFile
: Especifica o caminho para o arquivo de informações de compilação (.tsbuildinfo
). Esse arquivo armazena informações sobre a compilação anterior, permitindo compilações incrementais mais rápidas. -
target
: Define a versão do ECMAScript para a qual o código TypeScript será compilado. No caso, para aES2020
. -
useDefineForClassFields
: Quando definido comotrue
, permite o uso da sintaxe de campos de classe com#
para campos privados. -
lib
: Especifica as bibliotecas de definições de tipo que o TypeScript deve incluir. Aqui, você está usando as bibliotecasES2020
,DOM
eDOM.Iterable
. -
module
: Define o formato de módulo de saída.ESNext
indica que o TypeScript deve usar o formato mais recente de módulo. -
skipLibCheck
: Quando definido comotrue
, evita a verificação de bibliotecas de definições de tipo. -
moduleResolution
: Define como os módulos são resolvidos.bundler
é usado para projetos que usam um empacotador (como Webpack). -
resolveJsonModule
: Permite a importação de arquivos JSON como módulos. -
isolatedModules
: Habilita a compilação de módulos independentes, sem a necessidade de um arquivo de saída. -
noEmit
: Quando definido comotrue
, impede a geração de arquivos de saída (JavaScript). -
jsx
: Define o tipo de sintaxe JSX a ser usado. Aqui, está configurado parareact-jsx
. -
strict
: Ativa várias verificações rigorosas, comonoUnusedLocals
,noUnusedParameters
enoFallthroughCasesInSwitch
. -
include
: Especifica os padrões de inclusão de arquivos no projeto. No seu caso, todos os arquivos dentro do diretóriosrc
serão incluídos na compilação. -
allowSyntheticDefaultImports
: Permite que você importe módulos que não têm uma exportação padrão.
Defina "type": "module"
e adicione os scripts
no arquivo package.json
:
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
}
}
Adiciona a pasta dist
no arquivo .gitignore
Configurar as regras do editor e do código
Instale o módulo e a extensão do prettier:
npm i -D prettier
Crie o arquivo .prettierrc.json
:
{
"trailingComma": "none",
"semi": false,
"singleQuote": true
}
"trailingComma": "none"
: Isso indica que o Prettier não deve adicionar vírgulas ao final de listas, objetos ou parâmetros de função quando eles são formatados em várias linhas."semi": false
: Isso significa que o Prettier não deve adicionar ponto e vírgula ao final de cada instrução."singleQuote": true
: Isso especifica que o Prettier deve usar aspas simples em vez de aspas duplas, sempre que possível.
Adicione os scripts
:
{
"scripts": {
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write ."
}
}
Defina o prettier
como o formatador padrão do VSCode em Default Formatter
e habilite a opção Format On Save
Crie a pasta .vscode
e inclua o arquivo settings.json
:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "off",
"git.autofetch": true
}
"editor.formatOnSave": true
- Esta opção faz com que o VSCode formate automaticamente o código quando você salva um arquivo. Isso ajuda a manter o código limpo e consistente com as regras de formatação definidas."editor.defaultFormatter": "esbenp.prettier-vscode"
- Define o Prettier como o formatador de código padrão. O Prettier é uma ferramenta popular que suporta muitas linguagens e estilos de codificação."files.autoSave": "off"
- Desativa o salvamento automático de arquivos. Com essa configuração, os arquivos não serão salvos automaticamente após um período ou quando o foco é alterado; você precisará salvar manualmente suas alterações."git.autofetch": true
- Quando habilitado, o VSCode buscará automaticamente as alterações mais recentes do seu repositório Git periodicamente. Isso é útil para manter seu repositório local atualizado com as alterações remotas.
Crie o arquivo .editorconfig
:
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-
root = true
: Esta configuração sinaliza que este é o arquivo de configuração principal e que o EditorConfig não deve procurar por outros arquivos de configuração nas pastas acima. -
[*]
: Este é um padrão de correspondência que se aplica a todos os arquivos no projeto. -
indent_style = space
: Define que o estilo de indentação deve ser feito com espaços em vez de tabulações. -
indent_size = 2
: Especifica que o tamanho da indentação deve ser de dois espaços. -
end_of_line = lf
: Indica que o final de linha deve ser formatado usando LF (Line Feed), que é o padrão para sistemas Unix e macOS. -
charset = utf-8
: Define que o conjunto de caracteres do arquivo deve ser UTF-8. -
trim_trailing_whitespace = true
: Quando verdadeiro, remove automaticamente qualquer espaço em branco no final das linhas ao salvar o arquivo. -
insert_final_newline = true
: Garante que haja uma nova linha no final do arquivo ao salvar.
Configurar Linting
O linting é o processo de aplicar regras a uma base de código e destacar padrões ou códigos problemáticos que não aderem a determinadas diretrizes de estilo. ESLint permite que os desenvolvedores descubram problemas com seu código sem a necessidade de executá-lo.
Instale o eslint, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint-plugin-react-hooks,
eslint-plugin-react-refresh e o eslint-config-prettier:
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-config-prettier
Crie o arquivo .eslintrc.cjs
:
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true }
]
}
}
Adicione o script
:
{
"scripts": {
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
}
}
Instale o lint-staged e o husky
npm i -D lint-staged husky
Crie o arquivo .lintstagedrc.cjs
:
const path = require('path')
const buildCommand = (filenames) => {
const files = filenames.map((f) => path.relative(process.cwd(), f))
return [
`npx prettier --write ${files.join(' --file ')}`,
`npx eslint --fix ${files.join(' ')} --report-unused-disable-directives --max-warnings 0`
]
}
module.exports = {
'*.{js,jsx,ts,tsx}': [buildCommand]
}
Inicie as configurações do husky
:
npx husky init
Dessa forma, foi criado o script prepare
no arquivo package.json
e a pasta .husky
, onde vamos alterar o conteúdo do arquivo pre-commit
para:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install lint-staged
Configurar testes automatizados
Instale as bibliotecas de testes:
npm i -D vitest @vitest/ui jsdom @testing-library/jest-dom @testing-library/react @testing-library/user-event
Crie o arquivo vitest.setup.ts
:
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'
afterEach(() => {
cleanup()
})
Adicione as configurações no arquivo vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts']
}
})
Inclua o setup no arquivo tsconfig.app.json
:
{
"include": ["src", "./vitest.setup.ts"]
}
Adicione os scripts
:
{
"scripts": {
"test": "vitest --run",
"test:watch": "vitest",
"test:ui": "vitest --ui"
}
}
Adicione o comando para rodar os testes no arquivo lintstagedrc.cjs
:
const path = require('path')
const buildCommand = (filenames) => {
const files = filenames.map((f) => path.relative(process.cwd(), f))
return [
`npx prettier --write ${files.join(' --file ')}`,
`npx eslint --fix ${files.join(' ')} --report-unused-disable-directives --max-warnings 0`,
`npx vitest related ${files.join(' ')} --passWithNoTests --no-file-parallelism --run`
]
}
module.exports = {
'*.{js,jsx,ts,tsx}': [buildCommand]
}
Configurar Integração Contínua
Adicione o script "test:ci": "vitest --no-file-parallelism --run"
no package.json
e crie o arquivo ci.yml
na pasta .github/workflows
:
name: ci
on: [pull_request] # O workflow é acionado quando um pull request é aberto, sincronizado ou reaberto.
jobs:
build: # Este é o trabalho de construção que será executado.
runs-on: ubuntu-latest # O trabalho será executado na última versão do Ubuntu disponível.
steps: # Seguem os passos que serão executados em sequência.
- name: Checkout Repository
uses: actions/checkout@v4 # Este passo faz o checkout do seu repositório usando a ação checkout v4.
- name: Setup Node
uses: actions/setup-node@v4 # Este passo configura o ambiente Node.js usando a ação setup-node v4.
with:
node-version: lts/iron # Define a versão do Node.js para a versão LTS mais recente chamada "Iron".
cache: 'npm' # Habilita o cache para o gerenciador de pacotes NPM.
- name: Install dependencies
run: npm install # Este passo instala as dependências listadas no arquivo package-lock.json.
- name: Linting
run: npm run lint # Este passo executa o linting no código para verificar erros de estilo.
- name: Testing
run: npm run test:ci # Este passo executa os testes definidos para o projeto.
- name: Build
run: npm run build # Este passo constrói o projeto.
Configurar gerador de componentes
Instale o Plop
npm i -D plop
Crie a pasta generators
na raiz do projeto e adicione o arquivo plopfile.js
:
export default (plop) => {
plop.setGenerator('component', {
description: 'Create a component',
prompts: [
{
type: 'input',
name: 'name',
message: 'What is your component name?'
}
],
actions: [
{
type: 'add',
path: '../src/components/{{pascalCase name}}/index.tsx',
templateFile: 'templates/Component.tsx.hbs'
},
{
type: 'add',
path: '../src/components/{{pascalCase name}}/{{kebabCase name}}.test.tsx',
templateFile: 'templates/test.tsx.hbs'
}
]
})
}
Crie o arquivo .prettierignore
e ignore todos os templates:
generators/templates
Dentro de generators
, crie o templates index.tsx.hbs
e test.tsx.hbs
:
export const {{pascalCase name}} = () => {
return (
<div>{{pascalCase name}}</div>
)
}
import { describe, expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import { {{pascalCase name}} } from '.'
describe('<{{pascalCase name}} />', () => {
test('should render component', () => {
render(<{{pascalCase name}} />)
expect(screen.getByText('{{pascalCase name}}')).toBeInTheDocument()
})
})
Adicione o script
:
{
"scripts": {
"generate": "plop --plopfile generators/plopfile.js"
}
}
Crie um componente:
npm run generate MyComponent
Assim, foi gerado um componente e um teste seguindo o padrão dos templates.
Configurar o alias
Defina as configurações no arquivo tsconfig.app.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
Adicione as configurações no arquivo vite.config.json
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts']
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/shared')
}
}
})
Dentro da pasta components
, crie o arquivo index.ts
e exporte o componente:
export * from './MyComponent'
Essas configurações ajudam a importar todos os componentes exportados nesse arquivo através do alias @/components
e podem ser replicadas para todas as pastas presentes em src
Considerações finais
Este é um boilerplate
genérico para projetos React
utilizando o Vite
.
O repositório está disponível no meu perfil do GitHub e está aberto a contribuições.