O que é Monorepo e por que usar Turborepo?
Um monorepo é um repositório único que contém múltiplos projetos ou pacotes independentes. Diferente da abordagem tradicional de manter cada projeto em um repositório separado, o monorepo centraliza o código, facilita o compartilhamento de dependências e simplifica o versionamento. Turborepo é um orquestrador de build moderno que otimiza a execução de tarefas em monorepos através de cache inteligente e execução paralela eficiente.
A vantagem principal do Turborepo é a performance. Ele detecta quais pacotes foram alterados e executa apenas as tarefas necessárias, evitando rebuilds desnecessários. Isso reduz significativamente o tempo de desenvolvimento em projetos grandes. Além disso, funciona perfeitamente com TypeScript e ferramentas como npm, yarn e pnpm, oferecendo uma experiência fluida e sem fricção.
Estruturando um Monorepo com Turborepo e TypeScript
Setup Inicial
Para começar, crie uma estrutura básica de monorepo com Turborepo:
mkdir meu-monorepo && cd meu-monorepo
npm init -y
npm install -D turbo typescript
Crie o arquivo turbo.json na raiz para configurar as tarefas:
{
"$schema": "https://turborepo.com/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
O símbolo ^ indica dependência de outros pacotes — ou seja, antes de fazer build do pacote A, execute build de suas dependências.
Estrutura de Diretórios
Organize seus pacotes em um diretório comum:
meu-monorepo/
├── packages/
│ ├── core/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── ui/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── web/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── turbo.json
├── tsconfig.json
├── package.json
└── pnpm-workspace.yaml
Para usar pnpm (recomendado para monorepos), crie pnpm-workspace.yaml:
packages:
- 'packages/*'
Path Aliases: Simplificando Importações
Path aliases permitem importar módulos usando caminhos amigáveis em vez de caminhos relativos bagunçados. Configure-os no tsconfig.json raiz:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@core/*": ["packages/core/src/*"],
"@ui/*": ["packages/ui/src/*"],
"@shared/*": ["packages/shared/src/*"]
}
}
}
Agora, em qualquer pacote, você pode fazer:
// Ao invés de: import { Button } from '../../../ui/src/Button'
import { Button } from '@ui/components';
import { formatDate } from '@shared/utils';
Para que funcione em runtime no Node.js, instale e configure tsconfig-paths:
npm install tsconfig-paths
No seu arquivo de entrada (exemplo packages/web/src/index.ts):
import 'tsconfig-paths/register';
import { Button } from '@ui/components';
console.log('App iniciado', Button);
Criando e Reutilizando Shared Packages
Exemplo Prático: Um Pacote Compartilhado
Crie um pacote shared para funcionalidades comuns:
mkdir packages/shared
cd packages/shared
npm init -y
packages/shared/package.json:
{
"name": "@monorepo/shared",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js",
"./types": "./dist/types.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
packages/shared/src/types.ts:
export interface User {
id: string;
name: string;
email: string;
}
export interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
}
packages/shared/src/utils.ts:
import { User } from './types';
export const isValidEmail = (email: string): boolean => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
export const formatUserName = (user: User): string => {
return user.name.toUpperCase();
};
packages/shared/src/index.ts:
export * from './types';
export * from './utils';
Consumindo o Shared Package
Em outro pacote (exemplo web), adicione @monorepo/shared como dependência no packages/web/package.json:
{
"name": "web",
"dependencies": {
"@monorepo/shared": "workspace:*"
}
}
A flag workspace:* indica ao pnpm que use a versão local.
Agora use em packages/web/src/app.ts:
import { User, ApiResponse, isValidEmail, formatUserName } from '@monorepo/shared';
const user: User = {
id: '1',
name: 'João Silva',
email: 'joao@example.com'
};
if (isValidEmail(user.email)) {
console.log('Usuário válido:', formatUserName(user));
}
Executando Tarefas no Monorepo
Com Turborepo configurado, execute:
# Build de todos os pacotes respeitando dependências
turbo run build
# Executar testes apenas em pacotes alterados
turbo run test --filter=[HEAD]
# Watch em desenvolvimento
turbo run dev
# Ver grafo de dependências
turbo run build --graph
Para filtrar pacotes específicos:
turbo run build --filter @monorepo/shared
turbo run test --filter web
Conclusão
Dominar monorepos com Turborepo, paths aliases e shared packages transforma sua produtividade em projetos escaláveis. Os três pilares aprendidos aqui são: (1) Turborepo otimiza builds através de cache e execução paralela inteligente; (2) path aliases eliminam importações relativas confusas, melhorando legibilidade; (3) shared packages centralizam lógica comum, reduzindo duplicação e mantendo consistência entre projetos.
Comece pequeno com dois ou três pacotes, configure o turbo.json corretamente e expanda conforme necessário. A maioria dos problemas em monorepos vem de má configuração inicial — invista tempo nisso.