Module Federation 实战拆解:远程模块、依赖共享与 MF 2.0
微前端绕不开 Module Federation(MF)。它和 qiankun 那类方案常被放在一起比,但两者其实抽象层级不同:qiankun 管的是「应用怎么隔离地拼在一起」,MF 管的是「模块怎么跨应用加载与共享」。
这篇不空谈:我搭了两个最小应用(host / remote),用 webpack 5.111.0 + @module-federation/enhanced 2.9.0 真跑了一遍构建,把产物、stats、manifest 全抓出来给你看——MF 这套东西「到底生成了什么」,看完就有底了。
声明:本文配置与构建产物均为本机真实运行结果(含
mf-manifest.json原文)。运行时(浏览器里加载远程模块)未实测,涉及运行时的部分我标明了依据,不编造效果。
一、一句话理解 MF
把「另一个独立构建、独立部署的应用」里的模块,当成本地模块一样 import 进来,并且让它们共用同一份依赖(如 React、dayjs)。
关键点:host 的构建产物里并没有 remote 的代码——只有一个「远程地址」。真正的加载发生在运行期。
二、五个核心概念(一次记全)
| 概念 | 属于谁 | 作用 |
|---|---|---|
exposes | remote | 我对外提供哪些模块('./Button': './src/Button.js') |
remotes | host | 我消费哪些远程(remote_app@http://localhost:3002/remoteEntry.js) |
shared | 两边 | 哪些依赖要跨应用共享(避免重复打包/双实例) |
filename | remote | 远程入口文件名,约定 remoteEntry.js |
uniqueName | 两边 | 运行时全局容器名,避免多个应用互相踩 |
再加一个现代概念:manifest(MF 2.0 默认产出 mf-manifest.json)——把「暴露了什么、共享了什么、远程是谁」写成机器可读的元数据,运行时可以基于它加载,而不只是硬编码 remoteEntry.js 地址。
三、最小可跑配置(真实代码)
remote 端(提供方):
// remote/webpack.config.cjs
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
module.exports = {
mode: 'development',
output: { publicPath: 'http://localhost:3002/', uniqueName: 'remote_app', clean: true },
plugins: [
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: { './Button': './src/Button.js' }, // 对外提供
shared: { dayjs: { singleton: true, requiredVersion: '^1.11.0' } },
}),
],
};
host 端(消费方):
// host/webpack.config.cjs
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: { remote_app: 'remote_app@http://localhost:3002/remoteEntry.js' }, // 只是地址
shared: { dayjs: { singleton: true, requiredVersion: '^1.11.0' } },
}),
],
消费方代码就是普通的动态 import(这点最舒服):
const { Button, fmt } = await import('remote_app/Button');
四、构建出来的是什么(本机真实产物)
remote 的 dist/:
remoteEntry.js ← 远程入口(供 host 加载)
mf-manifest.json ← 元数据(暴露/共享/远程)
mf-stats.json ← 构建统计
__federation_expose_Button.js ← 被暴露模块的产物
node_modules_dayjs_dayjs_min_js.js ← 共享依赖被单独拆出
host 的 dist/:
main.js ← host 自己
mf-manifest.json
node_modules_dayjs_dayjs_min_js.js
注意 host 没有 remoteEntry.js(它不对外暴露),但同样有 dayjs 的独立 chunk——这就是 shared 在构建期的体现。
stats 里的关键证据(真实构建输出)
provide shared module (default) dayjs@1.11.23 = ../node_modules/dayjs/dayjs.min.js
consume shared module (default) dayjs@!=1...1.1...0 (singleton) (fallback: ../node_modules/dayjs/dayjs.min.js)
remote remote_app/Button 6 bytes (remote) 6 bytes (share-init)
external "remote_app@http://localhost:3002/remoteEntry.js" 42 bytes
四行说明四件事:
- provide:我提供 dayjs 1.11.23 给共享池;
- consume:我要消费 dayjs,带 singleton 与 requiredVersion 约束,并准备了 fallback(共享池没有时用自带的那份);
- remote:
remote_app/Button被登记为远程模块(构建期只占 6 字节的“引用”); - external:那个远程地址在产物里是个 external——构建期不下载、运行期才拉。
mf-manifest.json 写了什么(真实文件,节选)
{
"id": "remote_app",
"metaData": { "globalName": "remote_app", "remoteEntry": { "name": "remoteEntry.js" },
"pluginVersion": "2.9.0", "publicPath": "http://localhost:3002/" },
"shared": [{ "name": "dayjs", "version": "1.11.23", "singleton": true,
"requiredVersion": "^1.11.0",
"assets": { "js": { "sync": ["node_modules_dayjs_dayjs_min_js.js"] } } }],
"exposes": [{ "id": "remote_app:Button", "name": "Button", "path": "./Button",
"assets": { "js": { "sync": ["__federation_expose_Button.js"] } } }],
"remotes": []
}
host 侧的 manifest 则反过来(exposes 为空,remotes 有值):
"remotes": [{ "federationContainerName": "remote_app", "moduleName": "Button",
"alias": "remote_app", "entry": "http://localhost:3002/remoteEntry.js" }],
"exposes": []
这份 manifest 是 MF 2.0 相对 1.0 最大的“可运维性”升级:远程模块不再只靠一串 URL,而是有可枚举、可校验、可预加载的元数据。
五、运行时契约:remoteEntry 里有什么
我把 remoteEntry.js 拆开看了一眼(grep 真实产物),里面出现这些 API 名:
get init initializeSharing loadRemote registerRemotes (分别出现 27 / 16 / 6 / 4 / 3 次)
对应的运行时心智:
| API | 干什么 |
|---|---|
init | 初始化容器(传入共享作用域) |
get | 取某个暴露的模块(get('./Button')) |
initializeSharing | 建立/接入共享依赖池 |
loadRemote | MF 2.0 的便捷入口:loadRemote('remote_app/Button') 直接拿到模块 |
registerRemotes | 运行期动态注册远程(不用写死在构建配置里) |
loadRemote + registerRemotes 让「远程是谁」变成运行时数据——这也是为什么 manifest 驱动加载、微前端平台化管理成为可能。
顺带一个真实数字:dev 模式下我的
remoteEntry.js是 382 KB(未压缩、含 MF 运行时与共享逻辑),mf-manifest.json只有 1.2 KB。生产构建会小很多,但体积要心里有数。
六、MF vs qiankun:不是替代,是不同层
| Module Federation | qiankun 类方案 | |
|---|---|---|
| 抽象层级 | 模块(import 级) | 应用(HTML entry 级) |
| 依赖 | 共享(同一份 dayjs/React) | 各自打包(易双实例) |
| 隔离 | 不做沙箱(共享即耦合) | JS 沙箱 + 样式隔离 |
| 技术栈 | 构建器插件(webpack/Rspack/Vite) | 框架无关的运行时容器 |
| 适用 | 同构技术栈、要共享依赖(如多个 React 应用) | 异构/老旧应用隔离接入 |
实践里两者常叠用:qiankun 负责「应用级别接入与隔离」(尤其是老应用、Vue2+React 混布),MF 负责「新应用之间的模块与依赖共享」。本站 qiankun 系列 讲的是前者的落地细节。
七、MF 2.0 带来了什么
从本机实测的包和官方资料看,2.x 的主要变化:
@module-federation/enhanced(2.9.0):一个插件把「暴露/消费/共享/类型/manifest/devtools」都带上,不再手拼一堆插件;- 框架无关 runtime(
@module-federation/runtime2.9.0):不绑定 webpack,可在任意环境驱动加载; - manifest 与
mf-stats.json:可枚举、可托管、可做预加载与健康检查; - 运行时插件体系(runtime plugins):重试、降级、日志、缓存策略可以插件化;
- 多构建器支持:Rspack(
@rspack/core实测版本 2.2.4)与 Vite(@module-federation/vite1.21.6)都有官方方案; - 类型支持:配合 dts 插件可以给远程模块生成/注入 TS 类型(不然
import('remote_app/Button')全是 any)。
八、五个真实的坑(先想清楚再上)
- 依赖双实例:React 这类库没共享成功 → hooks 报错、context 不通用。核心解法就是
singleton: true+ 严格的requiredVersion,并理解「版本协商失败会 fallback 到各自那份」这件事(上面 stats 里那条consume shared module ... (fallback: ...)就是它)。 - 远程挂了怎么办:MF 默认不会帮你优雅降级。生产要自己加超时、重试、兜底 UI(remoteEntry 拉不到时整块区域降级),这也是 runtime plugin 的用武之地。
- 远程地址不是无限的:
remoteEntry.js是运行期拉的,意味着跨域 CORS、CDN 缓存策略、版本发布顺序都要设计(典型做法:remoteEntry 短缓存/不缓存,带 hash 的 chunk 长缓存)。 - 样式没有隔离:MF 只共享模块,不做样式沙箱——CSS 命名冲突、全局样式污染要自己用 CSS Modules / 命名空间 / Shadow DOM 解决。
- 本地开发体验:remote 没起时 host 直接报错。约定好本地端口、或用 manifest 指向测试环境远程,能省很多扯皮。
九、选型清单
| 你的情况 | 建议 |
|---|---|
| 多个同构应用(都 React/Vue3)要共享组件与依赖 | Module Federation |
| 要接异构/老旧应用、强隔离 | qiankun 类方案(也可叠 MF) |
| 只是把页面拼起来、几乎不共享代码 | iframe / Web Components 可能更省事 |
| 团队与发布完全统一 | 单体应用 / monorepo,别上微前端 |
一句话:MF 解决的是「模块与依赖的跨应用复用」,它的收益来自共享,它的风险也来自共享——共享得越深,团队之间的构建、版本、发布节奏就越需要对齐。
参考
- Module Federation 官方站(2.0 文档):module-federation.io
- webpack 官方 Module Federation 指南:webpack.js.org/concepts/module-federation
- 本站对照:qiankun 微前端系列
- 版本事实(本机
npm view实测,2026-09):webpack 5.111.0、@module-federation/enhanced2.9.0、@module-federation/runtime2.9.0、@module-federation/vite1.21.6、@rspack/core2.2.4 - 构建产物与 manifest 节选:本机
/tmp/mf-lab实测(webpack 5.111.0 + MF enhanced 2.9.0,dayjs 1.11.23)
