Module Federation 2.0: Webpack vs Rspack vs Vite Micro-Frontends 2026
Module Federation 2.0: Webpack vs Rspack vs Vite Micro-Frontends 2026
TL;DR
Module Federation 2.0 (MF2) is the major upgrade to Webpack's most impactful feature, now available for Rspack and Vite via @module-federation/enhanced and @module-federation/vite. The key additions: TypeScript type sharing across remotes, manifest-based dynamic host discovery, and a unified runtime that works across bundlers. For most teams, @module-federation/enhanced on Rspack is now the fastest migration path — Rspack's Webpack-compatible API with 5–10× faster builds makes MF2 a practical choice where Webpack's build times were previously a dealbreaker.
Key Takeaways
- Module Federation 2.0 adds TypeScript types, manifest discovery, and preloading — addressing the three biggest MF1 pain points
- @module-federation/enhanced works with both Webpack 5 and Rspack, making it the universal upgrade path
- @module-federation/vite brings MF support to Vite-based projects (Nuxt, SvelteKit, Astro) without Webpack dependency
- Rspack + MF2 reduces build times by 5–10× vs Webpack 5 while maintaining full compatibility
- single-spa remains the alternative for teams who want runtime framework independence over build-time federation
- Type safety across micro-frontend boundaries is now solved — shared type generation with
dts: true
The Problem Module Federation Solves
In a monolith, importing a component from another part of your app is trivial: import { Button } from '../design-system'. In a micro-frontend architecture, that import crosses team boundaries, deployment pipelines, and bundle boundaries. The traditional solutions were painful:
- iframe composition: Full isolation but terrible UX and no shared state
- npm package publishing: Every change requires a package release and downstream updates
- Build-time monorepo composition: Fast but defeats the independent deployment purpose
Module Federation solves this with runtime composition: Team A can deploy an update to their remote, and Team B's host automatically picks up the new version at runtime — no rebuild, no redeployment.
The first version (Webpack 5's built-in Module Federation) worked, but had significant limitations: no TypeScript support, no dynamic host/remote discovery, and it was Webpack-only. Module Federation 2.0 fixes all three.
Module Federation 2.0: What Changed
Released in 2024 and reaching stability in 2025, MF2 introduces five major improvements over the original:
1. TypeScript Type Sharing
The most-requested feature. MF1 forced teams to manually maintain type stubs or use any everywhere. MF2 automatically generates and shares type declarations across the federation:
// webpack.config.js with @module-federation/enhanced
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'design_system',
exposes: {
'./Button': './src/components/Button',
'./Modal': './src/components/Modal',
},
dts: {
generateTypes: true, // generates .d.ts files for exposed modules
consumeTypes: true, // downloads types from remotes
},
})
]
}
The host automatically downloads type definitions from remotes during build, providing full IDE autocompletion and type checking across team boundaries.
2. Manifest-Based Remote Discovery
MF1 required hardcoding remote URLs in the host configuration, making dynamic environments (staging, preview deploys, A/B testing) awkward. MF2 introduces a manifest approach:
// Host configuration with manifest discovery
new ModuleFederationPlugin({
name: 'host',
remotes: {
// Points to a manifest JSON, not a specific bundle
design_system: 'design_system@https://ds.example.com/mf-manifest.json',
checkout: 'checkout@https://checkout.example.com/mf-manifest.json',
},
})
The manifest file (mf-manifest.json) is generated automatically and includes version info, chunk lists, and shared module data. This enables:
- Zero-config preview environments (just deploy and the manifest updates)
- Gradual rollouts (serve different manifest versions to different users)
- Health checks without loading the full bundle
3. Preloading and Loading Performance
MF1 fetched remotes lazily, causing waterfall loading in deep federated apps. MF2 adds explicit preloading:
import { init, loadRemote, preloadRemotes } from '@module-federation/enhanced/runtime'
// Preload critical remotes during idle time
await preloadRemotes([
{
nameOrAlias: 'checkout',
exposes: ['./CheckoutFlow', './Cart'],
resourceCategory: 'all',
}
])
// Later, this is instant (already in cache)
const { CheckoutFlow } = await loadRemote('checkout/CheckoutFlow')
4. Enhanced Runtime API
The new @module-federation/enhanced/runtime package provides a programmatic API for dynamic federation without build-time configuration:
import { init, loadRemote } from '@module-federation/enhanced/runtime'
// Configure federation at runtime (useful for dynamic environments)
init({
name: 'host',
remotes: [
{
name: 'design_system',
entry: 'https://ds.example.com/mf-manifest.json',
type: 'manifest',
}
],
shared: {
react: { singleton: true, requiredVersion: '>=18' },
'react-dom': { singleton: true, requiredVersion: '>=18' },
}
})
// Dynamic remote loading with full TypeScript support
const { Button } = await loadRemote<typeof import('design_system/Button')>(
'design_system/Button'
)
5. Cross-Bundler Support
The unified @module-federation/enhanced package works with Webpack 5, Rspack, and (via @module-federation/vite) Vite. Teams migrating from Webpack to Rspack can keep their federation configuration unchanged.
@module-federation/enhanced on Rspack: The Speed Story
Rspack is a Rust-based bundler with Webpack-compatible configuration. For Module Federation, the combination is compelling:
| Metric | Webpack 5 + MF1 | Rspack + MF2 | Improvement |
|---|---|---|---|
| Cold build (large app) | 180s | 22s | 8× faster |
| HMR update | 3-8s | 200-400ms | 10-20× faster |
| Type generation | Manual | Automatic | Qualitative |
| Manifest | None | Automatic | Qualitative |
Migration from Webpack 5 + MF1 to Rspack + MF2:
// rspack.config.js — nearly identical to webpack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'checkout',
filename: 'remoteEntry.js',
exposes: {
'./CheckoutFlow': './src/CheckoutFlow',
},
remotes: {
design_system: 'design_system@https://ds.example.com/mf-manifest.json',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
dts: true, // Enable TypeScript support
}),
],
}
The only change from a Webpack config: import from @module-federation/enhanced/rspack instead of @module-federation/enhanced/webpack. The rest of the configuration is identical.
@module-federation/vite: Bringing MF to the Vite Ecosystem
For Vite-based projects (which dominate new React, Vue, and Svelte projects in 2026), @module-federation/vite brings full MF2 support:
npm install @module-federation/vite
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from '@module-federation/vite'
export default defineConfig({
plugins: [
react(),
federation({
name: 'product_catalog',
filename: 'remoteEntry.js',
exposes: {
'./ProductCard': './src/components/ProductCard',
'./ProductList': './src/components/ProductList',
},
remotes: {
design_system: {
type: 'module',
name: 'design_system',
entry: 'https://ds.example.com/mf-manifest.json',
entryGlobalName: 'design_system',
},
},
shared: ['react', 'react-dom'],
}),
],
})
Vite MF uses ES module federation rather than Webpack's CommonJS-based approach, which means better tree-shaking and native browser module support. The trade-off: production builds still require bundling (Vite MF uses Rollup's output), so truly granular federation is more complex than with Webpack/Rspack.
Current limitations of @module-federation/vite:
- No official Vite dev server HMR across remotes (under development)
- ESM-only (no CommonJS remotes)
- Less mature than the Webpack/Rspack variants
For Nuxt 3 and SvelteKit projects wanting MF, @module-federation/vite is the current best path despite these limitations.
Module Federation 2.0 vs single-spa
single-spa takes a fundamentally different approach to micro-frontends. Where Module Federation federates at the build/bundle level, single-spa federates at the framework level — each micro-frontend can use a different JavaScript framework (React, Vue, Angular) as long as it implements the lifecycle interface.
| Aspect | Module Federation 2.0 | single-spa |
|---|---|---|
| Framework requirement | Same bundler required (Webpack/Rspack/Vite) | Any framework or no framework |
| Loading strategy | Build-time dependency resolution | Runtime registration |
| Type safety | ✅ Full TypeScript across boundaries | ✗ Manual typing required |
| Shared dependencies | ✅ Automatic deduplication | ⚠ Manual configuration |
| Learning curve | Medium | High |
| Bundle size overhead | Low (shares runtime) | Medium (each app self-contained) |
| Use case | Monorepo teams splitting a large app | Polyglot orgs with different tech stacks |
When to use Module Federation 2.0: Your team uses the same framework (React, Vue, or Angular) across all micro-frontends, you want TypeScript across boundaries, and you want automatic shared dependency management. This describes ~80% of micro-frontend projects.
When to use single-spa: You have legacy applications (jQuery, AngularJS) that need to coexist with modern React apps, or multiple teams using genuinely different frameworks who can't agree on a common bundler.
For new projects in 2026, Module Federation 2.0 is almost always the right choice. single-spa's flexibility comes at significant complexity cost, and most teams don't need multi-framework support.
Practical Setup: React Host + Remote in 15 Minutes
Here's a minimal working example with Rspack + MF2:
Remote (Design System team):
# design-system/
npm install @rspack/core @module-federation/enhanced
// design-system/rspack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack')
module.exports = {
entry: './src/index',
output: { publicPath: 'auto' },
plugins: [
new ModuleFederationPlugin({
name: 'design_system',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
},
shared: {
react: { singleton: true, requiredVersion: '^18' },
'react-dom': { singleton: true, requiredVersion: '^18' },
},
dts: { generateTypes: true },
}),
],
}
Host (Shell application team):
// shell/rspack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
design_system: 'design_system@http://localhost:3001/mf-manifest.json',
},
shared: {
react: { singleton: true, requiredVersion: '^18' },
'react-dom': { singleton: true, requiredVersion: '^18' },
},
dts: { consumeTypes: true },
}),
],
}
Using the remote in the host:
// shell/src/App.tsx — full TypeScript support automatically
import React, { lazy, Suspense } from 'react'
// Types are automatically downloaded from the remote
const RemoteButton = lazy(() => import('design_system/Button'))
export function App() {
return (
<Suspense fallback={<div>Loading design system...</div>}>
<RemoteButton variant="primary" onClick={() => console.log('clicked')}>
Remote Button ✓
</RemoteButton>
</Suspense>
)
}
The TypeScript types for RemoteButton's props are automatically fetched from the remote during build — no manual type stubs required.
When Module Federation Is Overkill
Not every multi-team codebase needs Module Federation. Before adopting it, consider:
Stick with a monorepo if:
- All teams deploy together (independent deployment isn't needed)
- Your codebase builds in under 60 seconds (no build time problem to solve)
- Teams share most of their state and routing logic
- You have fewer than ~5 teams contributing to the frontend
Consider Module Federation when:
- Different teams need to deploy independently on different schedules
- A shared component library needs to update without coordinating consumer rebuilds
- Build times exceed several minutes due to codebase size
- You want runtime A/B testing or gradual rollouts at the component level
Module Federation adds real operational complexity: you now have distributed systems concerns in your frontend (remote availability, version compatibility, network errors during lazy loading). This complexity is worth it when the deployment independence payoff is real.
Ecosystem Status and npm Data
| Package | Weekly Downloads | Version | Bundler Support |
|---|---|---|---|
| @module-federation/enhanced | ~85K | 0.7.x | Webpack 5, Rspack |
| @module-federation/vite | ~12K | 0.7.x | Vite 5+ |
| @module-federation/runtime | ~220K | 0.7.x | Universal |
| single-spa | ~180K | 5.x | Any |
| webpack (with built-in MF) | ~25M | 5.x | Webpack only |
The @module-federation/runtime package's higher download count (vs the enhanced plugins) reflects teams using the runtime-only API for dynamic federation without build plugin changes.
Methodology
- Package data from npmjs.com and GitHub (March 2026)
- Build time benchmarks from Module Federation organization's official benchmarks
- Rspack vs Webpack comparison from ByteDance engineering blog
- Vite MF limitations from @module-federation/vite GitHub issue tracker
- single-spa comparison from the official micro-frontends documentation at micro-frontends.org
Building a micro-frontend architecture? See PkgPulse's webpack comparison and bundler analysis for live npm health data and download trends.