O que é Monorepo
Monorepo é um padrão de arquitetura que gerencia múltiplos projetos ou pacotes em um único repositório. É adotado por grandes empresas como Google, Meta e Microsoft.
flowchart TB
subgraph Polyrepo["Polyrepo"]
RA["repo-a<br/>pkg.json"] --> NPMA["npm"]
RB["repo-b<br/>pkg.json"] --> NPMB["npm"]
RC["repo-c<br/>pkg.json"] --> NPMC["npm"]
RD["repo-d<br/>pkg.json"] --> NPMD["npm"]
end
subgraph Monorepo["Monorepo - single-repo"]
UI["packages/<br/>ui"]
Utils["packages/<br/>utils"]
Web["apps/<br/>web"]
Shared["Dependências compartilhadas"]
UI --> Shared
Utils --> Shared
Web --> Shared
end
Vantagens e Desvantagens do Monorepo
Vantagens
flowchart TB
subgraph Benefits["Vantagens do Monorepo"]
subgraph Share["1. Facilidade de Compartilhamento de Código"]
Pkg["packages/shared"] --> App["apps/web"]
Note1["Importação imediata<br/>Sem necessidade de gerenciamento de versão"]
end
subgraph Atomic["2. Commits Atômicos"]
Note2["Mudanças em múltiplos pacotes em um único commit<br/>Lançamento simultâneo de breaking changes e adaptações"]
end
subgraph Unified["3. Toolchain Unificado"]
Note3["Gerenciamento centralizado de ESLint, TypeScript, configurações de teste"]
end
subgraph Visibility["4. Visualização de Dependências"]
Note4["Dependências entre pacotes claramente visíveis"]
end
end
Desvantagens e Soluções
| Desvantagem | Solução |
|---|---|
| Aumento do tamanho do repositório | Sparse checkout, shallow clone |
| Aumento do tempo de execução do CI | Build incremental, execução paralela, cache remoto |
| Complexidade no gerenciamento de permissões | CODEOWNERS, configuração de permissões por diretório |
| Aumento de conflitos | Divisão adequada de pacotes, responsabilidades claras |
Padrões de Estrutura de Diretórios
monorepo/
├── apps/ # Aplicações
│ ├── web/ # Frontend
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── api/ # Backend
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── mobile/ # Aplicativo móvel
│ └── ...
├── packages/ # Pacotes compartilhados
│ ├── ui/ # Componentes UI
│ │ ├── src/
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ └── index.ts
│ │ └── package.json
│ ├── utils/ # Funções utilitárias
│ │ └── ...
│ ├── config/ # Configurações compartilhadas
│ │ ├── eslint/
│ │ ├── typescript/
│ │ └── tailwind/
│ └── types/ # Definições de tipos compartilhadas
│ └── ...
├── tools/ # Ferramentas de desenvolvimento/scripts
│ ├── scripts/
│ └── generators/
├── package.json # package.json raiz
├── pnpm-workspace.yaml # Configuração do workspace
├── turbo.json # Configuração do Turborepo
└── tsconfig.base.json # Configuração base do TypeScript
Configuração do pnpm Workspaces
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "tools/*"
// package.json raiz
{
"name": "monorepo",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.4.0"
},
"packageManager": "pnpm@9.0.0"
}
Referência a Pacotes Internos
// apps/web/package.json
{
"name": "@monorepo/web",
"dependencies": {
"@monorepo/ui": "workspace:*",
"@monorepo/utils": "workspace:*",
"react": "^19.0.0"
}
}
// apps/web/src/App.tsx
import { Button, Modal } from '@monorepo/ui';
import { formatDate, debounce } from '@monorepo/utils';
export function App() {
return (
<div>
<Button onClick={() => console.log(formatDate(new Date()))}>
Clique
</Button>
</div>
);
}
Otimização de Build com Turborepo
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"dependsOn": ["^build"],
"outputs": []
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
Visualização de Dependências de Tarefas
flowchart TB
subgraph TaskGraph["Grafo de Tarefas Turborepo - turbo run build"]
Types["@repo/types<br/>build"] --> Utils["@repo/utils<br/>build"]
Utils --> UI["@repo/ui<br/>build"]
Utils --> API["@repo/api<br/>build"]
Utils --> Web["@repo/web<br/>build"]
end
Note1["^build: Build das dependências primeiro"]
Note2["Execução paralela: Tarefas sem dependências executam simultaneamente"]
Configuração de Cache Remoto
# Vercel Remote Cache
npx turbo login
npx turbo link
# Cache auto-hospedado (ducktape)
# turbo.json
{
"remoteCache": {
"signature": true
}
}
sequenceDiagram
participant DevA as Desenvolvedor A<br/>(feature-1)
participant Cache as Remote Cache
participant DevB as Desenvolvedor B<br/>(feature-2)
DevA->>Cache: build @repo/ui
Cache->>Cache: hash: abc123<br/>salvar artifacts
Note over DevA,DevB: Tempo passa
DevB->>Cache: build @repo/ui
Cache-->>DevB: Cache hit!<br/>Build ignorado
Note over DevA,DevB: Build com mesma entrada completa instantaneamente (segundos)
Comparação com Nx
| Aspecto | Turborepo | Nx |
|---|---|---|
| Curva de aprendizado | Baixa | Média~Alta |
| Complexidade de configuração | Simples | Rico em recursos |
| Generators | Não possui | Abundantes |
| Plugins | Limitados | Abundantes |
| Cache | Integração Vercel | Nx Cloud |
| Análise de dependências | Básica | Detalhada |
| Integração com IDE | Básica | Extensão VSCode disponível |
Critérios de escolha:
- Prioridade na simplicidade → Turborepo
- Recursos enterprise → Nx
- Uso do Vercel → Turborepo
- Uso do Angular → Nx
Pacotes de Configuração Compartilhada
// packages/config/eslint/index.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
},
};
// packages/config/eslint/react.js
module.exports = {
extends: [
'./index.js',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
settings: {
react: { version: 'detect' },
},
};
// packages/config/typescript/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true
}
}
// packages/config/typescript/react.json
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2022"]
}
}
Estratégia de CI/CD
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Para detecção de diferenças
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo run build --filter="...[HEAD^1]"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Test
run: pnpm turbo run test --filter="...[HEAD^1]"
- name: Lint
run: pnpm turbo run lint --filter="...[HEAD^1]"
Sintaxe de Filtragem
# Build apenas dos pacotes alterados
turbo run build --filter="...[HEAD^1]"
# Pacote específico e suas dependências
turbo run build --filter="@repo/web..."
# Pacote específico e seus dependentes
turbo run build --filter="...@repo/ui"
# Apenas sob diretório específico
turbo run build --filter="./apps/*"
Melhores Práticas
- Responsabilidade clara dos pacotes: Princípio de uma responsabilidade por pacote
- Evitar dependências circulares: O grafo de dependências deve sempre ser um DAG (Grafo Acíclico Direcionado)
- Estratégia de versionamento: Gerenciamento de versão unificado com Changesets, etc.
- Manutenção da documentação: README em cada pacote
- Granularidade adequada: Divisão de pacotes nem muito fina, nem muito grande