Workspaces y Monorepos
Los workspaces de npm enlazan paquetes internos en un solo repositorio para que el código compartido se envíe sin necesidad de publicarlo en un registro por cada cambio.
Receta
Tarjeta de receta de referencia rápida: lista para copiar y pegar.
{
"name": "@acme/platform",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "npm run build -w @acme/shared && npm run build -w @acme/api"
}
}// apps/api/package.json
{
"name": "@acme/api",
"dependencies": {
"@acme/shared": "workspace:*"
}
}Cuándo usarlo:
- Múltiples servicios desplegables comparten tipos o utilidades de TypeScript.
- Quieres un solo archivo lockfile y un solo
npm cipara toda la organización. - Los paquetes internos cambian en el mismo PR que sus consumidores.
Ejemplo práctico
platform/
package.json # raíz de workspaces
package-lock.json
apps/
api/
package.json # @acme/api
src/server.ts
packages/
shared/
package.json # @acme/shared
src/index.ts
// packages/shared/package.json
{
"name": "@acme/shared",
"version": "0.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json"
}
}// apps/api/src/server.ts
import { createLogger } from "@acme/shared";
const log = createLogger("api");
log.info("listening");npm install # enlaza los workspaces
npm run build -w @acme/shared
npm run dev -w @acme/apiLo que esto demuestra:
workspace:*le dice a npm que cree un enlace simbólico al paquete local@acme/shared.-wapunta a un solo workspace sincd.- El paquete compartido se construye antes de que la API importe la salida compilada.
En detalle
Cómo funciona
- Los globs
workspacesde la raíz descubren los archivospackage.jsonbajoapps/*ypackages/*. npm installeleva las dependencias compartidas y crea enlaces simbólicos a los paquetes internos ennode_modulesdel consumidor.workspace:*se resuelve a la versión local en el momento de la instalación; la publicación lo reemplaza con el semver concreto.- Un
package-lock.jsonen la raíz captura todo el grafo.
Protocolo workspace:
| Especificador | Significado |
|---|---|
workspace:* | Cualquier versión local (la más común) |
workspace:^ | Coincide con la versión principal compatible local |
workspace:1.2.3 | Fija a la versión local exacta |
Notas de TypeScript
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "dist",
"rootDir": "src"
}
}- Habilita
compositepara referencias de proyectos entre paquetes. - Los consumidores deben importar desde nombres de paquetes (
@acme/shared), no rutas relativas apackages/shared/src.
Errores comunes
- Importar rutas de origen entre paquetes - Evita los límites del paquete y rompe la publicación. Solución: exporta a través de
main/exportsdepackage.json. - Olvidar construir librerías compartidas - La API se ejecuta contra
dist/obsoleto. Solución: ordena los scriptsbuildo usa dependencias de pipeline de Turborepo. - Dependencias fantasma - El hoisting te permite importar paquetes no declarados. Solución: considera pnpm o ESLint
import-x/no-extraneous-dependencies. - Paquetes internos con versión 0.0.0 - Está bien para monorepos privados; las librerías publicables necesitan un semver real antes de
npm publish. Solución: ejecutanpm versionen el paquete antes del lanzamiento externo. - Dependencias circulares de workspace -
@acme/adepende de@acme/by viceversa. Solución: extrae el kernel compartido a@acme/core.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| npm pack + file: | Prueba rápida sin workspaces | Mantenimiento de monorepo a largo plazo |
| Verdaccio privado / npm org | Los equipos necesitan lanzamientos internos versionados | Cada cambio es atómico en un solo PR |
| Turborepo / Nx encima | Builds cacheados en más de 5 paquetes | Repositorio de dos paquetes (exagerado) |
Preguntas frecuentes
¿Qué significa workspace:* en el momento de la publicación?
npm reemplaza workspace:* con la versión concreta del package.json del paquete del workspace cuando haces npm publish desde ese paquete.
¿Cómo ejecuto un script en un workspace?
npm run test -w @acme/api
npm run build --workspaces --if-present-w apunta a un paquete; --workspaces se ejecuta en todos.
¿Pueden los workspaces mezclar aplicaciones y librerías?
Sí. Diseño común: apps/* para desplegables, packages/* para librerías compartidas. Mantén los desplegables claramente separados en Docker/CI.
¿Necesito archivos lockfile separados por paquete?
No. Un solo archivo lockfile raíz es el predeterminado de los workspaces de npm y es preferible para una CI reproducible.
¿Cómo añado una dependencia a un workspace?
npm install zod -w @acme/apiEl archivo lockfile raíz se actualiza; la dependencia aterriza en el package.json de ese workspace.
¿Deben ser privados los paquetes internos?
Marca "private": true en los paquetes que nunca se publican externamente. Quítalo solo cuando publiques en npm con proveniencia.
¿Cómo encuentra TypeScript los tipos del workspace?
Archivos .d.ts construidos en dist/ más el campo types en package.json. Construye los paquetes compartidos antes de la verificación de tipos de los dependientes.
¿Puedo usar Express y Fastify en diferentes workspaces?
Sí. Cada workspace de aplicación posee su dependencia de framework; el código compartido permanece agnóstico al framework.
¿Qué pasa si dos workspaces necesitan diferentes versiones de la misma librería?
npm puede instalar múltiples versiones anidadas en el archivo lockfile. Prefiere alinear las versiones para reducir el tamaño del paquete y la superficie de auditoría.
¿Cuándo debo publicar paquetes internos en npm?
Cuando otro repositorio los consume o necesitas límites de semver entre equipos. Hasta entonces, workspace:* es suficiente.
Relacionado
- Conceptos básicos de los gestores de paquetes - pnpm vs npm para monorepos
- Turborepo para Node - builds de workspace cacheados
- Monorepo de múltiples servicios - límites de servicio
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.