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

SVG-HM-01: Visión General de Estrategia de Repo Híbrido -- Monorepo con Polyrepos radiales Monorepo Central design-system | shared-utils | types Turborepo + pnpm workspaces MFE Checkout polyrepo MFE Catalog polyrepo MFE Account polyrepo MFE Search polyrepo npm publish npm publish Renovate Renovate

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

SVG-HM-02: Pipeline de Tareas de Turborepo (DAG) -- lint, typecheck, build, test con cache Pipeline de Tareas de Turborepo (DAG) lint eslint typecheck tsc --noEmit build tsup / rspack test vitest Turbo Remote Cache -- cache hit ~0.2 s vs cold ~30 s

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 affected potente: 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:

  1. Tu monorepo esta enfocado en librerias (design system + librerias compartidas), no es un monorepo de aplicaciones. El enfoque ligero de Turborepo es ideal.
  2. Turborepo agrega cache y orquestación de tareas sobre pnpm workspaces sin imponer opiniones estructurales.
  3. El protocolo workspace:* de pnpm proporciona la mejor experiencia de desarrollo local para referencias entre paquetes.
  4. El remote caching de Turborepo acelera dramaticamente el CI.
  5. 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?

Como funciona:

  • pnpm link <ruta-al-paquete-local> crea un symlink desde los node_modules del 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_modules desde su ubicacion original, lo que lleva a instancias duplicadas de React y el infame error "Invalid Hook Call"
  • La estructura estricta de node_modules de pnpm hace que vincular entre diferentes workspaces de pnpm sea particularmente complicado
  • No simula un npm publish real -- 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

SVG-HM-03: Flujo de Desarrollo Cross-Repo -- ciclo de yalc publish, add, test, push yalc publish design-system/ yalc add mfe-checkout/ Testing Local pnpm dev yalc push actualizar todos iterar y repetir ~/.yalc store

Como funciona:

  • yalc publish en el proyecto de la libreria copia los archivos del paquete compilado a un almacen global (~/.yalc)
  • yalc add @myorg/design-system en el proyecto consumidor copia los archivos desde el almacen global al proyecto y agrega una dependencia file:.yalc/@myorg/design-system al package.json
  • yalc push publica 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 --watch puede automatizar el push ante cambios

Contras:

  • Requiere compilar el paquete antes de publicar en yalc (no es instantaneo)
  • Agrega el directorio .yalc y modifica package.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)

SVG-HM-05: Flujo de Registro Local Verdaccio -- publicar, registro local, consumir 1. npm publish core-packages/ Verdaccio localhost:4873 registro npm local 3. pnpm install mfe-checkout/ fallthrough a npm publico

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 .npmrc con 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 overrides al package.json raiz o al pnpm-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:

EscenarioHerramientaPor que
Desarrollo diario (design system + MFE)Module Federation (dev server)Retroalimentacion instantanea, sin paso de build/link
Probar empaquetado/exportsyalcDetecta problemas reales de empaquetado
Pruebas de integracion a nivel de equipoVerdaccio (Docker)Simulacion realista de registro
Pruebas de integracion en CIVerdaccio o GitHub PackagesFlujo completo tipo produccion
ProduccionGitHub 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

  1. "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"]
}
  1. 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.

  2. Multiples puntos de entrada -- El mapa de exports con 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';
  1. Peer dependencies externas -- React y ReactDOM deben ser external en la configuración de tsup para evitar incluirlas en el bundle de la libreria.

  2. Evitar re-exports de barrel file de modulos completos -- Cada componente debe tener su propio punto de entrada. Un unico index.ts que 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-types cambia 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:

  1. .npmrc raiz en el monorepo:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
  1. El package.json de 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"
  }
}
  1. En el .npmrc de 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):

SVG-HM-04: Distribucion Dual del Design System -- paquete npm (build-time) y MF remote (runtime) Design System: Distribucion Dual @myorg/design-system fuente de verdad Paquete npm (Build-Time) Tipos TypeScript | tree-shaking | semver shared-types, shared-utils, tipos DS MF Remote (Runtime) Componentes UI | deploy independiente Button, Input, Card, ThemeProvider tsup build rspack bundle
AspectoPaquete npmModule Federation Remote
Cuando se consumeBuild timeRuntime
Tipos TypeScriptSeguridad de tipos completa via .d.tsRequiere paquete de tipos separado o @module-federation/typescript
VersionadoExplicito (semver en package.json)Implicito (lo que este desplegado)
Builds offline/CIFunciona sin redRequiere que el remote este disponible
Tree-shakingExcelente (con ESM)Limitado (carga los modulos expuestos)
Deploy independienteNo (requiere rebuild)Si (actualizar el remote, los consumidores obtienen los cambios)
Ideal paraTipos compartidos, utilidades, tokens de temaComponentes UI, modulos de funcionalidades

