Configuring Webpack Module Federation #
This guide details the architectural patterns and configuration workflows for Configuring Webpack Module Federation in enterprise micro-frontend ecosystems. We cover plugin initialization, runtime dependency resolution, and production deployment strategies to ensure scalable, isolated container communication.
Key Implementation Objectives:
- Define host and remote roles with precise
exposesandremotesmappings - Implement strict version resolution for shared dependencies to prevent runtime crashes
- Configure dynamic remote loading with graceful fallback mechanisms
- Optimize build pipelines for isolated container deployment and cache longevity
Setup/Config #
Establish foundational ModuleFederationPlugin syntax, define container boundaries, and align output paths. Follow the Step-by-step Webpack 5 container configuration to ensure correct chunk generation and remote entry exposure.
At build time, the plugin generates a remoteEntry.js manifest that exposes module metadata and shared dependency contracts. The output.publicPath must be explicitly aligned with your deployment environment. Hardcoding / will break cross-origin asset resolution; use auto or a dynamic runtime variable to ensure the browser fetches chunks from the correct origin.
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
output: {
publicPath: 'auto', // Critical for CDN/cross-origin deployments
uniqueName: 'hostApp',
},
plugins: [
new ModuleFederationPlugin({
name: 'hostApp',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/components/Header.tsx',
'./Auth': './src/providers/AuthProvider.tsx'
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' }
}
})
]
};
Runtime vs Build-Time Implications: The exposes map is resolved at build time into separate Webpack chunks. At runtime, the host’s remoteEntry.js acts as a dynamic module registry, allowing the browser to fetch exposed components on-demand without full page reloads.
Integration #
Configure runtime dependency sharing, implement dynamic remote consumption, and establish cross-container API contracts. Contrast this approach with Setting Up Vite with Federation Plugins to understand bundler-specific integration trade-offs, particularly around ES module preloading and dev server proxying.
Remote consumption relies on Webpack’s internal sharing scope. Before invoking a remote module, you must initialize the shared scope and bootstrap the remote container. This sequence guarantees that singleton dependencies are resolved before the remote’s execution context is established.
// remoteLoader.ts
const loadRemote = async (scope: string, module: string) => {
// 1. Initialize host's shared scope
await __webpack_init_sharing__('default');
// 2. Load remote container from window/global scope
const container = window[scope as keyof Window];
if (!container) throw new Error(`Remote container '${scope}' not found`);
// 3. Initialize remote with host's shared dependencies
await container.init(__webpack_share_scopes__.default);
// 4. Fetch and instantiate the exposed module
const factory = await container.get(module);
return factory();
};
// React integration with fallback
import React from 'react';
const RemoteComponent = React.lazy(() =>
loadRemote('remoteApp', './Dashboard').catch(() => import('./FallbackDashboard'))
);
Runtime vs Build-Time Implications: loadRemote executes entirely at runtime. The __webpack_init_sharing__ and container.init() calls negotiate dependency versions dynamically. Build-time type contracts should be enforced via shared TypeScript interfaces or .d.ts stubs to prevent interface drift between independently deployed containers.
Edge Cases #
Address dependency version collisions, CSS scoping isolation, and state leakage between federated boundaries. Reference Managing Shared Dependencies at Runtime for advanced fallback strategies when strict version matching fails.
Version mismatches are the primary cause of runtime crashes in federated architectures. Webpack’s shared configuration allows granular control over how dependencies are resolved across container boundaries. Use strictVersion to enforce exact matches during development, and configure requiredVersion with semver ranges to allow safe fallbacks in production.
// Advanced shared dependency configuration
shared: {
'lodash': {
singleton: true,
requiredVersion: '^4.17.21',
eager: false // Lazy-loaded to reduce initial bundle size
},
'@mui/material': {
singleton: true,
requiredVersion: '^5.14.0',
strictVersion: true // Throws build/runtime error if incompatible
}
}
Runtime vs Build-Time Implications: Setting eager: false defers dependency resolution to runtime, keeping the initial chunk lean. When strictVersion: true is enabled, Webpack validates the host’s installed version against the remote’s requiredVersion at build time and throws a hard error if they diverge, preventing silent hook violations or API mismatches.
Testing/Validation #
Validate remote entry loading, mock federation boundaries in CI/CD, and verify shared dependency resolution under network constraints. Implement failure simulation protocols aligned with Debugging remote container loading failures in production.
Federation testing requires mocking the global Webpack sharing APIs. In unit and integration tests, stub __webpack_init_sharing__, __webpack_share_scopes__, and window[scope] to simulate successful container initialization. Use network interception tools to force remoteEntry.js to return 500 or 404 responses, validating that your React.lazy fallbacks and error boundaries trigger correctly.
Validation Checklist:
- Mocking
__webpack_init_sharing__: Ensure test runners inject a no-op or promise-resolving mock for the sharing initializer. - Contract Testing: Verify exposed module exports match the agreed TypeScript interface using schema validation or snapshot testing.
- Network Failure Simulation: Use Playwright/Cypress to throttle networks and drop
remoteEntry.jsrequests, confirming graceful degradation. - Fallback Version Verification: Assert that when a remote requests an incompatible dependency version, the host’s fallback version is correctly injected without crashing the execution thread.
Deployment #
Configure long-term caching, optimize chunk splitting, and implement CDN-ready deployment patterns for federated assets. Apply Optimizing Webpack cache for faster federation builds to reduce CI pipeline latency and ensure deterministic builds.
Independent container deployment requires careful cache invalidation. remoteEntry.js should be served with Cache-Control: no-cache or must-revalidate to ensure the host always fetches the latest manifest. All other chunks should utilize content hashing ([contenthash]) to enable aggressive long-term caching.
Deployment Strategy:
- Content Hashing: Configure
output.filename: '[name].[contenthash].js'for all exposed modules and shared chunks. - Independent Release Cycles: Deploy remotes independently. The host only needs to know the remote’s base URL; it does not require a rebuild when a remote updates.
- CDN Cache Invalidation: Purge CDN caches for
remoteEntry.jsimmediately after deployment. Leave hashed chunks untouched to preserve edge cache hits. - Build Performance Tuning: Enable
cache: { type: 'filesystem' }and isolateModuleFederationPlugincache directories per container to prevent cross-contamination during parallel CI builds.
Common Pitfalls #
| Issue | Root Cause & Resolution |
|---|---|
Mismatched publicPath causing 404 on remoteEntry.js |
When output.publicPath is hardcoded to / instead of auto or a dynamic CDN URL, the browser requests the remote entry from the host domain, resulting in a 404 and container initialization failure. |
| React/ReactDOM version duplication breaking hooks | Loading multiple React instances across federated boundaries triggers ‘Invalid Hook Call’ errors. Enforcing singleton: true and matching requiredVersion ranges in the shared config resolves this. |
| CORS blocking remote container initialization | Cross-origin requests to remoteEntry.js fail if the remote server lacks Access-Control-Allow-Origin headers. Configure CORS middleware or deploy containers under the same origin to bypass browser security restrictions. |
| Over-eager sharing increasing initial bundle size | Setting eager: true on non-critical shared packages forces them into the initial chunk. Use lazy loading defaults and only mark truly critical dependencies (like React) as eager to optimize TTI. |
FAQ #
How do I handle version mismatches between host and remote shared dependencies?
Configure strictVersion: true to throw on mismatch, or use requiredVersion with fallback ranges. The host’s shared version takes precedence unless the remote specifies a higher compatible version.
Can Module Federation work across different domains without CORS issues?
Yes, but the remote server serving remoteEntry.js must explicitly allow cross-origin requests. Configure Access-Control-Allow-Origin: * or restrict to specific host domains.
What is the recommended deployment strategy for independent container updates?
Deploy containers independently with content-hashed filenames. Keep remoteEntry.js stable or use a versioned CDN path to prevent cache poisoning during rolling updates.