一、什么是 Module Federation?
Module Federation(模块联邦)是 Webpack 5 提供的一项革命性功能,它允许 JavaScript 应用在运行时动态加载另一个应用的代码模块。简单来说,它让微前端架构从”各子应用互不干涉”的隔离模式,进化到”各应用之间可以共享模块”的协作模式。

想象一下这样的场景:你的团队维护着三个独立的前端应用——电商官网、商家后台和用户中心。每个应用都有自己的 React 组件库、工具函数和业务逻辑。过去,共享代码需要发布 npm 包,每个应用升级依赖,重新构建部署。而有了 Module Federation,应用 A 可以直接在运行时拉取应用 B 的某个组件,无需构建、无需发版。
Module Federation 的核心价值在于:
- 运行时依赖共享:在浏览器端动态加载远程模块,而非构建时
- 独立部署:每个微应用可以独立开发、测试、部署,互不阻塞
- 代码复用零成本:无需重复打包公共依赖,减少总体体积
- 渐进式迁移:可以逐步将旧应用迁移到新架构,而非全量重写
它与传统的微前端方案(如 qiankun、single-spa)最大的区别在于:Module Federation 是 Webpack 层面的原生能力,不需要额外的框架或运行时容器。这不仅减少了第三方依赖,也让构建和调试更加直观。
二、核心概念解析
在开始编码之前,先理解 Module Federation 的几个关键概念:
2.1 Host(宿主)与 Remote(远程)
| 角色 | 说明 | 示例 |
|---|---|---|
| Host | 消费远程模块的应用,负责承载整体页面 | 主框架应用、微前端基座 |
| Remote | 暴露出模块供其他应用消费的应用 | 商品详情页、购物车组件 |
| Bidirectional | 一个应用同时是 Host 和 Remote | 应用既暴露组件又消费其他应用的模块 |
2.2 exposes 与 remotes
1 | exposes |
定义当前应用想要暴露给其他应用的模块;
1 | remotes |
定义当前应用要消费哪些远程应用暴露的模块。这是 Module Federation 配置中最重要的两个字段。
2.3 shared(共享依赖)
1 | shared |
字段用于指定哪些 npm 包应该在多个应用之间共享。Webpack 会确保共享的包在全局只加载一次,避免同一个库(如 React)被重复加载导致应用崩溃。
三、入门实战:搭建第一个模块联邦应用
我们通过一个实际的例子来理解 Module Federation。假设我们要搭建一个电商平台的微前端架构:一个主框架应用(Host)负责导航和布局,商品应用(Remote)负责商品列表和详情。
3.1 项目结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 micro-ecommerce/
├── host/ # 主框架应用(Host)
│ ├── webpack.config.js
│ ├── package.json
│ └── src/
│ ├── App.jsx
│ ├── bootstrap.jsx
│ └── index.js
└── remote-products/ # 商品应用(Remote)
├── webpack.config.js
├── package.json
└── src/
├── App.jsx
├── bootstrap.jsx
├── index.js
└── components/
├── ProductList.jsx
└── ProductDetail.jsx
3.2 商品应用(Remote)配置
首先,在商品应用中配置 Module Federation,将
1 | ProductList |
和
1 | ProductDetail |
组件暴露出去:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 // remote-products/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
devServer: { port: 3001 },
plugins: [
new ModuleFederationPlugin({
name: 'products',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail',
'./ProductService': './src/services/productService',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
几点说明:
-
1name: 'products'
:远程应用的唯一标识,Host 通过这个名字引用它
-
1filename: 'remoteEntry.js'
:Webpack 生成的入口文件,其中包含了所有暴露模块的加载信息
-
1exposes
:定义暴露模块的路径映射,键(如
1'./ProductList')是模块名,值是本地文件路径
-
1shared
:指定 React 为单例共享依赖,避免多实例冲突
3.3 主框架应用(Host)配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 // host/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
mode: 'development',
devServer: { port: 3000 },
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
products: 'products@http://localhost:3001/remoteEntry.js',
},
shared: {
react: {
singleton: true,
requiredVersion: deps.react,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
},
},
}),
],
};
这里的关键是
1 | remotes |
配置:
-
1products
:对应 Remote 应用的
1name -
1products@http://localhost:3001/remoteEntry.js
:格式为
1应用名@远程入口文件URL
四、在 Host 中消费远程模块
配置完成后,在主框架中通过动态 import 加载远程组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 // host/src/App.jsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
// 使用 React.lazy 懒加载远程模块
const ProductList = lazy(() => import('products/ProductList'));
const ProductDetail = lazy(() => import('products/ProductDetail'));
function App() {
return (
<BrowserRouter>
<div style={{ display: 'flex', minHeight: '100vh' }}>
{/* 主框架提供的导航 */}
<nav style={{ width: 220, background: '#f5f5f5', padding: 20 }}>
<h3>电商管理系统</h3>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/products">商品管理</Link></li>
<li><Link to="/orders">订单管理(其他微应用)</Link></li>
</ul>
</nav>
{/* 内容区域显示远程模块 */}
<main style={{ flex: 1, padding: 24 }}>
<Suspense fallback={<div>正在加载商品模块...</div>}>
<Routes>
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
</Suspense>
</main>
</div>
</BrowserRouter>
);
}
export default App;
当用户访问
1 | /products |
路径时,Webpack 会:
- 检查
1products/remoteEntry.js
是否已加载,如果没有则发送网络请求
- 从 remoteEntry.js 中获取
1ProductList
模块的加载地址
- 异步加载该模块的 chunk 文件
- 检查共享依赖(React)是否已加载,若无则从远程共享加载
- 最后渲染组件到页面
这一切发生在运行时,用户在浏览器中完全无感知。整个加载过程是懒加载 + 按需进行的,不会影响首屏性能。
五、共享依赖:避免”两次加载 React”的悲剧
如果不配置
1 | shared |
,每个微应用会打包自己版本的 React。想象一下:主框架加载 React 18.2.0,商品应用也加载 React 18.2.0——页面中会有两个 React 实例,导致
1 | Hooks |
无法工作、Context 数据丢失等问题。
正确配置共享依赖是关键:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 // 每个微应用的 webpack.config.js
new ModuleFederationPlugin({
// ...
shared: {
react: {
singleton: true, // 强制单例模式
requiredVersion: '^18.0.0', // 版本约束
eager: false, // 是否立即加载(首屏需要的设为 true)
strictVersion: false, // 版本不匹配时是否报错
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
},
'react-router-dom': {
singleton: true,
requiredVersion: '^6.0.0',
},
// 工具库可以宽松共享
lodash: {
singleton: false, // 不强制单例
requiredVersion: '^4.0.0',
},
},
});
关于
1 | eager |
属性的使用场景:共享依赖的加载默认是异步的,因为 remoteEntry.js 本身是异步加载。但如果某个共享库需要在首屏就可用(比如主框架本身的入口文件就依赖了 React),就需要设置
1 | eager: true |
,让 Webpack 在构建时就将该依赖打包进主入口。
六、高级用法:双向联邦与版本兼容
6.1 双向联邦(Bidirectional Federation)
每个应用既可以是 Host 也可以是 Remote,从而实现真正的对等协作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 // 商城应用的 webpack.config.js
new ModuleFederationPlugin({
name: 'shop',
// 即消费其他应用的模块
remotes: {
cart: 'cart@http://localhost:3002/remoteEntry.js',
user: 'user@http://localhost:3003/remoteEntry.js',
cms: 'cms@http://localhost:3004/remoteEntry.js',
},
// 也暴露自己的模块
exposes: {
'./ProductCard': './src/components/ProductCard',
'./ProductAPI': './src/api/productApi',
},
shared: { ... },
});
这种模式特别适合大型平台的横向扩展,比如在一个 SaaS 应用中,不同的功能模块(订单、用户、商品、营销)可以互相引用对方的组件和数据服务。
6.2 版本兼容策略
当微应用由不同团队维护时,版本同步是个挑战。Module Federation 提供了几种策略:
| 策略 | 配置 | 适用场景 | ||
|---|---|---|---|---|
| 严格版本 |
|
核心依赖必须完全匹配,否则报错 | ||
| 宽松版本 |
|
使用最高匹配版本,降级到兼容版本 | ||
| 版本回退 |
|
不检查版本,接受任何版本(有风险) |
最佳实践:对核心框架(React/Vue/Angular)使用严格模式,对工具库(lodash/dayjs)使用宽松模式。同时在 CI 中增加依赖版本检查流水线,防止不兼容的版本进入生产环境。
七、生产环境部署注意
7.1 跨域问题
在开发环境下,不同微应用运行在不同端口,需要配置 CORS:
1
2
3
4
5
6
7
8 // webpack devServer 配置
devServer: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
},
}
生产环境下,如果微应用部署在相同域名的不同路径下(如
1 | /app/products/ |
、
1 | /app/cart/ |
),则不需要 CORS。如果部署在不同域名,务必配置正确的 CORS 头。
7.2 部署策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 # 产品推荐:独立部署 + Nginx 反向代理
# 主框架部署到 /
# 商品微应用部署到 /app/products/
# 购物车微应用部署到 /app/cart/
# Nginx 配置示例
server {
listen 80;
server_name ecommerce.example.com;
# 主框架
location / {
proxy_pass http://host-app:3000;
}
# 商品微应用(所有路径都代理到微应用入口)
location /app/products/ {
proxy_pass http://products-app:3001/;
}
# 购物车微应用
location /app/cart/ {
proxy_pass http://cart-app:3002/;
}
}
关键点:remoteEntry.js 需要配置正确的
1 | publicPath |
,确保加载路径在代理后仍然正确。
1
2
3
4 // 每个微应用的 webpack 配置
output: {
publicPath: '/app/products/', // 必须与实际部署路径一致
},
7.3 缓存策略
remoteEntry.js 包含模块加载映射表,如果被浏览器缓存,新的部署可能无法被识别。建议:
- 给 remoteEntry.js 配置较短的
1max-age
(如 5 分钟)
- 使用 Webpack 5 的
1version
参数命名入口文件
- 在 CI/CD 中实现滚动更新,保证新旧版本共存
八、与主流框架的集成
8.1 React + Module Federation
React 是目前与 Module Federation 配合最成熟的框架。使用
1 | React.lazy + Suspense |
可以优雅地加载远程组件。结合 React Router v6 实现微应用级别的路由分配非常自然,代码已在前面展示。
8.2 Vue 3 + Module Federation
Vue 3 也可以使用 Module Federation,但需要额外配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 // Vue 3 远程应用配置
new ModuleFederationPlugin({
name: 'vue_remote',
filename: 'remoteEntry.js',
exposes: {
'./VueComponent': './src/components/MyComponent.vue',
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.0.0',
// Vue 3 需要显式指定这个共享作用域
shareScope: 'default',
},
},
});
// Host 中加载 Vue 远程组件
// 需要封装一层,因为 React.lazy 只支持 default export
const VueComponentWrapper = React.lazy(() =>
import('vue_remote/VueComponent').then(module => ({
default: module.VueComponent,
}))
);
8.3 Angular + Module Federation
Angular 推荐使用
1 | @angular-architects/module-federation |
插件来简化配置。Angular 的自定义元素(Custom Elements)方案也适合作为跨框架微前端的载体。
九、常见问题与调试技巧
9.1 Shared module 版本冲突
现象:控制台报错
1 | Shared module version mismatch |
解决方法:检查所有微应用的
1 | shared |
配置中的版本范围是否兼容。可以通过在浏览器中访问
1 | webpack/container/reference/<应用名> |
查看加载的共享依赖信息。
9.2 远程模块加载失败
现象:
1 | Cannot find module 'products/ProductList' |
调试步骤:
- 确认远程应用的
1remoteEntry.js
可以正常访问(浏览器直接打开 URL)
- 检查
1remotes
中的 URL 拼写和端口号
- 查看 network 面板确认请求是否发出以及响应状态
- 确认暴露的模块文件路径正确(使用绝对路径或相对于项目根目录的路径)
9.3 样式隔离
Module Federation 不提供内置的样式隔离。推荐方案:
- CSS Modules:每个微应用使用 CSS Modules 自动生成局部作用域类名
- CSS-in-JS:styled-components、Emotion 等方案天然隔离样式
- shadow DOM:适合独立微应用组件的隔离(但需要注意事件穿透问题)
- 命名约定:BEM 命名法,每个微应用使用统一前缀
十、总结与最佳实践
Module Federation 是 Webpack 5 最具变革性的功能之一,它为微前端架构提供了一种优雅、高性能的解决方案。通过本文的实战指南,你应该已经掌握了从零搭建模块联邦应用的能力。以下是几条核心最佳实践:
- 共享依赖必须精细配置:核心框架(React/Vue/Angular)使用
1singleton: true + strictVersion: true
,工具库使用宽松模式
- remoteEntry.js 的缓存策略要谨慎:太长的缓存时间会导致部署后无法更新,太短则影响性能。建议 5-15 分钟的 max-age
- publicPath 必须和实际部署路径一致:这是最容易踩坑的地方之一
- 优先使用 React.lazy 懒加载远程模块:避免首屏加载过多远程依赖影响性能
- 统一的技术栈会大幅降低复杂度:虽然 Module Federation 支持跨框架,但最佳实践是统一使用一个框架(如微应用全部使用 React)
- CI/CD 流水线中加入兼容性检查:部署前自动验证所有微应用的共享依赖版本是否兼容
随着微前端架构的普及,Module Federation 正在成为现代前端工程化的标配能力。无论你是正在构建大型企业级应用,还是在做微服务架构的前端落地,掌握 Module Federation 都能让你的项目架构更加灵活和可扩展。
汤不热吧