接入第三方组件库还要单独打包一份?我把 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 变化不大,现有代码的修改量主要在加载逻辑那一层。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 406 阅读