Que es un Monorepo
Un monorepo es un patron de arquitectura que gestiona multiples proyectos o paquetes en un unico repositorio. Es adoptado por grandes empresas como Google, Meta y 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["Dependencias compartidas"]
UI --> Shared
Utils --> Shared
Web --> Shared
end
Ventajas y Desventajas del Monorepo
Ventajas
flowchart TB
subgraph Benefits["Ventajas del Monorepo"]
subgraph Share["1. Facilidad para compartir codigo"]
Pkg["packages/shared"] --> App["apps/web"]
Note1["Importacion inmediata<br/>Sin gestion de versiones"]
end
subgraph Atomic["2. Commits atomicos"]
Note2["Cambios en multiples paquetes en un solo commit<br/>Lanzamiento simultaneo de cambios breaking y adaptaciones"]
end
subgraph Unified["3. Toolchain unificado"]
Note3["Gestion centralizada de ESLint, TypeScript, configuracion de tests"]
end
subgraph Visibility["4. Visibilidad de dependencias"]
Note4["Clara comprension de las dependencias entre paquetes"]
end
end
Desventajas y Soluciones
| Desventaja | Solucion |
|---|---|
| Crecimiento del tamano del repositorio | Sparse checkout, shallow clone |
| Aumento del tiempo de CI | Builds diferenciales, ejecucion paralela, cache remoto |
| Complejidad en gestion de permisos | CODEOWNERS, permisos por directorio |
| Aumento de conflictos | Division apropiada de paquetes, responsabilidades claras |
Patrones de Estructura de Directorios
monorepo/
├── apps/ # Aplicaciones
│ ├── web/ # Frontend
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── api/ # Backend
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── mobile/ # App movil
│ └── ...
├── packages/ # Paquetes compartidos
│ ├── ui/ # Componentes UI
│ │ ├── src/
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ └── index.ts
│ │ └── package.json
│ ├── utils/ # Funciones utilitarias
│ │ └── ...
│ ├── config/ # Configuracion compartida
│ │ ├── eslint/
│ │ ├── typescript/
│ │ └── tailwind/
│ └── types/ # Definiciones de tipos compartidos
│ └── ...
├── tools/ # Herramientas de desarrollo y scripts
│ ├── scripts/
│ └── generators/
├── package.json # package.json raiz
├── pnpm-workspace.yaml # Configuracion de workspace
├── turbo.json # Configuracion de Turborepo
└── tsconfig.base.json # Configuracion base de TypeScript
Configuracion de 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"
}
Referencia a Paquetes 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()))}>
Clic
</Button>
</div>
);
}
Optimizacion de Builds con 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": []
}
}
}
Visualizacion de Dependencias de Tareas
flowchart TB
subgraph TaskGraph["Grafo de tareas 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: Construir primero los paquetes dependientes"]
Note2["Ejecucion paralela: Las tareas sin dependencias se ejecutan simultaneamente"]
Configuracion de Cache Remoto
# Vercel Remote Cache
npx turbo login
npx turbo link
# Cache autoalojado (ducktape)
# turbo.json
{
"remoteCache": {
"signature": true
}
}
sequenceDiagram
participant DevA as Desarrollador A<br/>(feature-1)
participant Cache as Cache Remoto
participant DevB as Desarrollador B<br/>(feature-2)
DevA->>Cache: build @repo/ui
Cache->>Cache: hash: abc123<br/>guardar artifacts
Note over DevA,DevB: Tiempo transcurrido
DevB->>Cache: build @repo/ui
Cache-->>DevB: Cache hit!<br/>Omitir build
Note over DevA,DevB: Builds con la misma entrada se completan instantaneamente (segundos)
Comparacion con Nx
| Aspecto | Turborepo | Nx |
|---|---|---|
| Curva de aprendizaje | Baja | Media-Alta |
| Complejidad de configuracion | Simple | Muchas funcionalidades |
| Generadores | No tiene | Completos |
| Plugins | Limitados | Abundantes |
| Cache | Integracion con Vercel | Nx Cloud |
| Analisis de dependencias | Basico | Detallado |
| Integracion con IDE | Basica | Extension VSCode disponible |
Criterios de seleccion:
- Prioridad en simplicidad → Turborepo
- Funcionalidades enterprise → Nx
- Uso de Vercel → Turborepo
- Uso de Angular → Nx
Paquetes de Configuracion Compartida
// 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"]
}
}
Estrategia 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 deteccion de diferencias
- 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]"
Sintaxis de Filtrado
# Solo construir paquetes con cambios
turbo run build --filter="...[HEAD^1]"
# Paquete especifico y sus dependencias
turbo run build --filter="@repo/web..."
# Paquete especifico y sus dependientes
turbo run build --filter="...@repo/ui"
# Solo bajo un directorio especifico
turbo run build --filter="./apps/*"
Mejores Practicas
- Clarificar responsabilidades de paquetes: Principio de una responsabilidad por paquete
- Evitar dependencias circulares: El grafo de dependencias siempre debe ser un DAG (Grafo Aciclico Dirigido)
- Estrategia de versionado: Gestion de versiones unificada con Changesets u otras herramientas
- Preparar documentacion: Colocar README en cada paquete
- Granularidad apropiada: Division de paquetes ni muy fina ni muy gruesa