在前端工程化领域,代码复用和应用集成一直是痛点。传统的 npm 包共享 或 iframe 嵌套 方案,要么面临版本冲突,要么存在性能和体验瓶颈。Webpack 5 引入的 模块联邦(Module Federation) 彻底改变了这一现状,它允许 多个独立构建的应用共享代码,实现真正的“跨应用模块复用”,为微前端、大型应用拆分提供了原生支持。
业务场景分析:
现在有两个业务模块:电商管理平台和广告管理平台,代码是两个不同的团队维护。现在我们需要在广告模块,给某个活动配置一个商品。需要在广告项目中点击出现一个弹窗,弹出商品列表页并选择。
以前的思路,是把电商模块的商品选择组件复制到广告模块,或者把商品选择组件封装到公共组件库.(内部npm)
这样我们需要面对一些问题:代码复用率低;组件有改动需要改多次,并且需要重新build;npm的调试不够便利等等。
一、模块联邦解决了什么问题?
在模块联邦出现之前,前端应用间的代码共享主要有以下方案,均存在明显缺陷:
| 方案 | 原理 | 缺点 |
|---|---|---|
| npm 包 | 抽离公共代码为 npm 包 | 版本锁定,无法实时更新,需重新安装构建 |
| Git Submodule | 子仓库嵌入主仓库 | 依赖管理复杂,构建效率低 |
| iframe | 页面嵌套 | 性能差,通信复杂,样式隔离问题 |
| monorepo | 所有的业务代码、公共组件库代码都放在同一个仓库 | 仅仅在编译的时候共用了代码,打出生产包内部依然有需要重复加载的模块 |
模块联邦的创新点:
- 运行时动态加载:远程模块在运行时按需加载,无需编译时依赖。
- 共享依赖自动协商:不同应用共享同一依赖时,自动选择合适版本,避免重复加载。
- 双向共享能力:组件可以作为“宿主”加载远程模块,也可以作为“远程”暴露模块给其他应用。
二、核心概念与工作原理
1. 核心术语
- 宿主应用(Host):加载并使用远程模块的应用(如主应用)。
- 远程应用(Remote):暴露模块供其他应用使用的应用(如微应用)。
- 远程入口文件(Remote Entry):远程应用生成的特殊文件,包含模块暴露信息(通常命名为
remoteEntry.js)。 - 共享作用域(Shared Scope):所有应用共享的依赖池,用于协商依赖版本。
2. 工作流程
- 远程应用暴露模块:通过
exposes配置声明要共享的模块,Webpack 生成remoteEntry.js。 - 宿主应用加载远程入口:通过
remotes配置指定远程应用的remoteEntry.jsURL。 - 运行时动态加载:宿主应用在需要时,通过
import('remote/app/Module')动态加载远程模块。 - 依赖共享协商:若宿主和远程应用共享同一依赖(如 React),共享作用域会自动选择一个版本加载,避免重复。
三、核心配置详解
模块联邦的能力通过 ModuleFederationPlugin 插件实现,以下是关键配置项的详细说明:
基础配置模板
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 1. 应用标识(必填)
name: 'app1', // 全局唯一名称,作为远程应用时会挂载到 window[app1]
// 2. 远程应用配置(暴露模块)
filename: 'remoteEntry.js', // 生成的远程入口文件名(宿主通过此文件加载模块)
exposes: {
'./Button': './src/components/Button', // 暴露路径: 本地模块路径
'./utils': './src/utils',
},
// 3. 宿主应用配置(加载远程模块)
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js', // 别名: "远程应用名@远程入口URL"
},
// 4. 共享依赖配置(可选)
shared: {
react: {
singleton: true, // 强制单例(同一依赖只加载一次)
requiredVersion: '^17.0.0', // 要求的版本范围
eager: false, // 是否在初始化时立即加载(默认 false,按需加载)
},
'react-dom': { singleton: true },
},
}),
],
};
关键配置解析
1. name
应用的唯一标识,当作为远程应用时,会将模块暴露到 window[name] 全局变量(如 window.app2)。宿主应用通过此变量访问远程模块。
2. exposes(远程应用)
声明要暴露的模块,格式为 { 暴露路径: 本地模块路径 }。
- 暴露路径:宿主应用加载时使用的路径(如
import('app2/Button'))。 - 本地模块路径:相对于 Webpack 配置文件的模块路径。
3. remotes(宿主应用)
声明要加载的远程应用,格式为 { 别名: "远程应用名@远程入口URL" }。
- 别名:宿主应用中引用远程模块的前缀(如
import('app2/Button')中的app2)。 - 远程入口 URL:远程应用
remoteEntry.js的访问地址(必须是绝对路径)。
4. shared(共享依赖)
解决多应用间依赖共享问题,避免重复加载(如 React、Lodash 等)。
singleton: true:强制依赖单例,若多个应用请求同一依赖,只加载一次。requiredVersion:指定依赖版本范围,若版本不兼容会报错(可省略,自动协商)。eager: true:依赖在应用初始化时立即加载,而非按需加载(适合基础库)。
对于技术栈统一的微前端项目,共享依赖会极大地提升性能。 子项目本身甚至都不需要安装太多依赖,直接去引用主项目的共享依赖;多个项目使用同一个公共库,如Vue、ant-design-vue等,都使用同一个。
六、局限性与注意事项
1. 依赖版本冲突
若共享依赖版本不兼容(如 React 16 和 17),Webpack 会加载多个版本,可能导致运行时错误。解决方案:
- 统一依赖版本,或使用
requiredVersion严格限制版本范围。
2. 生产环境部署
- 确保远程应用的
publicPath是可访问的绝对路径(如 CDN 地址)。 - 配置 CORS 允许跨域请求,远程应用服务器需设置
Access-Control-Allow-Origin。(在绝大部分实际场景下,微服务的应用都部署在同一个目录下,不会出现跨域问题)
官网例子运行: