Turborepo con TypeScript: monorepo, paquetes compartidos tipados y pipeline de build

Turborepo es un sistema de build para monorepos que entiende el grafo de dependencias entre paquetes y los compila en el orden correcto, paralelizando todo lo que puede y cacheando los resultados. Combinado con pnpm workspaces y TypeScript, es la solución más usada para gestionar monorepos con paquetes compartidos tipados.

Estructura del monorepo

// Estructura de carpetas:
// mi-monorepo/
//   apps/
//     web/          (Next.js)
//     api/          (Node.js/Express)
//   packages/
//     ui/           (@mi-org/ui)
//     utils/        (@mi-org/utils)
//     tsconfig/     (@mi-org/tsconfig)
//   package.json    (raíz)
//   pnpm-workspace.yaml
//   turbo.json

// pnpm-workspace.yaml
// packages:
//   - "apps/*"
//   - "packages/*"

tsconfig base reutilizable

// packages/tsconfig/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "exclude": ["node_modules"]
}

// packages/tsconfig/nextjs.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "./base.json",
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "ES2022"],
    "jsx": "preserve",
    "allowJs": true
  }
}

// Uso en apps/web/tsconfig.json:
{
  "extends": "@mi-org/tsconfig/nextjs.json",
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Paquete compartido tipado (@mi-org/utils)

// packages/utils/package.json
{
  "name": "@mi-org/utils",
  "version": "0.0.1",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  }
}

// packages/utils/src/index.ts
export function formatearFecha(fecha: Date, locale: string = 'es-ES'): string {
  return new Intl.DateTimeFormat(locale, {
    day: '2-digit', month: '2-digit', year: 'numeric',
  }).format(fecha);
}

export function agrupar<T, K extends string | number>(
  items: T[],
  fn: (item: T) => K
): Record<K, T[]> {
  return items.reduce((acc, item) => {
    const clave = fn(item);
    acc[clave] = acc[clave] ?? [];
    acc[clave].push(item);
    return acc;
  }, {} as Record<K, T[]>);
}

Pipeline de builds con dependencias

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // ^ = dependencias del paquete primero
      "outputs": ["dist/**", ".next/**"],
      "cache": true
    },
    "dev": {
      "dependsOn": ["^build"],
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": []
    }
  }
}

Usar el paquete compartido en una app

// apps/web/package.json
{
  "dependencies": {
    "@mi-org/utils": "workspace:*",
    "@mi-org/ui": "workspace:*"
  }
}

// apps/web/src/app/page.tsx
import { formatearFecha, agrupar } from '@mi-org/utils';
// TypeScript encuentra los tipos desde packages/utils/dist/index.d.ts
// (o desde el source si se configura paths)

const fecha = formatearFecha(new Date()); // string

Remote caching en Vercel

Turborepo integra un sistema de caché remota. Conectarlo a Vercel Remote Cache permite compartir los resultados de build entre máquinas CI y desarrolladores:

// Conectar con Vercel:
// npx turbo login
// npx turbo link

// Con el token en CI:
// TURBO_TOKEN=... TURBO_TEAM=... npx turbo build

Imagen: Pexels / Nemuel Sereti

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP