3041 字
15 分钟
Webpack 5 Module Federation 完全指南:微前端架构的革命性方案

前言#

Module Federation(模块联邦)是 Webpack 5 引入的革命性特性,它允许 JavaScript 应用在运行时动态加载其他应用的代码。这一特性彻底改变了前端应用的组织方式,使得微前端架构的实现变得更加简单和高效。

与传统的代码共享方式(如 npm 包、Git Submodules)相比,Module Federation 的核心优势在于:

  • 运行时集成:无需重新构建即可使用最新版本
  • 独立部署:各应用可以独立开发、测试、发布
  • 零冗余:共享依赖只加载一次

核心概念#

在深入配置之前,先理解 Module Federation 的三个核心概念:

1. Host(宿主应用)#

定义:消费其他应用模块的应用

Host 应用通过配置 remotes 来引用远程应用提供的模块。它是模块的消费者

2. Remote(远程应用)#

定义:提供模块给其他应用使用的应用

Remote 应用通过配置 exposes 来暴露自己的模块供其他应用使用。它是模块的提供者

3. Shared(共享依赖)#

定义:多个应用间共享的公共库(如 React、Vue、Lodash)

通过 shared 配置,可以确保多个应用使用同一份依赖代码,避免重复加载。

基本配置#

Remote 应用配置(模块提供者)#

app1/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1', // 应用名称(全局唯一标识)
filename: 'remoteEntry.js', // 暴露的入口文件
exposes: {
'./Button': './src/Button', // 暴露 Button 组件
'./Header': './src/components/Header', // 暴露 Header 组件
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};

配置说明:

  • name:应用的唯一标识,其他应用将通过这个名称引用
  • filename:生成的入口文件名,默认为 remoteEntry.js
  • exposes:对外暴露的模块映射表
    • key:对外暴露的路径(别名)
    • value:实际的文件路径
  • shared:与其他应用共享的依赖

Host 应用配置(模块消费者)#

app2/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app2',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js', // 远程应用地址
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};

使用远程模块#

app2/src/App.js
import React, { lazy, Suspense } from 'react';
// 动态导入远程模块
const RemoteButton = lazy(() => import('app1/Button'));
function App() {
return (
<Suspense fallback="Loading Button...">
<RemoteButton />
</Suspense>
);
}
export default App;

关键点:

  • 必须使用 React.lazy 或动态 import() 来加载远程模块
  • 使用 Suspense 处理加载状态
  • 导入路径格式:远程应用名/暴露的模块路径

核心参数详解#

1. name(应用标识)#

name: 'app1' // 当前应用的唯一标识

作为全局变量挂载到 window 对象上,其他应用通过这个名称引用。

2. filename(入口文件名)#

filename: 'remoteEntry.js' // 生成的入口文件名,默认 remoteEntry.js

这个文件包含了应用暴露的模块清单和加载逻辑。

3. exposes(暴露模块)#

暴露给其他应用使用的模块映射表。

exposes: {
'./Component': './src/Component', // key 是别名,value 是实际路径
'./utils': './src/utils/index',
}

命名建议:

  • 使用 ./ 开头的相对路径格式
  • 名称简洁明了,便于其他应用理解

4. remotes(远程应用)#

声明要使用的远程应用及其地址。

静态配置#

remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
app2: 'app2@http://localhost:3002/remoteEntry.js',
}

动态配置#

运行时动态获取远程地址:

remotes: {
app1: `promise new Promise(resolve => {
const remoteUrl = getRemoteUrl(); // 运行时获取地址
const script = document.createElement('script');
script.src = remoteUrl;
script.onload = () => resolve(window.app1);
document.head.appendChild(script);
})`
}

动态配置的应用场景:

  • 根据环境变量切换远程地址
  • A/B 测试切换不同版本
  • 灰度发布控制

5. shared(共享依赖)#

配置与其他应用共享的依赖库。

