微前端架构的落地过程中,依赖共享和独立部署经常成为一对矛盾。借助 iframe 解决隔离问题虽然直接,但随之而来的样式穿透、路由同步和加载性能开销会让项目逐渐陷入维护困境。Webpack 5 引入的模块联邦(Module Federation)提供了一种新的思路:让多个独立构建的应用在运行时像使用本地模块一样加载远程代码,从构建层面同时满足团队自治与代码复用。模块联邦并不要求所有子应用使用同一个构建工具或同一个版本,它通过一个统一的远程入口文件(remoteEntry.js)暴露模块,再由宿主应用在运行时动态消费这些模块。

一、模块联邦要解决的核心问题
传统的微前端实现往往依赖 iframe 或 single-spa 这类外部框架。iframe 虽然提供了天然隔离,但跨 iframe 通信、样式继承、路由同步以及加载性能都是长期痛点;single-spa 解决了生命周期编排,却无法直接处理公共依赖的复用,往往需要额外配置 externals 或手动注入公共库。模块联邦直接从构建产物层面切入:每个应用独立打包,但可以声明自己需要消费的远程模块以及需要共享的依赖。构建完成后,远程应用会生成一个 remoteEntry.js 文件,里面包含了模块暴露的映射和运行时加载逻辑。
模块联邦的关键能力体现在两个方面。第一是远程模块的运行时加载:宿主应用不需要在构建时获取远程应用的完整代码,只需要在运行时加载远端入口文件,再通过 import() 动态获取模块,这为独立部署和灰度发布提供了基础。第二是共享依赖的版本协商:通过 shared 配置,多个应用可以声明共用的依赖(如 React、ReactDOM、lodash),模块联邦运行时根据版本要求自动选择加载兼容的版本,避免同一个库被重复打包,也避免多个 React 实例导致 Hooks 状态失效等问题。
相比传统的 NPM 包共享方式,模块联邦不再要求所有团队统一升级依赖版本。某个远程应用升级了 React 小版本,只要声明 requiredVersion 满足宿主要求,运行时就可以使用宿主提供的 React 实例,而远程应用构建时使用的是自己的版本。这种灵活性大幅降低了微前端架构中的版本耦合。
二、ModuleFederationPlugin 配置与远程模块暴露
模块联邦的核心是 Webpack 5 内置的 ModuleFederationPlugin。一个典型的远程应用(remote)需要配置 name、filename、exposes 和 shared。其中 name 是全局容器名称,filename 是远程入口文件名,exposes 用于声明对外暴露的模块路径,shared 用于声明需要共享的依赖。
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button'
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
})
]
};
上面的配置表示 remote_app 会对外暴露一个路径为 ./Button 的模块,实际指向本地 src/components/Button 文件。构建后会生成 remoteEntry.js,该文件包含模块加载逻辑和依赖共享信息。当多个远程模块同时存在时,每个应用都会生成自己的远程入口文件,它们之间互不干扰。
宿主应用(host)的配置则通过 remotes 字段声明需要消费的远程模块。一个常见的宿主配置如下:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
})
]
};
在业务代码中,宿主应用可以直接使用动态导入语法加载远程模块:const Button = await import('remote_app/Button');。这种写法让远程代码的加载看起来和本地模块一致,开发者无需关心底层网络请求和容器初始化。需要注意的是,singleton: true 表示该依赖在全局只能存在一个实例。对于 React 这类库,必须设置为单例,否则远程模块加载后可能出现多个 React 副本,导致 Hooks 状态异常或 Context 失效。
共享依赖的版本协商遵循 Webpack 的 semver 规则。宿主应用会优先提供自己已加载的共享模块,如果版本满足远程模块的 requiredVersion,则复用;如果不满足,则加载远程模块自带的版本。这种机制在保证灵活性的同时,也会带来潜在的重复加载问题,需要定期审查共享配置。
三、运行时动态加载远程模块
前面示例中的 remotes 配置属于静态声明,要求构建时明确知道远程入口的 URL。但在实际生产环境中,远程应用可能部署在不同的域名、使用不同的版本,甚至由配置中心动态下发地址。此时需要采用运行时动态加载远程模块的能力。Webpack 5 允许先通过脚本标签加载远程入口文件,再使用 import() 动态消费模块。
Webpack 5Module Federation微前端修改时间:2026-08-25 13:24:10