<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/module-federation-2-webpack-rspack-vite-micro-frontends-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/module-federation-2-webpack-rspack-vite-micro-frontends-2026/raw.md -->
<!-- Source path: content/guides/module-federation-2-webpack-rspack-vite-micro-frontends-2026.mdx -->

---
og_image: "/images/guides/module-federation-2-webpack-rspack-vite-micro-frontends-2026.webp"
title: "Module Federation 2.0: webpack vs Rspack vs Vite 2026"
description: "Module Federation 2.0 compared across webpack, Rspack, and Vite in 2026. Type-safe remotes, dynamic federation, and micro-frontend architecture decisions."
date: "2026-03-09"
tier: 2
authors: ["team"]
tags: ["webpack", "micro-frontends", "javascript", "architecture", "rspack", "vite"]
---

## 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:

```javascript
// 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:

```javascript
// 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:

```javascript
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:

```typescript
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:

```javascript
// 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:

```bash
npm install @module-federation/vite
```

```typescript
// 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):**

```bash
# design-system/
npm install @rspack/core @module-federation/enhanced
```

```javascript
// 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):**

```javascript
// 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:**

```typescript
// 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

---

One underappreciated aspect of Module Federation 2.0's type safety feature: it generates TypeScript declarations for remote module exports at build time via a manifest. Consuming applications get type-checked remote imports without manually maintaining separate type packages. This closes the main pain point of the original Module Federation 1.0 implementation, where consuming a remote component required either `any` casts or hand-maintained `.d.ts` files that drifted from the actual exported API.

*Building a micro-frontend architecture? See [PkgPulse's webpack comparison](https://www.pkgpulse.com/compare/webpack-vs-rspack) and [bundler analysis](https://www.pkgpulse.com/packages/webpack) for live npm health data and download trends.*

*See also: [Vite vs webpack](/compare/vite-vs-webpack) and [Rspack vs webpack](/compare/rspack-vs-webpack), [Micro-Frontends in 2026: Solution or Over-Engineering?](/guides/micro-frontends-2026-solution-or-overengineering).*
