Module Federation and Micro-Frontend Architectures
Architectural Foundations of Runtime Module Sharing
Module federation shifts dependency resolution from compile-time to runtime, enabling independent deployment cycles for micro-frontends. This architecture requires strict boundary contracts and relies on foundational Core Concepts of Modern Bundling to manage chunk loading, manifest generation, and cross-origin asset resolution without monolithic coupling. By deferring module resolution until execution, teams achieve parallel deployment pipelines while maintaining a unified user experience.
Implementation Workflow
- Define host/remote roles using
@originjs/vite-plugin-federationor native Rollup federation plugins. - Configure
exposesfor public component APIs andremotesfor external consumption. - Implement a shared manifest endpoint or DNS-based routing to dynamically resolve remote entry points at runtime.
- Validate remote handshake latency before mounting UI components.
Configuration Patterns
// vite.config.ts
import federation from '@originjs/vite-plugin-federation';
export default {
plugins: [
federation({
name: 'app1',
filename: 'remoteEntry.js',
exposes: { './Button': './src/components/Button.tsx' },
shared: ['react', 'react-dom']
})
]
};
// rollup.config.mjs
import federation from '@rollup/plugin-module-federation';
export default {
plugins: [
federation({
name: 'remote_app',
filename: 'remoteEntry.js',
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' }
}
})
]
};
Debugging Paths
- Verify
remoteEntry.jsloads before host initialization using the Network tab waterfall analysis. Target< 200msTTFB for remote entry points. - Inspect
window.__FEDERATION__(or equivalent runtime registry) to confirm exposed module availability and version alignment. - Implement
import.meta.globfallbacks for graceful degradation when remote endpoints fail or timeout.
Performance Impact: Properly configured federation reduces initial JavaScript payload by 35–60% compared to monolithic builds, directly improving First Contentful Paint (FCP) by 150–300ms on 3G connections.
Cross-Boundary Dependency Isolation and Format Interop
Micro-frontends frequently mix dependency formats across organizational boundaries, requiring explicit resolution strategies. When federating across teams, mismatched package exports can trigger dual-package hazards. Properly aligning module and exports fields prevents runtime crashes, a challenge thoroughly explored in Understanding ESM vs CommonJS in Modern Bundlers.
Implementation Workflow
- Audit dependency trees for CJS/ESM mismatches before exposing modules using
npm lsorpnpm why. - Configure bundler interop shims or apply
@rollup/plugin-commonjsselectively to legacy dependencies. - Enforce ESM-first consumption in shared libraries to avoid
require()/import()boundary violations during runtime evaluation.
Configuration Patterns
// esbuild.config.js
require('esbuild').build({
entryPoints: ['src/index.ts'],
bundle: true,
format: 'esm',
external: ['react', 'react-dom'],
outdir: 'dist',
splitting: true
});
// vite.config.ts
export default {
optimizeDeps: {
include: ['shared-ui-lib'], // Pre-bundle CJS dependencies before federation handshake
exclude: ['react', 'react-dom']
}
};
Debugging Paths
- Trace
require is not definederrors to CJS modules loaded in ESM-only remotes. Resolve by wrapping in dynamicimport()or applying@rollup/plugin-commonjs. - Inspect
import.meta.urland__dirnamepolyfills in federated chunks for path resolution failures. - Use
rollup-plugin-visualizerto verify shared dependencies are not duplicated across remote boundaries. Target< 5%duplication rate in shared vendor chunks.
CLI Execution:
# Validate interop during build
vite build --mode production --report
# Inspect final bundle composition
npx rollup-plugin-visualizer --open
Optimization Workflows and Dead Code Elimination
Federation inherently complicates static analysis, making aggressive optimization critical. Without careful configuration, unused shared dependencies inflate payloads across independently deployed apps. Implementing precise shared boundaries and leveraging static analysis ensures Tree-Shaking Mechanics and Dead Code Elimination functions correctly across distributed module graphs.
Implementation Workflow
- Mark shared dependencies with
singleton: trueandstrictVersion: trueto prevent duplicate runtime injection. - Use dynamic
import()for lazy-loaded remotes to defer network requests until route activation. - Configure
sideEffects: falsein sharedpackage.jsonmanifests to enable safe pruning across host/remote boundaries.
Configuration Patterns
// vite.config.ts (Shared Config)
export default {
plugins: [
federation({
shared: {
lodash: { singleton: true, requiredVersion: '^4.17.0', strictVersion: true },
'lodash-es': { singleton: true, requiredVersion: '^4.17.0', strictVersion: true }
}
})
]
};
// esbuild.config.js
require('esbuild').build({
minify: true,
treeShaking: true,
splitting: true,
external: ['react', 'react-dom', 'lodash-es']
});
Debugging Paths
- Audit network payloads for duplicate framework instances using runtime checks:
if (window.React !== window.remote.React) console.warn('Dual React instance detected'). - Enable
build.reportCompressedSizeand compare against monolithic baselines to detect shared bloat. Target< 150KBgzipped for initial remote payloads. - Use
@rollup/plugin-node-resolvewithbrowserandmodulefield prioritization to prevent dead code inclusion in remote chunks.
Performance Impact: Strict singleton enforcement and sideEffects: false declarations typically yield 20–40% reduction in shared chunk sizes, directly lowering Time to Interactive (TTI) by 100–250ms on mid-tier devices.
Toolchain Parity and Migration Strategies
Transitioning from legacy bundlers to modern Vite/Rollup/esbuild stacks requires understanding runtime differences. While Webpack pioneered native federation, modern toolchains prioritize dev-server speed and ESM-native execution. A detailed Webpack vs Vite module federation comparison highlights trade-offs in HMR propagation, chunk hashing, and plugin ecosystem maturity.
Implementation Workflow
- Map Webpack
ModuleFederationPluginconfigurations to Vite/Rollup equivalents, focusing onexposes/remotessyntax alignment. - Replace
webpack-dev-serverwithvite --hostfor federated dev environments, leveraging native ESM hot reloading. - Implement custom
resolve.aliasfor cross-repo linking during local development to bypass remote network calls.
Configuration Patterns
// vite.config.ts
export default {
server: {
fs: { strict: false }, // Enable monorepo symlink resolution across federated workspaces
hmr: { overlay: true, timeout: 5000 }
},
resolve: {
alias: {
'@remote-app': path.resolve(__dirname, '../remote-app/src')
}
}
};
// rollup.config.mjs
export default {
preserveSymlinks: true, // Maintain local dev overrides before remote publishing
plugins: [
alias({
entries: [{ find: '@shared', replacement: path.resolve(__dirname, '../shared') }]
})
]
};
Debugging Paths
- Monitor HMR WebSocket connections across multiple dev servers on different ports to prevent stale updates. Ensure
VITE_HMR_PORTis explicitly configured. - Validate chunk hash consistency between build and dev modes to prevent cache poisoning in remote manifests.
- Use
vite --debugto trace module resolution fallbacks when federated remotes fail to resolve during hot reload.
CLI Execution:
# Start federated dev environment with explicit port mapping
vite --host --strictPort --port 3000 --debug
# Watch mode with preserved symlinks for local linking
rollup -c --watch --preserveSymlinks
Production Observability and Cross-Boundary Debugging
Distributed architectures require unified debugging strategies. Source maps, error boundaries, and performance tracing must span host and remote boundaries. Implement centralized error reporting, cross-origin sourcemap merging, and runtime health checks to maintain reliability in production deployments.
Implementation Workflow
- Configure
sourcemap: 'hidden'or'inline'per environment to balance security and debuggability. - Deploy a unified error aggregation service (e.g., Sentry, Datadog) with cross-origin CORS headers enabled for
.mapfiles. - Implement
window.onerrorandunhandledrejectionlisteners that capture remote stack traces and normalize them against host source maps.
Configuration Patterns
// vite.config.ts
export default {
build: {
sourcemap: 'hidden', // Generates .map files without exposing them in HTML
cssCodeSplit: true, // Isolated style injection per remote boundary
reportCompressedSize: true
}
};
// rollup.config.mjs
export default {
output: {
sourcemap: true,
sourcemapPathTransform: (relativeSourcePath, sourcemapPath) => {
// Normalize CDN paths for cross-origin source map resolution
return `https://cdn.example.com/assets/${relativeSourcePath}`;
}
}
};
Debugging Paths
- Verify CORS headers (
Access-Control-Allow-Origin: *) for.mapfiles hosted on different CDNs to prevent source map fetch failures. - Use Chrome DevTools
Sourcespanel to map federated chunk URLs to original source paths viasourceMappingURLdirectives. - Implement
performance.mark()andperformance.measure()aroundimport()calls to detect remote load latency and waterfall bottlenecks. Target< 300msfor remote chunk hydration.
Performance Impact: Centralized error mapping and hidden sourcemaps reduce Mean Time to Resolution (MTTR) by 40–60% while keeping initial payload overhead under 2% via deferred .map fetching.