Desarrollo Local
Tabla de Contenidos
- Resumen
- Flujo de trabajo diario
- Desarrollo cross-repo con yalc
- Verdaccio para pruebas de integracion en equipo
- Ejecutar Workers en local
- Variables de entorno y secretos
- Scripts de desarrollo recomendados
- Resolucion de problemas frecuentes
- Referencias
Resumen
La experiencia de desarrollo es el factor mas importante para la velocidad a largo plazo de una plataforma de micro frontends. Con 3-5 equipos trabajando en un monorepo (shell, design system, librerias compartidas) y multiples polyrepos (MFEs individuales), el desarrollo local tiene que ser rapido, fiable y requerir la minima ceremonia para arrancar.
Objetivos
- Ciclos de feedback rapidos — HMR por debajo del segundo para cambios dentro de un MFE, segundos para cambios de integracion entre MFEs.
- Pruebas de integracion realistas — la capacidad de ejecutar el stack completo en local, incluyendo Workers, KV, D1 y Durable Objects, para que lo que funciona en local funcione en staging.
- Friccion minima en el setup — un nuevo desarrollador deberia pasar de
git clonea un entorno de desarrollo funcionando en menos de cinco minutos.
Requisitos previos
| Herramienta | Version requerida |
|---|---|
| pnpm | 10.30.3 |
| Wrangler | v4 |
| Node.js | >= 20 LTS |
Tres modos de desarrollo local
| Modo | Caso de uso | Herramientas |
|---|---|---|
| Desarrollo MFE en solitario | Trabajo diario en features dentro de un solo MFE | Rsbuild dev server, Module Federation, mock shell context |
| Desarrollo cross-repo con yalc | Probar cambios en el design system o librerias compartidas en un MFE consumidor antes de publicar | yalc publish/push |
| Integracion en equipo con Verdaccio | Probar el ciclo completo de publicacion y consumo entre equipos antes de lanzar a GitHub Packages | Verdaccio local registry |
Flujo de trabajo diario
Desarrollo de MFE en solitario
El modo de desarrollo mas habitual. El desarrollador trabaja en un unico MFE de forma aislada, con HMR rapido y un mock shell context que simula la aplicacion host.
Ejecutar pnpm dev arranca el Rsbuild dev server con Module Federation configurado. El MFE expone su remote entry y puede ser cargado por el shell, pero tambien se renderiza en solitario con un mock shell context provider que proporciona routing, estado de autenticacion y tokens de tema.
Configuracion tipica de rsbuild.config.ts para desarrollo:
// packages/mfe-dashboard/rsbuild.config.ts
import { defineConfig } from '@rsbuild/core'; // ^1.7.3
import { pluginReact } from '@rsbuild/plugin-react'; // ^1.4.1
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
export default defineConfig({
plugins: [pluginReact()],
server: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
dev: {
hmr: true,
liveReload: true,
},
pluginModuleFederation({
name: 'mfe_dashboard',
filename: 'remoteEntry.js',
exposes: {
'./DashboardApp': './src/bootstrap.tsx',
'./DashboardWidgets': './src/widgets/index.tsx',
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
'react-router': { singleton: true, requiredVersion: '^7.13.1' },
'@org/design-system': { singleton: true },
},
}),
});
Nota:
react-router-domse ha consolidado dentro dereact-routera partir de la v7. Usareact-routertanto para la configuracion de shared como para los imports.
Mock shell context para renderizado en solitario:
// packages/mfe-dashboard/src/dev-shell.tsx
import React from 'react';
import { BrowserRouter } from 'react-router';
import { ThemeProvider } from '@org/design-system';
import { ShellContext } from '@org/shell-contracts';
import { DashboardApp } from './bootstrap';
const mockShellContext = {
user: {
id: 'dev-user-001',
email: 'developer@example.com',
organizationId: 'org-dev-001',
roles: ['admin'],
},
auth: {
accessToken: 'dev-token-xxx',
isAuthenticated: true,
logout: () => console.log('[dev] logout called'),
},
navigation: {
basePath: '/dashboard',
navigate: (path: string) => console.log(`[dev] navigate to ${path}`),
},
featureFlags: {
'dashboard-v2': true,
'analytics-export': true,
},
};
export function DevShell() {
return (
<BrowserRouter>
<ThemeProvider defaultTheme="light">
<ShellContext.Provider value={mockShellContext}>
<DashboardApp />
</ShellContext.Provider>
</ThemeProvider>
</BrowserRouter>
);
}
Como se consume el design system durante el desarrollo en solitario:
- Opcion A (recomendada por velocidad): El design system se consume como un Module Federation remote desde el CDN de staging. Esto evita ejecutar el design system dev server en local y garantiza alineamiento con la ultima version publicada.
- Opcion B (cuando se modifica el design system): El design system dev server se ejecuta en
localhost:3002, y la configuracion de Module Federation del MFE apunta a el. Esto habilita HMR para que los cambios del design system se reflejen en el MFE.
# Option A: MFE with design system from staging CDN (default)
pnpm dev
# Option B: MFE with local design system
# Terminal 1 — design system
cd packages/design-system && pnpm dev # starts on :3002
# Terminal 2 — MFE dashboard
MFE_DESIGN_SYSTEM_URL=http://localhost:3002 pnpm dev
Scripts de desarrollo en package.json:
// packages/mfe-dashboard/package.json
{
"scripts": {
"dev": "rsbuild dev",
"dev:standalone": "STANDALONE=true rsbuild dev",
"build": "rsbuild build",
"preview": "rsbuild preview",
"typecheck": "tsc --noEmit",
"lint": "eslint src/ --ext .ts,.tsx",
"test": "vitest run",
"test:watch": "vitest"
}
}
Desarrollo integrado (Shell + MFEs)
Para pruebas de integracion, la aplicacion shell se ejecuta en local y carga los MFE remotes desde dev servers locales o desde el CDN de staging.
Configuracion del shell dev server:
// packages/shell/rsbuild.config.ts
import { defineConfig, loadEnv } from '@rsbuild/core'; // ^1.7.3
import { pluginReact } from '@rsbuild/plugin-react'; // ^1.4.1
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
const env = loadEnv({ prefixes: ['MFE_', 'RSBUILD_PUBLIC_'] });
const MFE_DEFAULTS = {
dashboard: 'https://mfe-dashboard.staging.example.com',
settings: 'https://mfe-settings.staging.example.com',
analytics: 'https://mfe-analytics.staging.example.com',
};
function remotePath(name: string): string {
const envKey = `MFE_${name.toUpperCase()}_URL`;
const baseUrl = process.env[envKey] || MFE_DEFAULTS[name as keyof typeof MFE_DEFAULTS];
return `${name}@${baseUrl}/remoteEntry.js`;
}
export default defineConfig({
plugins: [pluginReact()],
server: {
port: 3000,
},
pluginModuleFederation({
name: 'shell',
remotes: {
mfe_dashboard: remotePath('dashboard'),
mfe_settings: remotePath('settings'),
mfe_analytics: remotePath('analytics'),
},
shared: {
react: { singleton: true, requiredVersion: '^19.2.4' },
'react-dom': { singleton: true, requiredVersion: '^19.2.4' },
'react-router': { singleton: true, requiredVersion: '^7.13.1' },
'@org/design-system': { singleton: true },
},
}),
});
.env.local para el shell (cargando MFEs desde localhost):
# packages/shell/.env.local
# Override MFE remote URLs for local development.
# Comment out any line to fall back to the staging CDN.
MFE_DASHBOARD_URL=http://localhost:3001
MFE_SETTINGS_URL=http://localhost:3003
# MFE_ANALYTICS_URL=http://localhost:3004 # use staging for analytics
Arranque del desarrollo integrado:
# Terminal 1 — Shell
cd packages/shell && pnpm dev # :3000
# Terminal 2 — Dashboard MFE
cd packages/mfe-dashboard && pnpm dev # :3001
# Terminal 3 — Settings MFE (optional)
cd packages/mfe-settings && pnpm dev # :3003
Con Turborepo, esto se simplifica:
# From monorepo root — starts shell + all MFEs concurrently
pnpm turbo dev --filter=shell --filter=mfe-dashboard --filter=mfe-settings
Desarrollo cross-repo con yalc
Por que no pnpm link
El enfoque intuitivo para desarrollo cross-repo es pnpm link, que crea un symlink desde el proyecto consumidor al codigo fuente local. Sin embargo, causa problemas reales en un setup de React + Module Federation:
-
Instancias duplicadas de React. Cuando
pnpm linkcrea un symlink, el paquete enlazado resuelve su propionode_modules/reacten lugar del host. React lo detecta y lanza el famoso error "Invalid hook call. Hooks can only be called inside the body of a function component." Esto ocurre porque los hooks de React dependen de un singleton a nivel de modulo — dos imports diferentes dereactsignifica dos dispatchers diferentes. -
Contexto de React roto. Incluso si se evitan las instancias duplicadas de React con aliases, los valores de contexto de
React.createContext()no cruzan las fronteras de symlinks. El provider y el consumer referencian objetos de contexto distintos. -
Conflictos en el shared scope de Module Federation. La configuracion
singleton: truede Module Federation resuelve duplicados en runtime, pero los paquetes con symlink esquivan esta resolucion porque se resuelven en tiempo de build a traves del filesystem.
yalc evita todos estos problemas simulando un npm publish real en local. Empaqueta la libreria en un tarball, la almacena en un store local (~/.yalc), y la instala en el proyecto consumidor como si viniera de un registry. El proyecto consumidor ve una dependencia normal en node_modules/, no un symlink.
Flujo de trabajo con yalc
Configuracion inicial (una sola vez):
# Install yalc globally
pnpm add -g yalc
Desarrollo cross-repo paso a paso:
# Step 1: In the monorepo, build and publish the design system to yalc's local store
cd ~/dev/monorepo/packages/design-system
pnpm build
yalc publish
# Output: @org/design-system@1.4.0 published to local store.
# Step 2: In the polyrepo MFE, add the local version
cd ~/dev/mfe-dashboard
yalc add @org/design-system
# This:
# - Creates a .yalc/ directory with the package contents
# - Updates package.json to point to "file:.yalc/@org/design-system"
# - Creates/updates yalc.lock
# Step 3: Install dependencies to wire everything up
pnpm install
# Step 4: Run the MFE dev server — it now uses the local design system
pnpm dev
Iterando sobre los cambios:
# After making changes to the design system:
cd ~/dev/monorepo/packages/design-system
pnpm build
yalc push
# yalc push automatically updates ALL projects that have added this package.
# The MFE dev server picks up the change (may require a page reload if
# HMR cannot handle the scope of the change).
Scripts recomendados en el package.json del design system:
// packages/design-system/package.json
{
"scripts": {
"dev": "rsbuild dev",
"build": "rsbuild build",
"yalc:publish": "pnpm build && yalc publish",
"yalc:push": "pnpm build && yalc push",
"yalc:watch": "chokidar 'src/**/*.{ts,tsx,css}' -c 'pnpm yalc:push' --debounce 500"
},
"devDependencies": {
"chokidar-cli": "^3.0.0"
}
}
Scripts recomendados en el package.json del MFE consumidor:
// packages/mfe-dashboard/package.json (polyrepo)
{
"scripts": {
"yalc:add-ds": "yalc add @org/design-system && pnpm install",
"yalc:remove-ds": "yalc remove @org/design-system && pnpm install",
"yalc:check": "yalc check"
}
}
Archivos generados por yalc (todos en gitignore):
mfe-dashboard/
├── .yalc/
│ └── @org/
│ └── design-system/ # local copy of the package
├── yalc.lock # tracks yalc-managed dependencies
└── .gitignore # must include .yalc/ and yalc.lock
Agregar al .gitignore:
# yalc local packages
.yalc/
yalc.lock
Limpieza
Cuando termines con el desarrollo cross-repo, restaura la dependencia original:
# Remove the yalc-linked package and restore the original version from the registry
cd ~/dev/mfe-dashboard
yalc remove @org/design-system
pnpm install
# Verify package.json no longer references file:.yalc/...
cat package.json | grep design-system
# Should show: "@org/design-system": "^1.4.0"
Para limpiar todas las instalaciones de yalc a nivel global:
# Remove all yalc installations across all projects
yalc installations clean
# Remove all packages from the local yalc store
yalc installations show # see what's stored
rm -rf ~/.yalc/packages # nuclear option
Verdaccio para pruebas de integracion en equipo
Configuracion inicial
Verdaccio es un proxy registry npm privado y ligero. Se utiliza para pruebas de integracion a nivel de equipo del ciclo completo de publicacion y consumo antes de lanzar los paquetes a GitHub Packages.
Setup basado en Docker (recomendado para equipos):
# Start Verdaccio with persistent storage
docker run -d \
--name verdaccio \
-p 4873:4873 \
-v verdaccio-storage:/verdaccio/storage \
-v verdaccio-conf:/verdaccio/conf \
verdaccio/verdaccio
# Verdaccio is now available at http://localhost:4873
Instalacion global (mas sencilla para uso individual):
pnpm add -g verdaccio
verdaccio # starts on http://localhost:4873
Configuracion (.verdaccio/config.yaml):
# .verdaccio/config.yaml
storage: ./storage
plugins: ./plugins
web:
title: "Platform Local Registry"
logo: ""
auth:
htpasswd:
file: ./htpasswd
max_users: 100
uplinks:
npmjs:
url: https://registry.npmjs.org/
github-packages:
url: https://npm.pkg.github.com/
auth:
type: bearer
token: "${GITHUB_TOKEN}"
packages:
"@org/*":
# Try local first, fall back to GitHub Packages
access: "$all"
publish: "$authenticated"
proxy: github-packages
"**":
access: "$all"
publish: "$authenticated"
proxy: npmjs
server:
keepAliveTimeout: 60
middlewares:
audit:
enabled: true
listen: 0.0.0.0:4873
log:
type: stdout
format: pretty
level: warn
Flujo de trabajo
Publicar en Verdaccio:
# Create a user (first time only)
npm adduser --registry http://localhost:4873
# Publish a pre-release version to Verdaccio
cd packages/design-system
pnpm build
# Use Changesets to version, then publish to Verdaccio instead of GitHub Packages
pnpm changeset version # bumps versions based on changesets
pnpm publish --registry http://localhost:4873 --no-git-checks
# Or publish a specific pre-release tag
pnpm publish --registry http://localhost:4873 --tag canary --no-git-checks
Consumir desde Verdaccio (otros miembros del equipo):
# In the consuming project, create or update .npmrc to point to Verdaccio
# .npmrc (project-level, gitignored for local testing)
@org:registry=http://localhost:4873
# Install the pre-release version
pnpm add @org/design-system@canary
# or
pnpm add @org/design-system@1.5.0-canary.1
# Run the project and verify the integration
pnpm dev
Usar Verdaccio para probar un ciclo completo de release con Changesets:
# 1. Create changesets as normal
pnpm changeset
# 2. Version packages
pnpm changeset version
# 3. Publish to Verdaccio (instead of GitHub Packages) to verify
pnpm changeset publish --registry http://localhost:4873
# 4. In consuming projects, install from Verdaccio and test
# 5. Once verified, publish for real to GitHub Packages
pnpm changeset publish
Cuando usar yalc vs Verdaccio
| Aspecto | yalc | Verdaccio |
|---|---|---|
| Alcance | Desarrollador individual, maquina local | Equipo completo, accesible por red |
| Tiempo de setup | Segundos (pnpm add -g yalc) | Minutos (Docker o instalacion global + configuracion) |
| Simula | npm pack + instalacion local | Ciclo completo de publicacion/instalacion en npm registry |
| Resolucion de versiones | Referencia por ruta de archivo en package.json | Resolucion estandar por semver |
| Integracion con Changesets | Ninguna | Completa — permite probar changeset publish |
| Pruebas en CI/CD | No aplica | Se puede ejecutar en CI para pruebas de integracion |
| Usar cuando | Iteras en una libreria mientras pruebas en un consumidor | Pruebas del pipeline de release completo antes de publicar |
| Velocidad | Actualizaciones instantaneas con yalc push | Requiere pnpm publish + pnpm install por iteracion |
| Visibilidad en equipo | Solo en tu maquina | Compartido en todo el equipo (si Verdaccio esta en servidor compartido) |
Regla general:
- Usa yalc para el bucle interno de desarrollo — estas cambiando activamente una libreria y quieres ver el efecto en un consumidor de forma inmediata.
- Usa Verdaccio para el bucle externo de verificacion — quieres confirmar que el artefacto publicado, la resolucion de versiones y el comportamiento de instalacion funcionan correctamente antes de lanzar.
Ejecutar Workers en local
wrangler dev
El comando wrangler dev (v4) de Cloudflare arranca un Worker dev server local impulsado por Miniflare. Soporta KV, R2, D1 y Durable Objects con persistencia local para que el estado sobreviva a los reinicios.
Nota: Wrangler v3 llego al fin de vida en Q1 2026. Todos los proyectos deben usar Wrangler v4. Ejecuta
pnpm add -D wrangler@^4para actualizar.
Configuracion tipica de wrangler.toml para desarrollo local:
# workers/api-gateway/wrangler.toml
name = "api-gateway"
main = "src/index.ts"
compatibility_date = "2026-02-25"
[dev]
port = 8787
local_protocol = "http"
ip = "0.0.0.0"
# KV namespace bindings
[[kv_namespaces]]
binding = "SESSION_STORE"
id = "abc123" # production KV ID
preview_id = "dev-session-store" # used in wrangler dev
# D1 database binding
[[d1_databases]]
binding = "DB"
database_name = "platform-db"
database_id = "def456"
# R2 bucket binding
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "platform-assets"
preview_bucket_name = "platform-assets-dev"
# Service bindings to other Workers
[[services]]
binding = "AUTH_WORKER"
service = "auth-worker"
[[services]]
binding = "TENANT_WORKER"
service = "tenant-worker"
# Durable Object bindings
[durable_objects]
bindings = [
{ name = "COLLAB_SESSIONS", class_name = "CollabSession" }
]
[[migrations]]
tag = "v1"
new_classes = ["CollabSession"]
Arrancar el Worker en local:
# Start with local persistence (state stored in .wrangler/state/)
cd workers/api-gateway
wrangler dev --persist-to .wrangler/state
# The Worker is now available at http://localhost:8787
Ejecutar multiples Workers con service bindings:
Cuando los Workers se referencian entre si mediante service bindings, hay que ejecutar cada Worker en un terminal separado. Wrangler v4 descubre automaticamente otros Workers ejecutandose en local para resolver los service bindings. Los remote service bindings ya estan en GA y no requieren flags experimentales.
# Terminal 1 — Auth Worker
cd workers/auth-worker
wrangler dev --port 8788 --persist-to .wrangler/state
# Terminal 2 — Tenant Worker
cd workers/tenant-worker
wrangler dev --port 8789 --persist-to .wrangler/state
# Terminal 3 — API Gateway (calls Auth and Tenant Workers via service bindings)
cd workers/api-gateway
wrangler dev --port 8787 --persist-to .wrangler/state
# The API Gateway will route service binding calls to the locally running Workers.
Poblar D1 local para desarrollo:
# Apply migrations to local D1
cd workers/api-gateway
wrangler d1 execute platform-db --local --file=./migrations/0001_init.sql
wrangler d1 execute platform-db --local --file=./seeds/dev-data.sql
# Verify
wrangler d1 execute platform-db --local --command="SELECT * FROM tenants LIMIT 5"
Miniflare para Durable Objects
wrangler dev usa Miniflare internamente, lo que proporciona soporte local completo para Durable Objects incluyendo conexiones WebSocket.
Pruebas locales de Durable Objects:
// workers/collab-worker/src/collab-session.ts
import { DurableObject } from 'cloudflare:workers';
export class CollabSession extends DurableObject {
private connections: Set<WebSocket> = new Set();
async fetch(request: Request): Promise<Response> {
if (request.headers.get('Upgrade') === 'websocket') {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.ctx.acceptWebSocket(server);
this.connections.add(server);
return new Response(null, { status: 101, webSocket: client });
}
// REST API for Durable Object state
const url = new URL(request.url);
if (url.pathname === '/state') {
const state = await this.ctx.storage.get('document');
return Response.json({ state });
}
return new Response('Not found', { status: 404 });
}
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
// Broadcast to all connected clients
const data = typeof message === 'string' ? message : new TextDecoder().decode(message);
for (const conn of this.connections) {
if (conn !== ws && conn.readyState === WebSocket.OPEN) {
conn.send(data);
}
}
// Persist state
const parsed = JSON.parse(data as string);
if (parsed.type === 'update') {
await this.ctx.storage.put('document', parsed.payload);
}
}
async webSocketClose(ws: WebSocket) {
this.connections.delete(ws);
}
}
Probar WebSockets en local:
# Start the collab Worker
cd workers/collab-worker
wrangler dev --port 8790 --persist-to .wrangler/state
# Test with wscat (install: pnpm add -g wscat)
wscat -c ws://localhost:8790/session/doc-123
# Or test from the browser console:
# const ws = new WebSocket('ws://localhost:8790/session/doc-123');
# ws.onmessage = (e) => console.log('received:', e.data);
# ws.send(JSON.stringify({ type: 'update', payload: { text: 'hello' } }));
Setup de pruebas de integracion con Miniflare (via unstable_dev):
// workers/collab-worker/test/collab-session.test.ts
import { unstable_dev, UnstableDevWorker } from 'wrangler';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
describe('CollabSession Durable Object', () => {
let worker: UnstableDevWorker;
beforeAll(async () => {
worker = await unstable_dev('src/index.ts', {
vars: {
ENVIRONMENT: 'test',
},
});
});
afterAll(async () => {
await worker.stop();
});
it('should accept WebSocket connections', async () => {
const resp = await worker.fetch('/session/test-doc', {
headers: { Upgrade: 'websocket' },
});
expect(resp.status).toBe(101);
});
it('should persist document state', async () => {
// Send an update via WebSocket, then verify via REST
const resp = await worker.fetch('/session/test-doc/state');
const data = await resp.json();
expect(data).toHaveProperty('state');
});
});
Limitaciones conocidas del desarrollo local con Durable Objects:
- Sin simulacion de hibernacion. La API de Hibernatable WebSockets funciona en local, pero el comportamiento real de hibernacion (desalojo de memoria, wake-on-message) no se simula. El Durable Object permanece en memoria durante toda la vida del proceso
wrangler dev. - Sin unicidad global. En local, todas las instancias de Durable Objects se ejecutan en el mismo isolate. En produccion, son globalmente unicas y pueden ejecutarse en distintos data centers.
- Programacion de alarms. Los alarms de Durable Objects funcionan en local pero los tiempos pueden diferir de produccion.
Variables de entorno y secretos
.dev.vars
Cloudflare Workers usa .dev.vars para secretos locales, analogo a .env en aplicaciones Node.js. Este archivo lo carga wrangler dev y su contenido se inyecta como environment bindings del Worker.
Seguridad: Nunca hagas commit de
.dev.varsal control de versiones. Rota inmediatamente cualquier secreto en.dev.varssi se hace commit por accidente. Usawrangler secret put <KEY>para gestionar los secretos de produccion por separado. Trata todos los valores en.dev.varscomo credenciales exclusivas de test/desarrollo y evita reutilizar secretos de produccion para desarrollo local.
Ejemplo de archivo .dev.vars:
# workers/api-gateway/.dev.vars
# This file is NOT committed to git.
# WorkOS authentication
WORKOS_API_KEY=sk_test_abc123def456
WORKOS_CLIENT_ID=client_01ABC
WORKOS_WEBHOOK_SECRET=whsec_test_xyz789
# Database
DATABASE_URL=postgresql://localhost:5432/platform_dev
# External services
STRIPE_SECRET_KEY=sk_test_stripe_key_here
SENDGRID_API_KEY=SG.test_key_here
# Internal
JWT_SIGNING_SECRET=dev-signing-secret-not-for-production
ENCRYPTION_KEY=dev-encryption-key-32-bytes-long!!
# Environment indicator
ENVIRONMENT=development
Agregar al .gitignore:
# Cloudflare Workers local secrets
.dev.vars
# Wrangler local state (KV, D1, R2 data)
.wrangler/
Variables de entorno del frontend
Rsbuild usa el prefijo RSBUILD_PUBLIC_ para variables de entorno que deben estar disponibles en el codigo del lado cliente. Se reemplazan estaticamente en tiempo de build.
.env (versionado, valores por defecto compartidos):
# packages/shell/.env
# Default values — overridden by .env.local and .env.production
RSBUILD_PUBLIC_APP_NAME=Platform
RSBUILD_PUBLIC_API_GATEWAY_URL=https://api.staging.example.com
RSBUILD_PUBLIC_WORKOS_CLIENT_ID=client_01ABC
RSBUILD_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/auth/callback
.env.local (no versionado, sobrecargas locales):
# packages/shell/.env.local
# Local overrides — NOT committed to git.
# Point API gateway to local Worker
RSBUILD_PUBLIC_API_GATEWAY_URL=http://localhost:8787
# MFE remote URLs (local dev servers)
MFE_DASHBOARD_URL=http://localhost:3001
MFE_SETTINGS_URL=http://localhost:3003
MFE_ANALYTICS_URL=http://localhost:3004
# WorkOS local development
RSBUILD_PUBLIC_WORKOS_CLIENT_ID=client_01ABC_DEV
RSBUILD_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/auth/callback
# Feature flags for local development
RSBUILD_PUBLIC_ENABLE_DEV_TOOLS=true
RSBUILD_PUBLIC_MOCK_AUTH=false
Acceso a variables de entorno en el codigo:
// packages/shell/src/config.ts
export const config = {
appName: process.env.RSBUILD_PUBLIC_APP_NAME!,
apiGatewayUrl: process.env.RSBUILD_PUBLIC_API_GATEWAY_URL!,
workos: {
clientId: process.env.RSBUILD_PUBLIC_WORKOS_CLIENT_ID!,
redirectUri: process.env.RSBUILD_PUBLIC_WORKOS_REDIRECT_URI!,
},
enableDevTools: process.env.RSBUILD_PUBLIC_ENABLE_DEV_TOOLS === 'true',
} as const;
Orden de carga de variables de entorno en Rsbuild:
.env— valores por defecto compartidos (versionado).env.local— sobrecargas locales (en gitignore).env.development— especificos de desarrollo (versionado).env.development.local— sobrecargas locales de desarrollo (en gitignore)- Variables de entorno del shell — maxima prioridad
Scripts de desarrollo recomendados
El siguiente Makefile proporciona una interfaz unificada para todas las tareas de desarrollo habituales. Se coloca en la raiz del monorepo.
Requisito previo: Los targets
dev-workersydev-allusanconcurrentlypara ejecutar multiples procesos en paralelo. Instalalo como devDependency de la raiz:pnpm add -D concurrently -w
# Makefile — Monorepo root
# Usage: make <target>
.PHONY: help dev dev-shell dev-workers dev-all \
yalc-publish yalc-push yalc-watch yalc-clean \
test test-integration e2e \
build typecheck lint format \
verdaccio-start verdaccio-publish \
db-migrate db-seed clean
# ─── Development ──────────────────────────────────────────────────
dev: ## Start a specific MFE dev server (usage: make dev MFE=dashboard)
@if [ -z "$(MFE)" ]; then \
echo "Usage: make dev MFE=<name>"; \
echo "Available: dashboard, settings, analytics"; \
exit 1; \
fi
pnpm --filter mfe-$(MFE) dev
dev-shell: ## Start shell with local MFE remotes
pnpm --filter shell dev
dev-design-system: ## Start design system dev server (for cross-MFE testing)
pnpm --filter @org/design-system dev
dev-workers: ## Start all Workers locally (parallel)
@echo "Starting Workers..."
npx concurrently \
-n "api-gw,auth,tenant" \
-c "blue,green,yellow" \
"cd workers/api-gateway && wrangler dev --port 8787 --persist-to .wrangler/state" \
"cd workers/auth-worker && wrangler dev --port 8788 --persist-to .wrangler/state" \
"cd workers/tenant-worker && wrangler dev --port 8789 --persist-to .wrangler/state"
dev-all: ## Start everything: shell + MFEs + Workers
npx concurrently \
-n "shell,dashboard,settings,api-gw,auth,tenant" \
-c "cyan,blue,magenta,green,yellow,red" \
"pnpm --filter shell dev" \
"pnpm --filter mfe-dashboard dev" \
"pnpm --filter mfe-settings dev" \
"cd workers/api-gateway && wrangler dev --port 8787 --persist-to .wrangler/state" \
"cd workers/auth-worker && wrangler dev --port 8788 --persist-to .wrangler/state" \
"cd workers/tenant-worker && wrangler dev --port 8789 --persist-to .wrangler/state"
# ─── Cross-Repo (yalc) ───────────────────────────────────────────
yalc-publish: ## Build and publish design system to yalc local store
pnpm --filter @org/design-system build
cd packages/design-system && yalc publish
@echo "Published to yalc. Run 'yalc add @org/design-system' in consuming repos."
yalc-push: ## Build and push design system updates to all yalc consumers
pnpm --filter @org/design-system build
cd packages/design-system && yalc push
@echo "Pushed to all yalc consumers."
yalc-watch: ## Watch design system sources and auto-push to yalc on change
cd packages/design-system && pnpm yalc:watch
yalc-clean: ## Remove all yalc installations
yalc installations clean
@echo "All yalc installations cleaned."
# ─── Verdaccio ────────────────────────────────────────────────────
verdaccio-start: ## Start Verdaccio local registry via Docker
docker run -d \
--name verdaccio \
-p 4873:4873 \
-v verdaccio-storage:/verdaccio/storage \
verdaccio/verdaccio
@echo "Verdaccio running at http://localhost:4873"
verdaccio-publish: ## Publish all packages to local Verdaccio
pnpm turbo build --filter='./packages/*'
pnpm --filter './packages/*' publish --registry http://localhost:4873 --no-git-checks
@echo "Published all packages to Verdaccio."
# ─── Testing ──────────────────────────────────────────────────────
test: ## Run unit tests across all packages
pnpm turbo test
test-integration: ## Run integration tests (requires Workers running locally)
pnpm turbo test:integration
e2e: ## Run Playwright E2E tests
pnpm --filter e2e-tests exec playwright test
e2e-ui: ## Run Playwright E2E tests with UI mode
pnpm --filter e2e-tests exec playwright test --ui
# ─── Build & Quality ─────────────────────────────────────────────
build: ## Production build of all packages
pnpm turbo build
typecheck: ## TypeScript type checking across all packages
pnpm turbo typecheck
lint: ## Run ESLint + Prettier check
pnpm turbo lint
format: ## Auto-fix formatting with Prettier
pnpm prettier --write "**/*.{ts,tsx,json,md,css}"
# ─── Database ─────────────────────────────────────────────────────
db-migrate: ## Run D1 migrations locally
cd workers/api-gateway && \
for f in migrations/*.sql; do \
echo "Applying $$f..."; \
wrangler d1 execute platform-db --local --file=$$f; \
done
db-seed: ## Seed local D1 with development data
cd workers/api-gateway && \
wrangler d1 execute platform-db --local --file=seeds/dev-data.sql
@echo "Local database seeded."
# ─── Utilities ────────────────────────────────────────────────────
clean: ## Clean all build artifacts, node_modules caches, and wrangler state
pnpm turbo clean
rm -rf .turbo
find . -name '.wrangler' -type d -prune -exec rm -rf {} +
@echo "Cleaned."
help: ## Show this help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help
Resolucion de problemas frecuentes
Conflictos de puertos al ejecutar multiples dev servers
Sintoma: Error: listen EADDRINUSE :::3001 al arrancar un dev server.
Solucion:
# Find what is using the port
lsof -i :3001
# Kill the process
kill -9 <PID>
# Or use a different port
PORT=3005 pnpm dev
Asigna puertos fijos y conocidos a cada servicio para evitar conflictos:
| Servicio | Puerto |
|---|---|
| Shell | 3000 |
| MFE Dashboard | 3001 |
| MFE Design System | 3002 |
| MFE Settings | 3003 |
| MFE Analytics | 3004 |
| API Gateway Worker | 8787 |
| Auth Worker | 8788 |
| Tenant Worker | 8789 |
| Collab Worker | 8790 |
| Verdaccio | 4873 |
HMR de Module Federation no funciona entre remotes
Sintoma: Los cambios en un MFE remote no se reflejan en el shell via HMR; solo un reload completo de pagina los recoge.
Explicacion: Module Federation v2 soporta HMR dentro de un remote (los cambios a archivos dentro del boundary del remote se recargan en caliente correctamente), pero los cambios al remote entry manifest o a los boundaries de modulos expuestos requieren un reload completo del host. Es una limitacion conocida.
Alternativas:
// In the shell's rsbuild.config.ts, enable live reload as a fallback
export default defineConfig({
dev: {
hmr: true,
liveReload: true, // falls back to full reload when HMR fails
},
});
Para iterar mas rapido, desarrolla el MFE en modo solitario (con el mock shell context) donde HMR funciona completamente, y usa el modo integrado solo para verificacion final.
Errores de CORS al cargar MF remotes desde diferentes puertos de localhost
Sintoma: Access to script at 'http://localhost:3001/remoteEntry.js' from origin 'http://localhost:3000' has been blocked by CORS policy.
Solucion: Asegurate de que cada MFE dev server establece el header Access-Control-Allow-Origin:
// rsbuild.config.ts for each MFE
export default defineConfig({
server: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
},
});
El estado de KV/D1 no persiste entre reinicios de wrangler dev
Sintoma: Los datos locales de KV o D1 desaparecen tras reiniciar wrangler dev.
Causa: Por defecto, wrangler dev usa almacenamiento en memoria. El estado solo se persiste cuando pasas explicitamente --persist-to.
Solucion:
# Always use --persist-to for stateful development
wrangler dev --persist-to .wrangler/state
# Verify state files exist
ls .wrangler/state/
# Should show: v4/ directory with kv/, d1/, r2/ subdirectories
Agrega --persist-to .wrangler/state a todos los scripts de desarrollo de Workers en el Makefile y package.json.
Los tipos del design system no se actualizan despues de yalc push
Sintoma: TypeScript sigue viendo las definiciones de tipos antiguas del design system despues de ejecutar yalc push, aunque el codigo en runtime se haya actualizado.
Causa: TypeScript cachea los tipos de modulos resueltos. El language server de TypeScript no observa .yalc/ en busca de cambios.
Solucion:
# Option 1: Restart the TypeScript language server
# In VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server"
# Option 2: Clear the TypeScript build cache
rm -rf node_modules/.cache
rm -rf tsconfig.tsbuildinfo
# Option 3: Touch the tsconfig to force re-resolution
touch tsconfig.json
Para un flujo de trabajo mas robusto, agrega un script postpush al design system:
// packages/design-system/package.json
{
"scripts": {
"yalc:push": "pnpm build && yalc push --changed"
}
}
El flag --changed asegura que yalc solo hace push si la salida del build realmente cambio, reduciendo ruido de invalidacion innecesario.
wrangler dev no puede resolver service bindings
Sintoma: Las llamadas a env.AUTH_WORKER.fetch() fallan con "Service not found" al ejecutar en local.
Solucion: Todos los Workers involucrados en service bindings deben estar ejecutandose en local al mismo tiempo. Wrangler descubre otros Workers locales automaticamente, pero el orden de arranque importa — el Worker que hace la llamada al service binding debe arrancar despues de que el Worker destino este listo.
# Start dependencies first, then the gateway
# Terminal 1
cd workers/auth-worker && wrangler dev --port 8788
# Terminal 2 (after auth-worker is ready)
cd workers/api-gateway && wrangler dev --port 8787
Referencias
- yalc — https://github.com/wclr/yalc — Flujo de trabajo para desarrollo y pruebas de paquetes locales.
- Verdaccio — https://verdaccio.org/docs/what-is-verdaccio — Proxy registry npm privado y ligero.
- wrangler dev — https://developers.cloudflare.com/workers/wrangler/commands/#dev — Servidor de desarrollo local para Workers.
- Miniflare — https://miniflare.dev/ — Simulador local de Cloudflare Workers (ahora integrado en wrangler).
- Rsbuild Dev Server — https://rsbuild.dev/guide/basic/server — Configuracion del servidor de desarrollo de Rsbuild.
- Module Federation v2 — https://module-federation.io/ — Documentacion y guias de Module Federation.
- Turborepo — https://turbo.build/repo/docs — Sistema de build para monorepos.
- Cloudflare D1 — https://developers.cloudflare.com/d1/ — Base de datos SQL serverless.
- Cloudflare Durable Objects — https://developers.cloudflare.com/durable-objects/ — Computacion serverless con estado.