shared: {
react: {
singleton: true, // 只加载一个版本
requiredVersion: '^18.0.0', // 要求的版本范围
eager: false, // 是否立即加载
strictVersion: false, // 是否严格匹配版本
},
lodash: {
requiredVersion: false, // 不限制版本
}
}

shared 配置选项说明#

选项类型说明
singletonboolean确保只有一个共享实例(React 必须为 true)
eagerbooleantrue 时同步加载,false 时异步加载
requiredVersionstring/false指定版本要求,false 表示不限制
strictVersionboolean版本不匹配时是否报错
versionstring当前应用使用的版本

最佳实践:

  • React、Vue 等框架务必设置 singleton: true
  • 大型库(如 Lodash、Moment)设置 eager: false 按需加载
  • 使用 requiredVersion: deps.react 从 package.json 自动读取版本

高级用法#

1. 双向通信(互为 Host 和 Remote)#

两个应用既消费对方的模块,又暴露自己的模块。

// app1 配置
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button', // 提供给 app2
},
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js', // 使用 app2 的模块
},
shared: ['react', 'react-dom'],
})
// app2 配置
new ModuleFederationPlugin({
name: 'app2',
filename: 'remoteEntry.js',
exposes: {
'./Card': './src/Card', // 提供给 app1
},
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js', // 使用 app1 的模块
},
shared: ['react', 'react-dom'],
})

应用场景:

  • 设计系统与业务应用之间的双向依赖
  • 跨团队的模块互用

2. 运行时动态加载#

手动控制模块的加载流程。

// 动态导入函数
function loadComponent(scope, module) {
return async () => {
// 初始化共享作用域
await __webpack_init_sharing__('default');
const container = window[scope];
await container.init(__webpack_share_scopes__.default);
// 获取模块工厂函数
const factory = await container.get(module);
return factory();
};
}
// 使用示例
const MyComponent = React.lazy(loadComponent('app1', './Button'));

适用场景:

  • 需要在特定条件下才加载模块
  • 实现插件化架构
  • 模块懒加载优化

3. 版本管理最佳实践#

从 package.json 自动读取版本信息:

const deps = require('./package.json').dependencies;
module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: {
react: {
singleton: true,
requiredVersion: deps.react, // 从 package.json 读取
version: deps.react,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
version: deps['react-dom'],
}
}
})
]
};

实际应用场景#

1. 组件库共享#

设计系统团队维护统一的组件库,业务团队直接使用。

// design-system/webpack.config.js(组件库)
new ModuleFederationPlugin({
name: 'designSystem',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button',
'./Input': './src/Input',
'./Table': './src/Table',
'./DatePicker': './src/DatePicker',
},
shared: ['react', 'react-dom'],
})
// 业务应用使用
import Button from 'designSystem/Button';
import Table from 'designSystem/Table';
function App() {
return (
<div>
<Button>点击</Button>
<Table data={data} />
</div>
);
}

优势:

  • 组件库更新后,业务应用无需重新构建
  • 避免版本不一致问题
  • 统一的设计规范

2. 微前端架构#

主应用作为容器,加载各个子应用。

// 主应用配置
new ModuleFederationPlugin({
name: 'mainApp',
remotes: {
dashboard: 'dashboard@http://cdn.com/dashboard/remoteEntry.js',
userCenter: 'userCenter@http://cdn.com/user/remoteEntry.js',
orderSystem: 'orderSystem@http://cdn.com/order/remoteEntry.js',
},
shared: ['react', 'react-dom', 'react-router-dom'],
})
// 路由配置
import { lazy } from 'react';
const Dashboard = lazy(() => import('dashboard/App'));
const UserCenter = lazy(() => import('userCenter/App'));
const OrderSystem = lazy(() => import('orderSystem/App'));
function App() {
return (
<Router>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/user" element={<UserCenter />} />
<Route path="/order" element={<OrderSystem />} />
</Routes>
</Router>
);
}

