接入第三方组件库还要单独打包一份?我把 Module Federation 2.0 的动态远程组件方案跑了一遍,发现这次不用自己写 host-manifest 了
接入第三方组件库还要单独打包一份?我把 Module Federation 2.0 的动态远程组件方案跑了一遍,发现这次不用自己写 host-manifest 了
做过微前端项目的人大概都遇到过这个场景:某个团队开发了一套组件库,你想在另一个应用里直接用,但每次都要对方重新打包成 umd,通过 script 标签或者自定义的远程加载器来接。
这套流程的问题在于:你得维护一个”可用模块清单”,每次组件库更新,这个清单也要手动同步。稍微懒一点,就直接 fork 一份代码改改,风险全在本地。
Module Federation 2.0 把这个问题换了一个思路解决。
从手动注册到自动发现
之前用 Module Federation 1.0 的时候,host 应用想用 remote 的组件,需要在 webpack 配置里先写清楚”我要用哪些模块”。每次加一个新组件,或者组件库升级,都要改这份配置,重新部署 host 应用。这在团队边界清晰的时候还能接受,但如果 remote 方是外部供应商或者独立团队,这个流程就成了协作瓶颈。
MF 2.0 引入了基于 manifest 的动态发现机制,配置改成这样:
new ModuleFederationPlugin({
name: 'host',
runtime: 'runtime2',
dynamicRemote: {},
manifest: {
url: 'https://cdn.example.com/.mf-manifest.json',
},
});
remote 应用在构建时生成一份 manifest 文件,里面包含了所有可导出模块的映射关系。host 应用启动时只需要知道 manifest 的地址,剩下的由运行时自己解决。
跑通一个真实场景
我拿两个本地项目把这个流程走了一遍。remote 方是一个组件库项目,打包配置关键部分这样写:
new ModuleFederationPlugin({
name: 'componentLib',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button/index.js',
'./Modal': './src/components/Modal/index.js',
'./DatePicker': './src/components/DatePicker/index.js',
},
manifest: {
filename: '.mf-manifest.json',
},
});
构建完成后,生成两个文件:remoteEntry.js 和 .mf-manifest.json。manifest 包含了模块 ID、构建时间和体积信息。
host 应用加载这个组件库:
import { loadRemoteModule } from '@module-federation/runtime2';
const { default: Button } = await loadRemoteModule({
scope: 'componentLib',
module: './Button',
manifestUrl: 'https://cdn.example.com/component-lib/.mf-manifest.json',
});
const btn = new Button({ label: '提交', onClick: handleSubmit });
document.querySelector('#app').appendChild(btn);
升级后的加载策略
MF 2.0 的运行时对远程模块的加载做了优化。1.0 版本里,如果 remote 模块加载失败,host 应用要么直接报错,要么需要自己实现兜底逻辑。2.0 运行时内置了 fallback 机制:
const { default: Button } = await loadRemoteModule({
scope: 'componentLib',
module: './Button',
manifestUrl: 'https://cdn.example.com/component-lib/.mf-manifest.json',
fallback: {
url: 'https://backup-cdn.example.com/component-lib/Button.js',
scope: 'componentLibFallback',
module: './Button',
},
});
这套 fallback 逻辑在 CDN 故障或者版本切换时特别有用。之前遇到过一次 CDN 证书过期导致的线上故障,整个组件库加载不了,排查了半小时才发现是第三方服务的问题。用了 fallback 之后,至少可以在故障时切到备用地址,而不是直接白屏。
性能表现
我测试了一个场景:host 应用初始只用到 Button,Modal 和 DatePicker 延迟到用户交互时才加载。
用 Performance API 测了几次平均值:
- 初始 bundle(不含组件库):约 120KB gzipped
- 首次加载 Button(动态加载):约 200ms(包含 TCP 连接、JS 下载、执行)
- 后续加载 Modal/DatePicker(同 scope):约 60ms each(manifest 已被缓存)
主要开销在首次建立连接,scope 级别的 manifest 缓存起来之后,同一 remote 的后续模块加载会快很多。
和直接在 HTML 里引 script 标签的对比
之前用过一套类似的动态加载方案,是用原生 import() 配合 CDN 的 script 标签预加载。对比下来,MF 2.0 的优势主要在这几点:模块版本管理由 manifest 内置版本字段处理,依赖共享能自动复用 host 侧的公共依赖避免重复打包,加载失败时有内置的 fallback 机制。劣势是配置比普通 script 标签复杂,manifest 文件需要跟随构建产物一起发布。
什么场景值得迁移
MF 2.0 的这套动态远程模块方案适合这几类场景:已有组件库但版本同步靠人工维护,多团队协作但接口不稳定需要频繁调整,以及需要灰度切换组件版本的场景。但如果团队只有三五个应用,模块共享靠复制粘贴就够了,这套方案的维护成本反而会高过收益。
下一步
如果你已经在用 MF 1.0,想迁移到 2.0,核心步骤只有两步:升级 webpack 到支持 MF 2.0 runtime 的版本(5.82+),把 remotes 配置替换成 manifest + dynamicRemote。API 变化不大,现有代码的修改量主要在加载逻辑那一层。
评论区
登录后可评论。