Configuración Híbrida Monorepo/Polyrepo para Micro Frontends con React + TypeScript
Este documento explora el diseño e implementación de una arquitectura híbrida monorepo/polyrepo para aplicaciones de micro frontends construidas con React y TypeScript. Abarca las decisiones de herramientas como Turborepo, pnpm workspaces y Module Federation, junto con patrones para la gestión de dependencias compartidas, pipelines de CI/CD y estrategias de coordinación entre repositorios.
Investigación Integral de Herramientas y Patrones
1. Comparacion de Herramientas de Monorepo para Paquetes Core
Contexto de la Arquitectura
Tu monorepo contiene: design system, utilidades compartidas, tipos compartidos y configuraciones comunes. Este es un monorepo "enfocado en librerias" (no un "monorepo de aplicaciones"), lo que afecta significativamente la eleccion de herramientas.
Turborepo
Que es: Un sistema de build de alto rendimiento para monorepos JavaScript/TypeScript de Vercel. Escrito en Rust (migrado desde Go a partir de finales de 2023).
Fortalezas para tu caso de uso:
- Configuración minima: se agrega a un monorepo existente en menos de 10 minutos
- Lee la configuración de workspaces de tu gestor de paquetes (pnpm, npm o yarn) e infiere el grafo de dependencias a partir de los archivos
package.json - Remote caching via Vercel (o autoalojado) -- un cache hit toma ~0.2s vs ~30s para un build en frio
- Configuración componible (introducida en Turborepo 2.7, diciembre 2025) permite fragmentos de configuración reutilizables
- Ligero: no impone opiniones sobre la estructura del proyecto
Limitaciones:
- Es puramente un task runner -- sin generacion de codigo, sin visualizacion del grafo de dependencias, sin aplicacion de restricciones del proyecto
- Sin flujo de trabajo integrado para publicacion de librerias
- Menos adecuado para aplicar restricciones arquitectonicas
Ejemplo de turbo.json para tu configuración:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"check-types": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["^build"]
}
}
}
Nx
Que es: Un sistema de build integral y "Plataforma de Inteligencia de Build" de Nrwl. Evoluciono significativamente en 2025 con funcionalidades de CI/CD impulsadas por IA.
Fortalezas para tu caso de uso:
- Visualizacion integrada del grafo de dependencias (
nx graph) - Comando
affectedpotente: solo compila/testea lo que cambio - Generadores de codigo integrados para librerias React, componentes
- Aplica limites de modulo y restricciones arquitectonicas mediante reglas de lint
- Soporte nativo de Module Federation con
@nx/react/module-federation - Soporte de primera clase para librerias publicables
- Hasta 7x mas rapido que Turborepo en monorepos grandes (dependiente del benchmark)
Limitaciones:
- Curva de aprendizaje mas pronunciada: Nx quiere gestionar tu workspace a su manera
- Mas pesado: mas configuración, mas conceptos (executors, generators, targets)
- La migración de un proyecto existente es mas compleja que con Turborepo
- Puede sentirse excesivo para un monorepo solo de librerias con 4 paquetes
Ejemplo de nx.json:
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"],
"cache": true
},
"lint": {
"cache": true
},
"test": {
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": ["default", "!{projectRoot}/**/*.spec.ts"]
}
}
pnpm Workspaces Solo
Que es: La funcionalidad de workspace integrada de pnpm para gestionar multiples paquetes en un unico repositorio.
Fortalezas para tu caso de uso:
- Cero herramientas adicionales mas alla de pnpm en si
- Protocolo
workspace:*para dependencias locales -- la mejor ergonomia de desarrollo local en su clase - Resolucion estricta de dependencias que previene dependencias fantasma
- Almacenamiento direccionable por contenido que ahorra espacio de disco significativo
Limitaciones:
- Sin orquestación de tareas (los builds se ejecutan secuencialmente o tu mismo escribes scripts de paralelismo)
- Sin cache (cada build es un build en frio)
- Sin deteccion de cambios afectados
- Sin visualizacion del grafo de dependencias
pnpm-workspace.yaml:
packages:
- "packages/*"
Recomendacion para Tu Configuración
Usa Turborepo + pnpm workspaces. Esta es la justificacion:
- Tu monorepo esta enfocado en librerias (design system + librerias compartidas), no es un monorepo de aplicaciones. El enfoque ligero de Turborepo es ideal.
- Turborepo agrega cache y orquestación de tareas sobre pnpm workspaces sin imponer opiniones estructurales.
- El protocolo
workspace:*de pnpm proporciona la mejor experiencia de desarrollo local para referencias entre paquetes. - El remote caching de Turborepo acelera dramaticamente el CI.
- Nx seria la eleccion correcta si tuvieras los micro frontends dentro del monorepo (su integracion con Module Federation es excelente), pero como tus MFEs estan en repositorios separados, las ventajas de Nx se reducen para el monorepo de paquetes core.
Estructura del monorepo:
core-packages/
├── packages/
│ ├── design-system/ # Libreria de componentes React
│ │ ├── src/
│ │ ├── package.json # @myorg/design-system
│ │ └── tsconfig.json
│ ├── shared-utils/ # Funciones utilitarias
│ │ ├── src/
│ │ ├── package.json # @myorg/shared-utils
│ │ └── tsconfig.json
│ ├── shared-types/ # Definiciones de tipos TypeScript
│ │ ├── src/
│ │ ├── package.json # @myorg/shared-types
│ │ └── tsconfig.json
│ └── config/ # Configuraciónes compartidas (ESLint, TypeScript, Prettier)
│ ├── eslint/
│ ├── typescript/
│ └── prettier/
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.json # tsconfig raiz con project references
Root package.json:
{
"name": "@myorg/core-packages",
"private": true,
"packageManager": "pnpm@10.30.3",
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"check-types": "turbo run check-types",
"test": "turbo run test",
"changeset": "changeset",
"version-packages": "changeset version",
"publish-packages": "turbo run build lint test && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.27.0",
"turbo": "^2.8.12"
}
}
2. Desarrollo Local Cross-Repo
Este es el desafio mas critico en tu configuración híbrida: como trabajan los desarrolladores en un micro frontend mientras iteran simultaneamente sobre el design system o las utilidades compartidas?
Enfoque 1: pnpm link / npm link
Como funciona:
pnpm link <ruta-al-paquete-local>crea un symlink desde losnode_modulesdel proyecto consumidor al directorio del paquete local- Los cambios en el paquete vinculado son visibles inmediatamente (no se requiere rebuild del enlace, pero si necesitas recompilar el paquete en si)
Configuración:
# En un repositorio de micro frontend:
pnpm link /path/to/core-packages/packages/design-system
pnpm link /path/to/core-packages/packages/shared-utils
Limitaciones:
- Los symlinks pueden causar problemas de "paquete dual": el paquete vinculado resuelve sus propios
node_modulesdesde su ubicacion original, lo que lleva a instancias duplicadas de React y el infame error "Invalid Hook Call" - La estructura estricta de
node_modulesde pnpm hace que vincular entre diferentes workspaces de pnpm sea particularmente complicado - No simula un
npm publishreal -- puedes pasar por alto problemas de empaquetado - Falla cuando el proyecto consumidor usa un gestor de paquetes diferente al del fuente
Veredicto: No recomendado como enfoque principal para desarrollo cross-repo con React debido al problema de dependencias duplicadas.
Enfoque 2: yalc
Como funciona:
yalc publishen el proyecto de la libreria copia los archivos del paquete compilado a un almacen global (~/.yalc)yalc add @myorg/design-systemen el proyecto consumidor copia los archivos desde el almacen global al proyecto y agrega una dependenciafile:.yalc/@myorg/design-systemalpackage.jsonyalc pushpublica Y actualiza todos los proyectos consumidores en un solo paso
Configuración:
# Instalar globalmente
npm install -g yalc
# En el paquete del design system (despues de compilar):
cd core-packages/packages/design-system
pnpm build
yalc publish
# En un repositorio de micro frontend:
cd mfe-checkout
yalc add @myorg/design-system
yalc add @myorg/shared-utils
# Cuando hagas cambios en el design system:
cd core-packages/packages/design-system
pnpm build && yalc push # Actualiza automaticamente todos los consumidores
Pros:
- Simula un npm publish real -- detecta problemas de empaquetado tempranamente
- Sin problemas de dependencias duplicadas (los archivos se copian, no se enlazan con symlinks)
- Funciona con diferentes gestores de paquetes
yalc push --watchpuede automatizar el push ante cambios
Contras:
- Requiere compilar el paquete antes de publicar en yalc (no es instantaneo)
- Agrega el directorio
.yalcy modificapackage.json/ lockfile (debe incluirse en gitignore) - No se mantiene tan activamente como otras herramientas (la ultima actualizacion significativa varia)
- La compatibilidad con pnpm ha tenido problemas historicamente (aunque la mayoria estan resueltos)
Adiciones necesarias al .gitignore:
.yalc
yalc.lock
Veredicto: Buen punto medio. Ideal para iteracion rapida cross-repo cuando necesitas resolucion realista de paquetes.
Enfoque 3: Verdaccio (Registro npm Local)
Como funciona:
- Ejecuta un registro npm local completo en
http://localhost:4873 - Publicas paquetes en el exactamente como lo harias en npmjs.com
- Los proyectos consumidores instalan desde el usando una configuración
.npmrccon scope - Redirige al registro npm publico para los paquetes que no se encuentren localmente
Configuración:
# Instalar e iniciar
npm install -g verdaccio
verdaccio # Inicia en http://localhost:4873
# Crear un usuario (una sola vez)
npm adduser --registry http://localhost:4873
Archivo .npmrc del proyecto (en cada repositorio de micro frontend):
@myorg:registry=http://localhost:4873
Flujo de publicacion:
# En el paquete del design system:
cd core-packages/packages/design-system
pnpm build
npm publish --registry http://localhost:4873
# En el micro frontend:
pnpm install @myorg/design-system@latest
Configuración Docker para uso en equipo:
# docker-compose.yml
services:
verdaccio:
image: verdaccio/verdaccio
ports:
- "4873:4873"
volumes:
- verdaccio-storage:/verdaccio/storage
- ./verdaccio-config.yaml:/verdaccio/conf/config.yaml
volumes:
verdaccio-storage:
Pros:
- La simulacion mas realista del flujo de trabajo de produccion
- Funciona con cualquier gestor de paquetes
- Actua como proxy con cache para npm publico (acelera las instalaciones)
- Los equipos pueden compartir una unica instancia de Verdaccio
- Se puede usar en CI para pruebas de integracion
Contras:
- Requiere ejecutar un servicio separado
- Mas ceremonial por ciclo de publicacion (se requiere incremento de version cada vez)
- Ciclo de iteracion mas lento comparado con symlinks o yalc
Veredicto: Ideal para entornos de equipo y pruebas de integracion en CI. Excesivo para la iteracion individual del desarrollador.
Enfoque 4: pnpm overrides / npm overrides
Como funciona:
- Sobreescribe cualquier resolucion de dependencias para apuntar a una ruta de archivo local
- En pnpm, agrega
overridesalpackage.jsonraiz o alpnpm-workspace.yaml
Configuración (package.json en el micro frontend):
{
"pnpm": {
"overrides": {
"@myorg/design-system": "file:../core-packages/packages/design-system",
"@myorg/shared-utils": "file:../core-packages/packages/shared-utils"
}
}
}
Advertencia importante: Esto incorpora rutas absolutas en pnpm-lock.yaml, lo que causa problemas:
- Los archivos de lock difieren entre las maquinas de los desarrolladores
- No se puede hacer commit de forma confiable al control de versiones
- Existe una solicitud de funcionalidad abierta (mayo 2025) para "overrides solo locales" en pnpm, pero aun no esta implementada
Solucion alternativa -- usar un script de overrides solo local:
#!/bin/bash
# scripts/link-local.sh -- NO HAGAS COMMIT del package.json modificado
CORE_PATH="${CORE_PACKAGES_PATH:-../core-packages}"
# Agregar overrides temporalmente
node -e "
const pkg = require('./package.json');
pkg.pnpm = pkg.pnpm || {};
pkg.pnpm.overrides = {
'@myorg/design-system': 'file:${CORE_PATH}/packages/design-system',
'@myorg/shared-utils': 'file:${CORE_PATH}/packages/shared-utils',
'@myorg/shared-types': 'file:${CORE_PATH}/packages/shared-types'
};
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
"
pnpm install
echo "Local overrides applied. Run 'git checkout package.json pnpm-lock.yaml' to revert."
Veredicto: Util como hack rapido pero no adecuado como flujo de trabajo a nivel de equipo debido a la contaminacion del archivo de lock.
Enfoque 5: Module Federation para Desarrollo Local
Como funciona:
- El design system se expone como un remote de Module Federation
- Los micro frontends lo consumen en runtime, no en build time
- Para desarrollo local, apuntas la URL del remote a
localhost
Configuración en el design system (remote):
// packages/design-system/rsbuild.config.ts (o webpack.config.js)
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
export default {
plugins: [
new ModuleFederationPlugin({
name: 'design_system',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Input': './src/components/Input',
'./Card': './src/components/Card',
'./theme': './src/theme',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
Configuración en un micro frontend (host/consumidor):
// mfe-checkout/rsbuild.config.ts
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
export default {
plugins: [
new ModuleFederationPlugin({
name: 'mfe_checkout',
remotes: {
design_system: process.env.NODE_ENV === 'development'
? 'design_system@http://localhost:3001/remoteEntry.js'
: 'design_system@https://cdn.myorg.com/design-system/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
Uso en el codigo del micro frontend:
// Importacion dinamica desde el modulo federado
const Button = React.lazy(() => import('design_system/Button'));
// O con wrapper tipado (recomendado):
import type { ButtonProps } from '@myorg/design-system'; // tipos desde npm
const Button = React.lazy(() => import('design_system/Button'));
Veredicto: Este es el enfoque mas potente para tu arquitectura. Elimina por completo la necesidad de linking/publicacion durante el desarrollo. Consulta la Seccion 3 para el analisis completo de Module Federation vs distribucion por paquetes npm.
Flujo de Trabajo Combinado Recomendado
Usa un enfoque por capas:
| Escenario | Herramienta | Por que |
|---|---|---|
| Desarrollo diario (design system + MFE) | Module Federation (dev server) | Retroalimentacion instantanea, sin paso de build/link |
| Probar empaquetado/exports | yalc | Detecta problemas reales de empaquetado |
| Pruebas de integracion a nivel de equipo | Verdaccio (Docker) | Simulacion realista de registro |
| Pruebas de integracion en CI | Verdaccio o GitHub Packages | Flujo completo tipo produccion |
| Produccion | GitHub Packages (npm) + Module Federation (runtime) | Ver Seccion 3 |
3. Distribucion del Paquete de Design System
Estructura del Paquete
packages/design-system/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.styles.ts # o Button.module.css
│ │ │ ├── Button.test.tsx
│ │ │ └── index.ts
│ │ ├── Input/
│ │ ├── Card/
│ │ └── ...
│ ├── hooks/
│ │ ├── useTheme.ts
│ │ └── index.ts
│ ├── theme/
│ │ ├── tokens.ts
│ │ ├── ThemeProvider.tsx
│ │ └── index.ts
│ ├── utils/
│ │ └── index.ts
│ └── index.ts # Export barrel principal
├── tsup.config.ts
├── package.json
├── tsconfig.json
└── CHANGELOG.md
Configuración de Build con tsup
tsup (construido sobre esbuild) es la herramienta de build recomendada para librerias de componentes React debido a su velocidad, simplicidad y soporte de tree-shaking.
tsup.config.ts:
import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
// Puntos de entrada granulares para mejor tree-shaking:
'components/Button': 'src/components/Button/index.ts',
'components/Input': 'src/components/Input/index.ts',
'components/Card': 'src/components/Card/index.ts',
'hooks/index': 'src/hooks/index.ts',
'theme/index': 'src/theme/index.ts',
},
format: ['esm', 'cjs'],
dts: true,
splitting: true,
clean: true,
treeshake: true,
sourcemap: true,
external: ['react', 'react-dom'], // Dependencias peer
minify: false, // Dejar que los consumidores manejen la minificacion
target: 'es2020',
});
package.json para Distribucion
{
"name": "@myorg/design-system",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./components/*": {
"import": {
"types": "./dist/components/*/index.d.ts",
"default": "./dist/components/*/index.js"
},
"require": {
"types": "./dist/components/*/index.d.cts",
"default": "./dist/components/*/index.cjs"
}
},
"./hooks": {
"import": {
"types": "./dist/hooks/index.d.ts",
"default": "./dist/hooks/index.js"
}
},
"./theme": {
"import": {
"types": "./dist/theme/index.d.ts",
"default": "./dist/theme/index.js"
}
}
},
"sideEffects": false,
"files": ["dist"],
"peerDependencies": {
"react": "^18.2.0 || ^19.0.0",
"react-dom": "^18.2.0 || ^19.0.0"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"access": "restricted"
},
"repository": {
"type": "git",
"url": "https://github.com/myorg/core-packages.git",
"directory": "packages/design-system"
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src/",
"check-types": "tsc --noEmit",
"test": "vitest run"
}
}
Consideraciones de Tree-Shaking
"sideEffects": false-- Esto le dice a los bundlers que cualquier export no utilizado puede eliminarse de forma segura. Si tienes importaciones de CSS o estilos globales, listalos explicitamente:
{
"sideEffects": ["**/*.css", "**/*.scss"]
}
-
La salida ESM es obligatoria -- El tree-shaking solo funciona de forma confiable con ES modules. El campo
"type": "module"y"module"aseguran que los bundlers usen la version ESM. -
Multiples puntos de entrada -- El mapa de
exportscon rutas granulares permite a los consumidores importar solo lo que necesitan:
// Importa solo el codigo de Button, no todo el design system:
import { Button } from '@myorg/design-system/components/Button';
-
Peer dependencies externas -- React y ReactDOM deben ser
externalen la configuración de tsup para evitar incluirlas en el bundle de la libreria. -
Evitar re-exports de barrel file de modulos completos -- Cada componente debe tener su propio punto de entrada. Un unico
index.tsque re-exporta todo anula el tree-shaking en algunos bundlers.
Estrategia de Versionado
Recomendacion: Usa Changesets con versionado independiente.
.changeset/config.json:
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
Flujo de trabajo:
# El desarrollador hace cambios, luego:
pnpm changeset
# Prompt interactivo: seleccionar paquetes, tipo de incremento, escribir resumen
# CI o el responsable de releases ejecuta:
pnpm changeset version # Actualiza versiones + changelogs
pnpm changeset publish # Publica en el registro
# O combinar con Turborepo:
pnpm publish-packages # build + lint + test + publish
Por que versionado independiente (no fijo):
@myorg/shared-typescambia con mucha mas frecuencia que@myorg/design-system- El versionado fijo fuerza a TODOS los paquetes a incrementar version en cada cambio, causando actualizaciones innecesarias en los repositorios consumidores
- El versionado independiente permite a los micro frontends actualizar solo los paquetes de los que realmente dependen
Publicacion en un Registro npm Privado
Recomendacion: GitHub Packages.
Por que GitHub Packages sobre las alternativas:
- Integracion nativa con GitHub Actions (autenticación via
GITHUB_TOKEN) - El control de acceso replica los permisos del repositorio
- Con scope a tu organizacion de GitHub
- Sin servicio adicional que gestionar
- Gratis para paquetes privados dentro de los planes de GitHub
Configuración:
.npmrcraiz en el monorepo:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
- El
package.jsonde cada paquete debe incluir:
{
"name": "@myorg/design-system",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"repository": {
"type": "git",
"url": "https://github.com/myorg/core-packages.git"
}
}
- En el
.npmrcde cada micro frontend:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
Alternativa con Cloudflare: Cloudflare no ofrece un servicio nativo de registro npm. Si quieres una alternativa autoalojada a GitHub Packages, Verdaccio en Cloudflare Workers o un deploy edge similar podria funcionar, pero requiere significativamente mas esfuerzo con poco beneficio respecto a GitHub Packages.
Paquete npm vs Module Federation: Cuando Usar Cada Uno
Estrategia de distribucion dual (recomendada):
| Aspecto | Paquete npm | Module Federation Remote |
|---|---|---|
| Cuando se consume | Build time | Runtime |
| Tipos TypeScript | Seguridad de tipos completa via .d.ts | Requiere paquete de tipos separado o @module-federation/typescript |
| Versionado | Explicito (semver en package.json) | Implicito (lo que este desplegado) |
| Builds offline/CI | Funciona sin red | Requiere que el remote este disponible |
| Tree-shaking | Excelente (con ESM) | Limitado (carga los modulos expuestos) |
| Deploy independiente | No (requiere rebuild) | Si (actualizar el remote, los consumidores obtienen los cambios) |
| Ideal para | Tipos compartidos, utilidades, tokens de tema | Componentes UI, modulos de funcionalidades |
Enfoque recomendado:
- Publica
@myorg/shared-typesy@myorg/shared-utilscomo paquetes npm unicamente (se consumen en build time y hacen tree-shake bien) - Publica
@myorg/design-systemcomo paquete npm Y remote de Module Federation:- Paquete npm: para tipos TypeScript, autocompletado del IDE y como fallback
- Remote de Module Federation: para consumo en runtime con deploy independiente
- Usa los paquetes
@myorg/configcomo paquetes npm unicamente (devDependencies)
Configuración compartida de Module Federation para el design system:
// Configuracion de federation del design system
{
name: 'design_system',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Input': './src/components/Input',
'./Card': './src/components/Card',
'./ThemeProvider': './src/theme/ThemeProvider',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
eager: false, // NO pongas eager:true -- infla el entry
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
eager: false,
},
// Compartir los tokens de tema del design system como singleton:
'@myorg/shared-utils': {
singleton: true,
requiredVersion: '^1.0.0',
},
},
}
4. TypeScript Project References
Como Funcionan las TypeScript Project References
Las project references de TypeScript permiten al compilador entender tu monorepo como una serie de proyectos conectados ("islas") en lugar de una unidad monolitica. Cada paquete se compila de forma independiente, con declaraciones explicitas de dependencias.
Opciones clave del compilador requeridas:
| Opcion | Proposito |
|---|---|
composite: true | Habilita el modo de project references; implica declaration e incremental |
declaration: true | Genera archivos .d.ts (requerido para resolucion de tipos entre proyectos) |
declarationMap: true | Genera sourcemaps para las declaraciones (habilita "Go to Definition" entre paquetes) |
incremental: true | Almacena en cache los resultados de compilacion en tsconfig.tsbuildinfo |
Configuración para el Monorepo
Configuración base compartida -- packages/config/typescript/base.json:
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true
}
}
Configuración compartida para libreria React -- packages/config/typescript/react-library.json:
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"composite": true
}
}
tsconfig.json raiz (project references):
{
"files": [],
"references": [
{ "path": "packages/shared-types" },
{ "path": "packages/shared-utils" },
{ "path": "packages/design-system" }
]
}
packages/shared-types/tsconfig.json:
{
"extends": "../config/typescript/base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
packages/shared-utils/tsconfig.json:
{
"extends": "../config/typescript/base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{ "path": "../shared-types" }
]
}
packages/design-system/tsconfig.json:
{
"extends": "../config/typescript/react-library.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{ "path": "../shared-types" },
{ "path": "../shared-utils" }
]
}
Compilacion con Project References
# Compilar todos los proyectos en orden de dependencias:
tsc --build
# Compilar un proyecto especifico y sus dependencias:
tsc --build packages/design-system
# Limpiar artefactos de compilacion:
tsc --build --clean
En turbo.json, la tarea check-types ya maneja esto mediante la configuración dependsOn: ["^build"], asegurando que los paquetes se compilen antes de que sus dependientes se verifiquen en cuanto a tipos.
tsconfig paths para Desarrollo Local (dentro del monorepo)
Los alias de rutas en el tsconfig.json raiz habilitan funcionalidades del IDE (autocompletado, go-to-definition) sin requerir un paso de build:
{
"compilerOptions": {
"paths": {
"@myorg/shared-types": ["./packages/shared-types/src"],
"@myorg/shared-types/*": ["./packages/shared-types/src/*"],
"@myorg/shared-utils": ["./packages/shared-utils/src"],
"@myorg/shared-utils/*": ["./packages/shared-utils/src/*"],
"@myorg/design-system": ["./packages/design-system/src"],
"@myorg/design-system/*": ["./packages/design-system/src/*"]
}
}
}
Importante: Estas rutas son solo para desarrollo. La resolucion real de modulos para los consumidores usa el campo exports en package.json. El protocolo workspace:* de pnpm resuelve la dependencia, y el mapa de exports apunta a la salida compilada.
Seguridad de Tipos para Repos Externos (Micro Frontends)
Cuando los micro frontends consumen los paquetes desde el registro npm, la seguridad de tipos proviene de los archivos .d.ts publicados junto al JavaScript:
- tsup genera archivos
.d.tsdesde el fuente TypeScript - Los campos
"types"y"exports"enpackage.jsonapuntan a estas declaraciones - Los consumidores obtienen verificación completa de tipos, autocompletado y go-to-definition (via
declarationMap)
Para tipos de Module Federation, usa @module-federation/typescript:
pnpm add -D @module-federation/typescript
Este plugin genera declaraciones de tipos para modulos federados y los pone a disposicion de los hosts consumidores. Alternativamente, mantiene un paquete @myorg/design-system-types que exporta solo las interfaces y tipos TypeScript, consumido como dependencia npm incluso cuando los componentes en si vienen via Module Federation.
Un enfoque pragmatico es que los micro frontends dependan de @myorg/design-system como devDependency (solo para tipos) mientras consumen componentes en runtime via Module Federation:
{
"devDependencies": {
"@myorg/design-system": "^2.0.0"
}
}
// Los tipos vienen del paquete npm (build time):
import type { ButtonProps } from '@myorg/design-system';
// Los componentes vienen de Module Federation (runtime):
const Button = React.lazy(() => import('design_system/Button'));
5. Patrones de CI/CD
CI/CD del Monorepo (Paquetes Core)
Flujo de trabajo de GitHub Actions para el monorepo:
.github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- run: pnpm lint
- run: pnpm check-types
- run: pnpm test
.github/workflows/release.yml:
name: Release
on:
push:
branches: [main]
permissions:
contents: write
packages: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
registry-url: 'https://npm.pkg.github.com'
scope: '@myorg'
- run: pnpm install --frozen-lockfile
- run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
publish: pnpm publish-packages
version: pnpm changeset version
title: 'chore: version packages'
commit: 'chore: version packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Como opera este flujo de trabajo:
- En cada push a
main, la accion de Changesets verifica si hay changesets pendientes. - Si hay changesets, crea un PR "Version Packages" que incrementa versiones y actualiza changelogs.
- Cuando ese PR se fusiona, la accion detecta los incrementos de version y ejecuta
pnpm publish-packages, que compila, testea y publica en GitHub Packages.
Deploy del Remote de Module Federation del Design System
Si sirves el design system como un remote de Module Federation, agrega un paso de deploy:
- name: Deploy Module Federation Remote
if: steps.changesets.outputs.published == 'true'
run: |
# Compilar el bundle del MF remote
cd packages/design-system
pnpm build:federation
# Deploy a CDN (ejemplo: Cloudflare R2 o AWS S3)
aws s3 sync dist/federation/ s3://my-cdn-bucket/design-system/ \
--cache-control "public, max-age=31536000, immutable"
# Invalidar cache del CDN
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
--paths "/design-system/remoteEntry.js"
Repos de Micro Frontend: Consumiendo Nuevas Versiones
Actualizaciones automatizadas de dependencias con Renovate (recomendado sobre Dependabot):
Por que Renovate:
- Soporta mas de 90 gestores de paquetes (vs los 30+ de Dependabot)
- Agrupa actualizaciones de paquetes del monorepo en un unico PR (preset
group:monorepos) - Presets de configuración compartida estandarizan las politicas de dependencias en todos los repos
- Automerge integrado con reglas configurables
- Gestores regex para actualizar versiones en cualquier formato de archivo
- Funciona con GitHub, GitLab, Bitbucket, Azure DevOps
Configuración compartida de Renovate (en un repo separado o gist):
renovate-config/default.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"group:monorepos",
":automergeMinor",
":automergeDigest"
],
"packageRules": [
{
"description": "Auto-merge patch updates from our design system",
"matchPackagePatterns": ["@myorg/*"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"description": "Group all @myorg packages together",
"matchPackagePatterns": ["@myorg/*"],
"groupName": "myorg core packages"
},
{
"description": "Require manual review for major updates",
"matchPackagePatterns": ["@myorg/*"],
"matchUpdateTypes": ["major"],
"automerge": false,
"reviewers": ["team:frontend-platform"]
}
],
"registryAliases": {
"npm": "https://npm.pkg.github.com"
},
"npmrc": "@myorg:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${RENOVATE_TOKEN}"
}
En cada repositorio de micro frontend, renovate.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>myorg/renovate-config"]
}
Alternativa: actualizaciones disparadas por webhook de GitHub Actions.
Cuando el monorepo publica nuevas versiones, dispara actualizaciones en los repositorios de micro frontend:
.github/workflows/notify-consumers.yml (en el monorepo):
name: Notify Consumers
on:
workflow_run:
workflows: [Release]
types: [completed]
jobs:
notify:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
strategy:
matrix:
repo:
- myorg/mfe-checkout
- myorg/mfe-catalog
- myorg/mfe-account
- myorg/mfe-search
- myorg/mfe-admin
steps:
- name: Trigger dependency update
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.CROSS_REPO_PAT }}
repository: ${{ matrix.repo }}
event-type: core-packages-updated
client-payload: '{"ref": "${{ github.ref }}"}'
En cada repositorio de micro frontend, .github/workflows/update-deps.yml:
name: Update Core Packages
on:
repository_dispatch:
types: [core-packages-updated]
permissions:
contents: write
pull-requests: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://npm.pkg.github.com'
scope: '@myorg'
- run: pnpm update "@myorg/*" --latest
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update @myorg core packages'
title: 'chore: update @myorg core packages'
body: |
Actualizacion automatizada disparada por una nueva release de los paquetes core.
Por favor verifica:
- [ ] Los componentes del design system se renderizan correctamente
- [ ] Sin errores de TypeScript
- [ ] Los tests pasan
branch: chore/update-core-packages
delete-branch: true
Pipeline de CI del Micro Frontend
.github/workflows/ci.yml (en cada repo de MFE):
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
registry-url: 'https://npm.pkg.github.com'
scope: '@myorg'
- run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: pnpm lint
- run: pnpm check-types
- run: pnpm test
- run: pnpm build
deploy:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... deploy del micro frontend a CDN/hosting
6. Gestor de Paquetes Recomendado
Matriz Comparativa
| Funcionalidad | pnpm | yarn (v4 Berry) | npm (v10) |
|---|---|---|---|
| Eficiencia de disco | Almacen direccionable por contenido (mejor) | PnP o node_modules | node_modules estandar |
| Velocidad de instalacion | La mas rapida | Rapida con PnP | La mas lenta |
| Dependencias estrictas | Aplicadas por defecto | Opcional (PnP) | No aplicadas |
| Protocolo workspace | workspace:* (mejor) | workspace:* | Soportado (basico) |
| Vinculacion cross-repo | pnpm link (estricto) | yarn link | npm link |
| Compatibilidad con Module Federation | Excelente | PnP tiene problemas | Buena |
| Integracion con Turborepo | De primera clase | De primera clase | De primera clase |
| Soporte monorepo | Workspaces nativos | Workspaces nativos | Workspaces nativos |
| overrides / resolutions | pnpm.overrides | resolutions | overrides |
Recomendacion: pnpm
pnpm es la eleccion clara para tu configuración. Esta es la razon:
-
Almacen direccionable por contenido: Con 5-10 repositorios de micro frontend + el monorepo, el almacen global de pnpm previene descargas duplicadas. Una unica copia de
react@18.2.0se comparte entre todos los proyectos. -
Resolucion estricta de dependencias: Previene dependencias fantasma (importar paquetes no declarados en
package.json), lo cual es critico cuando tus paquetes seran consumidos por otros repos que pueden tener arboles de dependencias diferentes. -
Protocolo
workspace:*: La mejor experiencia de desarrollo local dentro del monorepo. Cuando publicas,workspace:*se reemplaza automaticamente con el numero de version real. -
Compatibilidad con Module Federation: La estructura de node_modules de pnpm (symlinks al almacen
.pnpm) funciona bien con la resolucion de modulos de webpack y rspack. A diferencia de Yarn PnP, no hay problemas de compatibilidad conocidos con Module Federation. -
Integracion con Turborepo: Turborepo lee la configuración de workspaces de pnpm de forma nativa y entiende el grafo de dependencias.
-
Vinculacion cross-repo: Aunque
pnpm linktiene sus particularidades, la combinacion con yalc o servidores de desarrollo de Module Federation cubre todos los escenarios cross-repo. -
Rendimiento a escala: Con 5-10 repositorios de micro frontend, la ventaja de velocidad de instalacion de pnpm se acumula significativamente.
Fijacion de version de pnpm en el monorepo:
{
"packageManager": "pnpm@10.30.3",
"engines": {
"node": ">=22.0.0",
"pnpm": ">=9.0.0"
}
}
Usa corepack para asegurar versiones consistentes de pnpm:
corepack enable
corepack prepare pnpm@10.30.3 --activate
7. Arquitectura General Recomendada
Resumen de Elecciones de Herramientas
| Aspecto | Herramienta | Alternativas Consideradas |
|---|---|---|
| Gestor de paquetes | pnpm | yarn, npm |
| Orquestación del monorepo | Turborepo | Nx, pnpm solo |
| Build de paquetes | tsup | Rollup, Vite library mode |
| Versionado/publicacion | Changesets | Lerna, manual |
| Registro privado | GitHub Packages | Verdaccio, npm Enterprise |
| Desarrollo local cross-repo | Module Federation dev + yalc | pnpm link, Verdaccio |
| Module Federation runtime | @module-federation/enhanced (Rspack/webpack) | Vite plugin |
| Actualizaciones automaticas de deps | Renovate | Dependabot |
| Configuración TypeScript | Project references + configuraciones base compartidas | solo paths |
| CI/CD | GitHub Actions + Turborepo remote cache | CircleCI, Jenkins |
Flujo de Trabajo del Desarrollador (Dia a Dia)
1. Clonar ambos repos:
git clone myorg/core-packages
git clone myorg/mfe-checkout
2. Iniciar el dev server del design system (remote de Module Federation):
cd core-packages && pnpm dev
# El design system sirve remoteEntry.js en localhost:3001
3. Iniciar el micro frontend (host de Module Federation):
cd mfe-checkout && pnpm dev
# El MFE consume el design system desde localhost:3001
4. Hacer cambios en el design system:
# Editar packages/design-system/src/components/Button/Button.tsx
# El hot reload se propaga a traves de Module Federation -- no se necesita linking
5. Cuando estes listo para compartir con el equipo:
pnpm changeset # Declarar el cambio
git commit && git push # PR y revision
# Al fusionar: CI compila, versiona y publica automaticamente
# Renovate crea PRs de actualizacion en todos los repos de micro frontend
Arquitectura de Produccion
Principios Clave
-
Tipos en build time, componentes en runtime: Usa paquetes npm para tipos TypeScript y utilidades. Usa Module Federation para componentes UI que se benefician del deploy independiente.
-
Automatizar todo entre repos: Changesets para versionado, Renovate para propagación de dependencias, GitHub Actions para CI/CD. Minimizar la coordinación manual.
-
Limites estrictos: La resolucion estricta de pnpm + TypeScript project references + limites claros entre paquetes previenen el caos de dependencias.
-
Cache agresivo: Turborepo remote cache para el monorepo, almacen direccionable por contenido de pnpm para instalaciones, cache del CDN para remotes de Module Federation.
-
Modos de desarrollo duales: Module Federation para desarrollo diario cross-repo (retroalimentacion instantanea), yalc para probar el comportamiento real de empaquetado antes de las releases.
Fuentes
- Nx vs. Turborepo: Integrated Ecosystem or High-Speed Task Runner?
- Turborepo, Nx, and Lerna: The Truth about Monorepo Tooling in 2026
- Why I Chose Turborepo Over Nx
- How we configured pnpm and Turborepo for our monorepo (Nhost)
- Setting Up a Scalable Monorepo With Turborepo and PNPM
- Structuring a repository (Turborepo docs)
- Turborepo TypeScript Guide
- Different approaches to testing packages locally: yalc
- Different approaches to testing packages locally: Verdaccio
- yalc GitHub repository
- Verdaccio - lightweight private npm registry
- pnpm vs verdaccio vs yalc comparison
- How to bundle a tree-shakable TypeScript library with tsup
- Creating a tree-shakable library with tsup
- Tree Shaking in React: How to write a tree-shakable component library
- How to create a lean, tree-shakeable React Design System library
- Module Federation Shared Configuration
- Solving micro-frontend challenges with Module Federation
- Micro Frontends with Module Federation in Monorepo
- Module Federation - Rspack
- Everything You Need to Know About TypeScript Project References (Nx)
- Managing TypeScript Packages in Monorepos (Nx)
- Live types in a TypeScript monorepo
- TypeScript project references (moonrepo)
- TypeScript Documentation: Project References
- Working with the npm registry - GitHub Packages
- Publishing Node.js packages - GitHub Actions
- Changesets GitHub repository
- Complete Monorepo Guide: pnpm + Workspace + Changesets (2025)
- Renovate vs Dependabot comparison
- Renovate Bot GitHub
- From Monorepo to Polyrepo: Scaling Micro-Frontends Across Teams
- Creating separate monorepo CI/CD pipelines with GitHub Actions
- GitHub Actions in 2026: Complete Guide to Monorepo CI/CD
- pnpm overrides for dependency management
- pnpm local-only overrides discussion
- Choosing the Right JavaScript Package Manager in 2025