Monorepositório com JavaScript: organização de código na prática
No desenvolvimento web moderno com JavaScript (Node.js), surge cada vez mais a questão: como organizar o código de forma eficiente quando você tem vários pacotes, bibliotecas ou microsserviços que dependem uns dos outros? A abordagem tradicional com repositórios separados (multirepo) pode levar à duplicação de código, dificuldades com sincronização de versões e CI/CD lento. A solução é o monorepositório (monorepo). Neste artigo, vamos analisar o que é um monorepositório, por que ele é necessário e como organizá-lo na prática usando JavaScript.
O que é um monorepositório e por que ele é necessário?
Monorepositório é uma abordagem onde o código de vários projetos (pacotes, bibliotecas, serviços) é armazenado em um único repositório. Não se trata apenas de "colocar tudo em uma pasta", mas de uma arquitetura bem pensada que resolve muitos problemas:
- Versão única de dependências: você evita situações onde diferentes pacotes usam versões diferentes da mesma biblioteca.
- Refatoração facilitada: alterações em uma biblioteca compartilhada são imediatamente visíveis em todos os projetos que a utilizam.
- CI/CD simplificado: um repositório — uma configuração de build e teste.
- Compartilhamento de código: utilitários, tipos e configurações ficam disponíveis para todos os pacotes sem necessidade de publicação no npm.
No entanto, o monorepositório não é adequado para todos. Ele exige disciplina da equipe e boas ferramentas. Para JavaScript (Node.js), as principais ferramentas são npm workspaces (integrado ao npm 7+) e Lerna (uma ferramenta mais avançada).
Configuração de um monorepositório básico com npm workspaces
Vamos começar com um exemplo simples. Suponha que temos um projeto composto por dois pacotes: @myapp/core (núcleo com lógica de negócios) e @myapp/server (servidor em Express).
Vamos criar a estrutura de pastas:
my-monorepo/├── packages/│ ├── core/│ │ ├── src/│ │ │ └── index.js│ │ ├── package.json│ │ └── ...│ └── server/│ ├── src/│ │ └── index.js│ ├── package.json│ └── ...├── package.json└── ...O package.json raiz deve conter o campo workspaces:
{ "name": "my-monorepo", "private": true, "workspaces": [ "packages/*" ], "scripts": { "start": "node packages/server/src/index.js", "test": "npm run test --workspaces" }}Agora, no packages/core/package.json:
{ "name": "@myapp/core", "version": "1.0.0", "main": "src/index.js"}E no packages/server/package.json, adicionamos a dependência do core:
{ "name": "@myapp/server", "version": "1.0.0", "dependencies": { "@myapp/core": "*", "express": "^4.18.0" }}Após executar npm install na raiz, o npm criará symlinks entre os pacotes. Agora, no código do servidor, podemos importar o core diretamente:
// packages/server/src/index.jsconst express = require('express');const { calculate } = require('@myapp/core');
const app = express();app.get('/', (req, res) => { res.send(calculate(2, 3));});app.listen(3000);Este é um exemplo básico, mas já mostra a principal vantagem: alterações no @myapp/core são imediatamente visíveis no @myapp/server sem necessidade de publicação em um registro.
Organização avançada com Lerna
Quando o número de pacotes ultrapassa 5-10, o npm workspaces pode não ser suficiente. É aí que entra o Lerna — uma ferramenta que adiciona recursos poderosos:
- Execução de comandos em todos ou em pacotes selecionados.
- Gerenciamento automático de versões (modo fixed ou independent).
- Pu