3. A/B 测试#

根据实验分组动态加载不同版本的功能模块。

const experimentGroup = getUserExperimentGroup(); // 获取用户分组
const remotes = {
feature: experimentGroup === 'A'
? 'featureA@http://cdn.com/featureA/remoteEntry.js'
: 'featureB@http://cdn.com/featureB/remoteEntry.js'
};
new ModuleFederationPlugin({
name: 'app',
remotes,
shared: ['react', 'react-dom'],
})

4. 国际化独立部署#

不同语言版本独立部署和维护。

const locale = getUserLocale(); // 获取用户语言偏好
const remotes = {
i18n: `i18n_${locale}@http://cdn.com/i18n/${locale}/remoteEntry.js`
};

核心优势#

Module Federation 相比传统方案的优势:

✅ 真正的运行时集成#

  • 无需重新构建主应用即可使用最新版本
  • 远程模块更新后立即生效
  • 支持热更新和灰度发布

✅ 独立部署#

  • 各应用独立开发、测试、发版
  • 团队间解耦,提高开发效率
  • 降低发布风险

✅ 代码共享#

  • 避免重复打包相同的依赖
  • 共享库只加载一次,节省带宽
  • 减小总体包体积

✅ 灵活的版本控制#

  • 支持语义化版本管理
  • 可配置版本兼容策略
  • 自动处理版本冲突

✅ 按需加载#

  • 动态加载远程模块
  • 提升首屏加载性能
  • 优化资源利用

注意事项与最佳实践#

1. TypeScript 类型安全#

TypeScript 环境下需要手动声明远程模块的类型。

src/types/remote-modules.d.ts
declare module 'app1/Button' {
const Button: React.FC<{
onClick?: () => void;
children: React.ReactNode;
}>;
export default Button;
}
declare module 'app1/Header' {
interface HeaderProps {
title: string;
logo?: string;
}
const Header: React.FC<HeaderProps>;
export default Header;
}

自动化方案: 使用 @module-federation/typescript 插件自动生成类型文件。

Terminal window
npm install @module-federation/typescript

2. 错误处理#

远程模块加载可能失败(网络问题、模块不存在等),需要完善的错误处理机制。

import React, { lazy, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
// 带错误处理的远程组件
const RemoteButton = lazy(() =>
import('app1/Button').catch(() => {
console.error('Failed to load remote button');
// 返回降级组件
return { default: () => <button>本地备用按钮</button> };
})
);
function App() {
return (
<ErrorBoundary
fallback={<div>组件加载失败,请刷新页面</div>}
onError={(error) => {
// 上报错误
reportError(error);
}}
>
<Suspense fallback={<div>加载中...</div>}>
<RemoteButton />
</Suspense>
</ErrorBoundary>
);
}

错误处理最佳实践:

  • 使用 ErrorBoundary 捕获加载错误
  • 提供降级方案(Fallback UI)
  • 记录错误日志并上报监控系统
  • 设置合理的超时时间

3. 环境变量管理#

不同环境使用不同的 remote 地址。

webpack.config.js
const getRemoteUrl = () => {
switch (process.env.NODE_ENV) {
case 'production':
return 'https://cdn.example.com/app1/remoteEntry.js';
case 'staging':
return 'https://staging.example.com/app1/remoteEntry.js';
default:
return 'http://localhost:3001/remoteEntry.js';
}
};
module.exports = {
plugins: [
new ModuleFederationPlugin({
remotes: {
app1: `app1@${getRemoteUrl()}`,
},
}),
],
};

4. 构建性能优化#

合理配置 shared 避免打包体积过大。

// 推荐配置
shared: {
// 框架库:必须单例
react: {
singleton: true,
requiredVersion: deps.react,
eager: false, // 异步加载
},
// 工具库:按需加载
lodash: {
singleton: false,
eager: false,
requiredVersion: false,
},
// 不共享太大的库
// moment: false, // 明确不共享
}

优化建议:

  • 只共享必要的依赖(React、Vue 等框架)
  • 小型工具库可以不共享,避免版本冲突
  • 使用 eager: false 实现按需加载
  • 使用 Webpack Bundle Analyzer 分析包体积

5. 浏览器兼容性#

Module Federation 基于动态 import 和 ES Modules,需要现代浏览器支持。

最低浏览器版本要求:

  • Chrome 63+
  • Firefox 67+
  • Safari 11.1+
  • Edge 79+

兼容性方案:

// 检测浏览器支持
if (typeof window !== 'undefined' && !('import' in document.createElement('script'))) {
console.error('浏览器不支持动态导入');
// 使用降级方案
}

6. 安全性考虑#

动态加载远程代码存在安全风险,需要做好防护。

安全措施:

  • 使用 HTTPS 传输 remoteEntry.js
  • 验证远程资源的完整性(Subresource Integrity)
  • 限制允许加载的远程源(CSP 策略)
  • 定期审计远程模块的依赖
<!-- 使用 SRI 验证完整性 -->
<script
src="https://cdn.example.com/app1/remoteEntry.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>

常见问题(FAQ)#

Q1: 如何处理远程模块加载失败?#

使用 ErrorBoundary + Suspense 组合处理:

<ErrorBoundary fallback={<ErrorUI />}>
<Suspense fallback={<Loading />}>
<RemoteComponent />
</Suspense>
</ErrorBoundary>

Q2: 如何在开发环境调试?#

配置 devServer 允许跨域:

devServer: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
}

