Organizando um projeto JavaScript
LidoNesta aula, você aprenderá a organizar um projeto JavaScript de forma eficiente, cobrindo estrutura de pastas, módulos, configuração e convenções de código. Ao final, terá um guia prático para estruturar projetos escaláveis e de fácil manutenção.
Organizar um projeto JavaScript vai muito além de simplesmente colocar arquivos em pastas. Uma boa organização é essencial para a escalabilidade, manutenibilidade e colaboração em equipe. Nesta aula, vamos explorar as melhores práticas para estruturar um projeto, desde a divisão de pastas até a configuração de ferramentas e convenções de código.
Vamos começar entendendo a importância de uma estrutura bem definida e como ela impacta diretamente no desenvolvimento. Em seguida, mergulharemos em módulos, que são a base para escrever código limpo e reutilizável. Depois, veremos como configurar ferramentas como linters e formatadores, e finalizaremos com convenções que padronizam o estilo de código.
Estrutura de pastas
Uma estrutura de pastas bem planejada facilita a navegação no projeto, separa responsabilidades e ajuda a evitar dependências circulares. Não existe uma estrutura única que sirva para todos os projetos, mas algumas boas práticas são amplamente adotadas.
Uma abordagem comum é separar o código-fonte (em src), os testes (em test ou __tests__), os arquivos de configuração (na raiz) e os artefatos gerados (como dist ou build). Dentro de src, você pode organizar por funcionalidade ou por tipo de arquivo. A organização por funcionalidade (feature-based) é recomendada para projetos maiores, pois agrupa tudo relacionado a uma funcionalidade em um só lugar.
meu-projeto/
├── src/
│ ├── components/
│ ├── services/
│ ├── utils/
│ ├── pages/
│ └── index.js
├── test/
├── config/
├── public/
├── package.json
└── README.md
Exemplo de estrutura orientada a funcionalidades:
meu-projeto/
├── src/
│ ├── features/
│ │ ├── auth/
│ │ │ ├── AuthService.js
│ │ │ ├── Login.js
│ │ │ └── auth.test.js
│ │ ├── dashboard/
│ │ │ ├── Dashboard.js
│ │ │ └── Dashboard.css
│ │ └── ...
│ ├── shared/
│ │ ├── components/
│ │ └── hooks/
│ └── app.js
└── ...
Independentemente da estrutura escolhida, o importante é ser consistente e documentar a estrutura para a equipe.
Módulos
Módulos são a forma de dividir o código em partes reutilizáveis. No JavaScript moderno, usamos os módulos ES6 (import/export). Eles permitem encapsular funcionalidades, evitar poluição do escopo global e facilitar o teste.
Existem dois tipos principais de exportação: nomeada e padrão. A exportação nomeada permite exportar múltiplos valores, enquanto a padrão exporta um único valor por módulo. A escolha entre elas deve ser consistente no projeto.
// utils/math.js
export function soma(a, b) {
return a + b;
}
export function multiplica(a, b) {
return a * b;
}
// main.js
import { soma, multiplica } from './utils/math.js';
console.log(soma(2, 3)); // 5
Para organizar módulos, é comum criar um arquivo index.js em cada pasta para reexportar os módulos internos, facilitando o import de vários arquivos com um único caminho.
// components/index.js
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
Além disso, é importante evitar dependências circulares, onde dois módulos se importam mutuamente, pois isso pode causar erros difíceis de depurar. Uma boa prática é manter a hierarquia de dependências em uma direção (ex.: camadas de baixo não dependem de camadas de cima).
Configuração
A configuração do projeto envolve ferramentas como o package.json, transpiladores (Babel), empacotadores (Webpack, Vite), linters (ESLint) e formatadores (Prettier). Uma configuração bem definida garante consistência e automatiza tarefas.
O package.json é o coração do projeto: ele contém scripts, dependências e metadados. É essencial definir scripts para tarefas comuns, como dev, build, test e lint.
// package.json
{
"name": "meu-projeto",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {},
"devDependencies": {
"eslint": "^8.0.0",
"jest": "^29.0.0",
"vite": "^4.0.0"
}
}
Para o ESLint, você pode usar um arquivo de configuração .eslintrc.js ou eslint.config.js (dependendo da versão). É importante definir regras claras e integrar com o Prettier para evitar conflitos.
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['eslint:recommended', 'prettier'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
},
};
Além disso, o Prettier pode ser configurado com um arquivo .prettierrc para padronizar a formatação, como uso de aspas simples, ponto e vírgula, etc.
Convenções
Convenções são acordos sobre como escrever código: nomes de variáveis, formatação, estrutura de arquivos, etc. Seguir convenções torna o código mais legível e reduz a fricção entre membros da equipe.
Algumas convenções comuns em JavaScript:
- Use
camelCasepara variáveis e funções:minhaVariavel,minhaFuncao(). - Use
PascalCasepara classes e componentes:MinhaClasse,MeuComponente. - Use
UPPER_CASEpara constantes imutáveis:MAX_LIMIT. - Prefira
constpara valores que não mudam eletapenas quando necessário. - Use aspas simples ou duplas de forma consistente (o Prettier pode forçar uma delas).
- Adote o padrão de nomenclatura para arquivos:
kebab-caseoucamelCase(ex.:meu-componente.jsoumeuComponente.js). Escolha um e siga.
Além disso, é recomendável adotar um style guide, como o do Airbnb ou do Standard, para ter regras mais detalhadas. Ferramentas como o ESLint ajudam a aplicar essas regras automaticamente.
Também é importante definir convenções para commits, como o Conventional Commits, que padroniza as mensagens de commit e facilita a geração de changelogs.
// Exemplo de commit semântico
feat: adiciona página de login
fix: corrige bug no cálculo de frete
docs: atualiza README
Boas práticas e observações finais
Além dos tópicos acima, algumas boas práticas ajudam a manter o projeto organizado:
- Mantenha as dependências atualizadas e remova as não utilizadas.
- Use um gerenciador de pacotes com lockfile (como
package-lock.json) para garantir consistência entre ambientes. - Documente a estrutura e as decisões de arquitetura em um
README.mdou em um arquivodocs/. - Automatize tarefas com hooks de pre-commit (ex.: lint-staged) para rodar linters e testes antes de cada commit.
Organizar um projeto é um processo contínuo; revise e ajuste a estrutura conforme o projeto evolui.
Referências
- MDN: Módulos JavaScript
- Node.js: Documentação de pacotes
- ESLint: Documentação oficial
- Prettier: Documentação oficial
- Airbnb JavaScript Style Guide
- Conventional Commits
- Vite: Guia oficial
Exercícios
Crie uma estrutura de pastas para um projeto de aplicativo de tarefas (to-do list), incluindo pastas para componentes, serviços, utilitários e testes. Explique a função de cada pasta.
✓ Resposta: Uma possível estrutura:
A pastameu-app/ ├── src/ │ ├── components/ │ │ ├── TaskForm.js │ │ └── TaskList.js │ ├── services/ │ │ └── taskService.js │ ├── utils/ │ │ └── dateUtils.js │ ├── styles/ │ │ └── main.css │ └── index.js ├── test/ │ ├── components/ │ └── services/ ├── package.json └── README.mdcomponentsguarda os componentes de UI,servicescontém a lógica de negócio e comunicação com APIs,utilstem funções auxiliares,stylesCSS,testcontém os testes, e a raiz tem arquivos de configuração.Converta o seguinte código para usar módulos ES6, separando a função
saudacaoem um arquivo próprio e importando-a no arquivo principal. Mostre os arquivos resultantes.function saudacao(nome) { return 'Olá, ' + nome + '!'; } console.log(saudacao('Maria'));✓ Resposta: Arquivosaudacao.js:
Arquivo principalexport function saudacao(nome) { return `Olá, ${nome}!`; }main.js:import { saudacao } from './saudacao.js'; console.log(saudacao('Maria'));Escreva um arquivo
package.jsonpara um projeto que usa Vite, ESLint e Jest. Inclua scripts para desenvolvimento, build, teste e lint.✓ Resposta:{ "name": "meu-projeto", "version": "1.0.0", "scripts": { "dev": "vite", "build": "vite build", "test": "jest", "lint": "eslint ." }, "dependencies": {}, "devDependencies": { "vite": "^4.0.0", "eslint": "^8.0.0", "jest": "^29.0.0" } }Explique a diferença entre exportação nomeada e exportação padrão, e dê um exemplo de quando usar cada uma.
✓ Resposta: A exportação nomeada permite exportar múltiplos valores de um módulo, e o import deve usar os mesmos nomes. A exportação padrão exporta um único valor, e no import podemos escolher qualquer nome. Use exportação nomeada para utilitários ou múltiplas funções; use exportação padrão para uma classe ou função principal de um módulo (ex.: componente React).Escreva uma configuração básica do ESLint que estenda o estilo recomendado e desative a regra
no-console. Explique por que essa configuração é útil.✓ Resposta:
Essa configuração aplica as regras recomendadas do ESLint (que ajudam a evitar erros comuns) e desliga a regra que proíbe o uso de// .eslintrc.js module.exports = { env: { browser: true, es2021: true, }, extends: ['eslint:recommended'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, rules: { 'no-console': 'off', }, };console.log, permitindo logs durante o desenvolvimento.