Na prática: TypeScript + ESLint + Prettier + EditorConfig + Jest, Supertest...
Este material tem como finalidade apresentar de forma prática como configurar a suíte de ferramentas mencionadas no titulo deste material. Ou seja, este material não visa ser didático, mas sim prático. Paralelamente a isso, estou criando uma série de documentos com abordagem didática sobre o mesmo tema…
📚 Livros
No meu repositório no GitHub, você encontrará um link para o download de alguns livros publicados por mim. Além disso, poderá acompanhar o desenvolvimento das minhas obras mais recentes, como o livro sobre Node.js para iniciantes. Acesse: https://github.com/fabiojaniolima/livros
🔗 Sugestão de leitura
Os posts a seguir são meras sugestões ou recomendações de leitura e não devem ser considerados pré-requisitos para a leitura ou execução das práticas descritas neste material:
- Introdução ao Node.js (Single-Thread, Event-Loop e mercado)
- NVM - Gerencie múltiplas instalações do Node.js
- Node.js + Express + ES6 + ESLint + Prettier + Sucrase de forma simples e rápida
- Node.js: web scraping com Puppeteer
- EditorConfig - Padronizando características essenciais
Dica: Recomendo fortemente o uso do ESLint, Prettier e EditorConfig em todos os projetos, pois é uma boa prática manter a consistência em diversos aspectos.
❗️Pré-requisitos
- Node.js
- NPM, Yarn…
- VSCode*
Ao longo deste material, utilizarei o NPM como gerenciador de pacotes. No entanto, você está livre para usar o Yarn ou qualquer outro gerenciador de sua preferência. Quanto ao editor de código, farei uso do VSCode, mas sinta-se à vontade para escolher o editor que mais lhe agrada.
📦 Pacotes utilizados
- typescript
- tsx
- supertest
- rimraf
- eslint
- prettier
- jest
- dotenv
- cors
- express
Observe que optei pelo ESLint em substituição ao TSLint. Esta escolha deve-se ao fato de que, atualmente, o ESLint oferece suporte completo às funcionalidades do TypeScript. Adicionalmente, a equipe de desenvolvimento do TSLint tem incentivado essa migração e concentrado esforços no desenvolvimento do ESLint.
🧑💻 Mãos a obra
Para os passos a seguir, estou partindo do pressuposto de que você já inicializou um diretório como um projeto Node. Por exemplo:
npm init -y
EditorConfig
Este é um daqueles plugins de uso geral, ou seja, independe da tecnologia. Confira minha configuração de uso geral:
root = true
[*]
charset = utf-8
indent_size = 2
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
indent_size = 4
Para saber mais, leia meu artigo: EditorConfig - Padronizando características essenciais
Configurações iniciais
Vamos começar instalando nossas primeiras dependências de desenvolvimento:
npm install typescript @types/node rimraf tsx -D
typescript
: instala o TypeScript e utilitário tsc.@types/node
: oferece definições de tipos para o conjunto nativo de APIs/módulos do NodeJS.rimraf
: será usado como um utilitário para excluir automaticamente builds antigas.tsx
: permite executar TypeScript em ambiente de desenvolvimento.
Para gerar o tsconfig.json
(arquivo de definições do TypeScript), execute o comando a seguir:
npx tsc --init
O arquivo gerado é autoexplicativo, contendo configurações e suas respectivas explicações em comentários. Edite o arquivo tsconfig.json
, mantendo apenas as configurações a seguir:
{
"compilerOptions": {
/* Language and Environment */
"target": "es2021",
"lib": ["ES2021"],
/* Modules */
"module": "commonjs",
"rootDir": "./src",
/* JavaScript Support */
"allowJs": true,
"checkJs": true,
/* Emit */
"outDir": "./build",
"sourceMap": false,
"removeComments": true,
/* Interop Constraints */
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
/* Type Checking */
"strict": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
Edite o arquivo package.json
e adicione o alias de build ao bloco scripts:
{
"name": "project",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "rimraf ./build && tsc",
//...omitido
⚠️ Após a configuração do arquivo
tsconfig.json
, é comum se deparar com um erro do seguinte tipo:No inputs were found in config file '/home/fabio/project/tsconfig.json'. Specified 'include' paths were '["src/**/*"]' and 'exclude' paths were '["node_modules","build"]'.
Caso isso ocorra, não há motivo para preocupação. Esse erro acontece porque, até o momento, não existe código no projeto, e o transpiler está sinalizando justamente essa ausência.
ESLint e Prettier
Para assegurar uma boa sintaxe e formatação, é essencial utilizar estes dois recursos em conjunto. Para instalá-los, execute o seguinte comando:
npm install eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-import eslint-plugin-simple-import-sort -D
eslint
: responsável pelo lint do código, verifica erros de sintaxe, formatação e outros aspectos.prettier
: focado na formatação e padronização do código.eslint-config-prettier
: desativa as regras do ESLint que podem entrar em conflito com o Prettier.eslint-plugin-prettier
: permite executar o Prettier como uma regra do ESLint.eslint-plugin-import
: aplica regras de agrupamento e validação para imports e exports.eslint-plugin-simple-import-sort
: organiza os imports em ordem alfabética.
Execute o comando abaixo para gerar o arquivo de configuração .eslintrc.*
:
npm init @eslint/config
Reproduza as respostas conforme abaixo:
- O ESLint será configurado com foco na verificação de sintaxe e detecção de problemas:
- Utilizaremos ECMAScript Modules (import/export):
- Não utilizaremos nenhum framework:
- Utilizaremos TypeScript:
- Nossa app é uma aplicação backend, ou seja, roda do lado do servidor com NodeJS:
- O arquivo de configuração do ESLint será gerado em formato JSON:
- Pode ser necessário instalar alguns pacotes adicionais, sendo assim selecione Yes:
- Selecione seu gerenciador de pacotes:
Edite o arquivo .eslintrc.json
e ajuste os blocos extends
, plugins
, ignorePatterns
e rules
conforme abaixo:
{
"env": {
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "import", "simple-import-sort"],
"ignorePatterns": ["build", "coverage", "package-lock.json"],
"rules": {
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
},
"overrides": [
{
"files": ["*.js"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
]
}
Uma vez que o arquivo de configurações do ESLint está pronto, é hora de configurar as regras que desejamos aplicar no Prettier. Para configurar o Prettier, crie na raiz do projeto um arquivo chamado .prettierrc.json
com o seguinte conteúdo:
{
"semi": true,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"arrowParens": "always",
"trailingComma": "all"
}
As configurações mencionadas acima refletem apenas uma preferência pessoal para formatação estética. Portanto, sinta-se à vontade para desenvolver seu próprio estilo, caso prefira. Abaixo, explico o significado de cada uma das opções utilizadas:
semi
: quandotrue
, indica que a linha deve terminar com ponto e vírgulatabWidth
: define o número de espaços utilizados para a indentação.printWidth
: estabelece o comprimento máximo da linha de código antes de aplicar uma quebra de linha.singleQuote
: quandotrue
, especifica o uso de aspas simples como padrão.arrowParens
: sempre utiliza parênteses ao redor de um único parâmetro em funções arrow.trailingComma
: adiciona vírgulas ao final de listas multilinhas, facilitando a adição de novos itens sem esquecer da vírgula anterior.
Observação: a lista completa de regras/opções disponíveis pode ser encontrada no link: https://prettier.io/docs/en/options.html.
Edite o package.json
para incluir um novo alias/script chamado lint:fix
conforme abaixo:
{
"name": "project",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "rimraf ./build && tsc",
"lint:fix": "eslint --fix --ext .js,.mjs,.ts,.json .",
//...omitido
A instrução lint:fix
possibilita a execução do ESLint via terminal, isto é, em modo CLI (Interface de Linha de Comando). Para habilitar a funcionalidade de correção automática ao salvar um arquivo (autoFixOnSave
), é necessário configurar essa opção no seu editor de código.
Jest
Instalando dependências:
npm install jest ts-jest eslint-plugin-jest @types/jest -D
Para criar o arquivo de definições do Jest (jest.config.js
), execute:
npx jest --init
Um questionário será exibido. Responda a este questionário de acordo com as referências fornecidas abaixo:
O passo 4 possibilita trabalhar com a cobertura de código. Isso significa que podemos especificar caminhos a serem monitorados pelo Jest, que, por sua vez, irá reportar a porcentagem do código coberta por testes e identificar as partes não cobertas. Caso prefira não utilizar esse recurso, basta responder 'n'.
Prossiga editando o arquivo jest.config.js
. Localize as variáveis preset
e testMatch
e defina-as com os valores a seguir:"
preset: 'ts-jest',
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
Na configuração acima estamos buscando testes que estejam dentro de um diretório
__tests__
, não importa onde esse esteja dentro do projeto, arquivos*.spec.ts
ou*.test.ts
.
Agora, edite o arquivo .eslintrc.json
e adicione a propriedade "jest": true
conforme abaixo:
{
"env": {
"es2021": true,
"node": true,
"jest": true
//...omitido
Caso deseje utilizar a funcionalidade de Cobertura de Código do Jest, será necessário editar o arquivo jest.config.js
. Localize e defina as variáveis da seguinte maneira:
collectCoverageFrom: ['<rootDir>/src/services/**/*.ts'],
coverageReporters: ["text-summary", "lcov"],
No campo collectCoverageFrom
, informe um array com os caminhos dos arquivos que devem ser incluídos na cobertura. Por exemplo, para cobrir outro diretório no mesmo nível de services
, adicione uma expressão regular: <rootDir>/src/{services,helpers}/**/*.ts
. Para mais padrões de expressões regulares, consulte a documentação oficial.
Os resultados da Cobertura de Código serão salvos no diretório especificado pela variável coverageDirectory
. Não se esqueça de adicionar este diretório ao .gitignore
.
Agora, crie o arquivo __tests__/sum.ts
e insira o seguinte conteúdo:
describe('Only one test', () => {
it('Sum 1 + 1 = 2', () => {
expect(1 + 1).toBe(2)
})
})
O arquivo mencionado é utilizado exclusivamente para verificar se o Jest está operando corretamente. Agora, proceda com a execução do seguinte comando:
npm run test
Como resultado, teremos o seguinte output na console:
Vamos avaliar cada uma das partes retornadas:
- Resultado de cada uma das etapas.
- Resumo da Cobertura de Testes (Coverage Summary). Lembre-se de que, no arquivo
jest.config.js
, configuramos a opçãocollectCoverageFrom
. O Coverage Summary calcula a cobertura de testes para os caminhos especificados nessa configuração. - Resumo do teste
Realmente é impressionante! No entanto, a cereja do bolo se encontra em ./coverage/lcov-report/index.html
. Abra esse arquivo no seu navegador para visualizar a cobertura dos seus testes.
Como ainda não temos código sendo testado em nossa aplicação, o resultado será uma página sem dados de cobertura.
Agora, será necessário editar o tsconfig.json
e adicionar coverage
, __tests__
e **/*.spec.ts
à lista de itens a serem ignorados:"
"exclude": [
"node_modules",
"build",
"coverage",
"**/__tests__",
"**/*.spec.ts",
"**/*.test.ts"
]
Scripts e rotinas de execução (package.json)
Edite o package.json
, localize a sessão main
e scripts
e altere estas conforme abaixo:
"main": "./build/start/server.js",
"scripts": {
"build": "rimraf ./build && tsc",
"start:dev": "tsx ./src/start/server.ts",
"start:inspect": "tsx --inspect-brk ./src/start/server.ts",
"start:watch": "tsx watch ./src/start/server.ts",
"lint:fix": "eslint --fix --ext .js,.mjs,.ts,.json .",
"test": "jest"
}
main
: define o ponto de entrada da aplicação. Aqui, ele aponta para o diretório de build, ou seja, ao executarnode .
, este arquivo será o inicializado.build
: emprega orimraf
para remover o diretório./build
e, em seguida, compila os arquivos JavaScript usando o tsc.start:dev
: inicializa o servidor em modo desenvolvimento.start:inspect
: inicializa o servidor em modo desenvolvimento com inspect ativo para debug — no final deste material mostrarei como fazer o debug pelo VSCode.start:watch
: inicia o servidor de desenvolvimento em modo de observação ('watch').lint:fix
: habilita a execução do ESLint através do terminal.test
: executa o Jest.
Para rodar qualquer um dos scripts definidos, utilize o comando:
npm run nome_do_script
Por exemplo:
npm run lint:fix
⚠️ Como nosso diretório
./src
ainda não contém arquivos TypeScript, a execução de qualquer outro script resultará em erro. Não se preocupe, pois isso é esperado."
Express
Vamos as dependências:
npm install express dotenv cors
express
: micro-framework simples e flexível;cors
(cross-origin): é uma especificação que define meios para que um recurso do servidor seja acessado remotamente via web/rede; Resumidamente, o cors permite que nossa aplicação seja acessada de um endereço externo;dotenv
: é um módulo de dependência zero, responsável por carregar variáveis de ambiente de um arquivo.env
emprocess.env.*
.
Será necessário instalar as definições de tipo do Express:
npm install @types/express @types/cors -D
Agora crie na raiz do projeto um arquivo .env
e atribua o valor abaixo a ele:
APP_PORT=3000
Este arquivo irá centralizar informações sensíveis da aplicação e por isso não é versionado. Crie um arquivo
.env-sample
de amostra, este pode conter informações pre-preenchidas não sensíveis. Exemplo:DB_HOST="localhost"
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASS=
Crie o arquivo de rotas ./src/routes.ts
:
import { Router } from 'express';
const router = Router();
router.get('/', (_, res) => {
res.json({ message: 'Hello world!' });
});
export default router;
Agora crie o arquivo ./src/start/app.ts
com o seguinte conteúdo:
import 'dotenv/config'
import cors from 'cors'
import express, { Errback, Request, Response } from 'express'
import router from '../routes'
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(router)
app.use((_: Request, res: Response) => {
res.status(404).json({ message: 'Page Not Found' })
})
app.use((_: Errback, _req: Request, res: Response) => {
return res.status(500).json({ message: 'Internal Error' })
})
export default app
Crie o arquivo ./src/start/server.ts
, esse arquivo é o responsável por subir o servidor na porta informado no arquivo .env
:
import app from './app'
const port = parseInt(process.env.APP_PORT as string) || 3000
app.listen(port, () => {
console.log('\x1b[33m%s\x1b[0m', `=> 🚀 Server running on the port: ${port}`)
})
Para testar o resultado basta executar na raiz do projeto:
npm run start:dev
Se tudo correu bem teremos como retorno o seguinte output:
Show de bola! Nossa aplicação está rodando na porta 3000
, para acessar basta chamar: http://localhost:3000 e teremos um lindo e belo JSON como output:
{ "message": "Hello World!" }
Supertest
Bom, nossa aplicação com Express é extremamente simples, porém, suficiente para realizarmos testes reais com o Jest. Nossa missão é disparar uma requisição contra a rota raiz (GET /
) e verificar se o status de retorno é 200 e se a propriedade message
foi devolvida.
Para facilitar nossa vida vamos instalar uma lib projetada para lidar com testes que envolvem requisições HTTP:
npm install supertest @types/supertest -D
Você pode excluir o arquivo de teste
__tests__/sum.ts
se preferir, afinal de contas ele tinha um único objetivo, verificar se o Jest estava de fato funcionando.
Edite novamente o arquivo jest.config.js
e:
...// Por conta de limitação aqui da plataforma (body < 20000 caracteres) eu não consigo postar o conteúdo completo. Continue lendo em: https://fabiojanio.medium.com/na-prática-typescript-eslint-prettier-editorconfig-jest-supertest-7206684ab55d