Q3: 如何优化加载速度?#

优化策略:

  • 使用 CDN 部署 remoteEntry.js
  • 配置合理的 shared 策略减少重复加载
  • 使用 prefetch/preload 预加载关键模块
  • 启用 HTTP/2 支持多路复用
  • 使用 Service Worker 缓存静态资源
<!-- 预加载远程入口文件 -->
<link rel="prefetch" href="https://cdn.example.com/app1/remoteEntry.js" />

Q4: 如何实现模块的版本回滚?#

方案一:多版本部署

const version = getFeatureVersion(); // 从配置中心获取
remotes: {
app1: `app1@https://cdn.com/app1/${version}/remoteEntry.js`
}

方案二:使用 CDN 版本管理

https://cdn.com/app1/v1.2.3/remoteEntry.js
https://cdn.com/app1/v1.2.2/remoteEntry.js (回滚版本)

Q5: 如何监控远程模块的性能?#

使用 Performance API 监控加载时间:

const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('remoteEntry.js')) {
console.log('Remote module load time:', entry.duration);
// 上报到监控系统
reportPerformance({
module: entry.name,
duration: entry.duration,
});
}
}
});
observer.observe({ entryTypes: ['resource'] });

总结#

Module Federation 为前端应用的组织方式带来了革命性的变化,它让微前端架构的实现变得更加优雅和高效。

核心价值:

  • 真正的运行时集成,无需重新构建
  • 独立部署,团队高度自治
  • 共享依赖,避免冗余加载
  • 灵活的版本管理策略

适用场景:

  • 大型企业级应用的微前端架构
  • 跨团队的组件库共享
  • 多应用间的功能复用
  • A/B 测试和灰度发布

实施建议:

  1. 从小规模试点开始,逐步推广
  2. 建立完善的监控和错误处理机制
  3. 制定统一的共享依赖版本规范
  4. 做好文档和团队培训
  5. 持续优化性能和用户体验

Module Federation 不仅仅是一个技术特性,更是一种新的应用架构思想。合理使用它,能够显著提升大型前端项目的开发效率和可维护性。

Webpack 5 Module Federation 完全指南:微前端架构的革命性方案
https://fuwari.vercel.app/posts/module-federation/
作者
Lorem Ipsum
发布于
2025-11-10
许可协议
CC BY-NC-SA 4.0