Enfoque recomendado:

  • Publica @myorg/shared-types y @myorg/shared-utils como paquetes npm unicamente (se consumen en build time y hacen tree-shake bien)
  • Publica @myorg/design-system como 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/config como 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:

OpcionProposito
composite: trueHabilita el modo de project references; implica declaration e incremental
declaration: trueGenera archivos .d.ts (requerido para resolucion de tipos entre proyectos)
declarationMap: trueGenera sourcemaps para las declaraciones (habilita "Go to Definition" entre paquetes)
incremental: trueAlmacena 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:

  1. tsup genera archivos .d.ts desde el fuente TypeScript
  2. Los campos "types" y "exports" en package.json apuntan a estas declaraciones
  3. 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:

  1. En cada push a main, la accion de Changesets verifica si hay changesets pendientes.
  2. Si hay changesets, crea un PR "Version Packages" que incrementa versiones y actualiza changelogs.
  3. 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

Funcionalidadpnpmyarn (v4 Berry)npm (v10)
Eficiencia de discoAlmacen direccionable por contenido (mejor)PnP o node_modulesnode_modules estandar
Velocidad de instalacionLa mas rapidaRapida con PnPLa mas lenta
Dependencias estrictasAplicadas por defectoOpcional (PnP)No aplicadas
Protocolo workspaceworkspace:* (mejor)workspace:*Soportado (basico)
Vinculacion cross-repopnpm link (estricto)yarn linknpm link
Compatibilidad con Module FederationExcelentePnP tiene problemasBuena
Integracion con TurborepoDe primera claseDe primera claseDe primera clase
Soporte monorepoWorkspaces nativosWorkspaces nativosWorkspaces nativos
overrides / resolutionspnpm.overridesresolutionsoverrides

Recomendacion: pnpm

pnpm es la eleccion clara para tu configuración. Esta es la razon:

  1. 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.0 se comparte entre todos los proyectos.

  2. 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.

  3. Protocolo workspace:*: La mejor experiencia de desarrollo local dentro del monorepo. Cuando publicas, workspace:* se reemplaza automaticamente con el numero de version real.

  4. 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.

  5. Integracion con Turborepo: Turborepo lee la configuración de workspaces de pnpm de forma nativa y entiende el grafo de dependencias.

  6. Vinculacion cross-repo: Aunque pnpm link tiene sus particularidades, la combinacion con yalc o servidores de desarrollo de Module Federation cubre todos los escenarios cross-repo.

  7. 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

AspectoHerramientaAlternativas Consideradas
Gestor de paquetespnpmyarn, npm
Orquestación del monorepoTurborepoNx, pnpm solo
Build de paquetestsupRollup, Vite library mode
Versionado/publicacionChangesetsLerna, manual
Registro privadoGitHub PackagesVerdaccio, npm Enterprise
Desarrollo local cross-repoModule Federation dev + yalcpnpm link, Verdaccio
Module Federation runtime@module-federation/enhanced (Rspack/webpack)Vite plugin
Actualizaciones automaticas de depsRenovateDependabot
Configuración TypeScriptProject references + configuraciones base compartidassolo paths
CI/CDGitHub Actions + Turborepo remote cacheCircleCI, 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

SVG-HM-06: Arquitectura de Produccion -- CDN, Workers, R2, KV sirviendo micro frontends CDN Edge (Cloudflare) Workers routing + SSR R2 Storage MF remotes + estaticos KV Store config + feature flags GitHub Packages npm (build-time) MFE Host shell app MFE Checkout remote MFE Catalog remote MFE Account remote MFE ...N remote runtime (MF remote) build-time (npm pkg)

Principios Clave

  1. 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.

  2. Automatizar todo entre repos: Changesets para versionado, Renovate para propagación de dependencias, GitHub Actions para CI/CD. Minimizar la coordinación manual.

  3. Limites estrictos: La resolucion estricta de pnpm + TypeScript project references + limites claros entre paquetes previenen el caos de dependencias.

  4. Cache agresivo: Turborepo remote cache para el monorepo, almacen direccionable por contenido de pnpm para instalaciones, cache del CDN para remotes de Module Federation.

  5. 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