Patrones de Diseno Monorepo - Desarrollo escalable con Turborepo, pnpm y Nx

Avanzado | 2025.12.02

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

DesventajaSolucion
Crecimiento del tamano del repositorioSparse checkout, shallow clone
Aumento del tiempo de CIBuilds diferenciales, ejecucion paralela, cache remoto
Complejidad en gestion de permisosCODEOWNERS, permisos por directorio
Aumento de conflictosDivision 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

AspectoTurborepoNx
Curva de aprendizajeBajaMedia-Alta
Complejidad de configuracionSimpleMuchas funcionalidades
GeneradoresNo tieneCompletos
PluginsLimitadosAbundantes
CacheIntegracion con VercelNx Cloud
Analisis de dependenciasBasicoDetallado
Integracion con IDEBasicaExtension 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

  1. Clarificar responsabilidades de paquetes: Principio de una responsabilidad por paquete
  2. Evitar dependencias circulares: El grafo de dependencias siempre debe ser un DAG (Grafo Aciclico Dirigido)
  3. Estrategia de versionado: Gestion de versiones unificada con Changesets u otras herramientas
  4. Preparar documentacion: Colocar README en cada paquete
  5. Granularidad apropiada: Division de paquetes ni muy fina ni muy gruesa

Enlaces de Referencia

← Volver a la lista