如何设计一套可插件化扩展的模块联邦(Module Federation)架构

作者:袖梨 2026-06-26
模块联邦可作为插件系统底座,需将remotes视为动态注册/卸载的插件实例,统一导出含id、routes、init、destroy的插件对象,并由host统一调度生命周期与共享依赖。

模块联邦本身不是插件系统,但能成为插件系统的运行时底座——关键在于把 remotes 当作可动态注册/卸载的插件实例,而非静态配置项。

remote 必须导出统一插件接口,不能只暴露组件

很多团队直接在 exposes 里写 ./Button./Dashboard,这会导致 host 无法感知插件能力边界,也无法做生命周期管理。真正可插拔的 remote 应该导出一个符合约定的插件对象:

  • register() 函数必须返回包含 idroutesinitdestroy 的对象,例如:{ id: 'user-management', routes: [...], init: () => {}, destroy: () => {} }
  • 所有 remote 都要依赖并实现同一份类型定义,比如来自 @company/plugin-corePlugin 类型,避免字段拼写不一致或缺失
  • 不要在 remote 中直接调用 ReactDOM.render 或挂载全局事件,host 才是唯一调度者;remote 的 init 只负责准备资源(如加载 locale、初始化 store slice)

host 需要运行时解析 remote 列表,不能硬编码 import()

import('microfrontend1/register') 写死在 host 代码里,等于把插件列表编译进包,失去“运行时扩展”意义。正确做法是让 host 从远端配置中心或本地 JSON 文件读取插件元信息:

  • 配置格式示例:{ "id": "analytics", "url": "https://cdn.example.com/analytics/remoteEntry.js", "scope": "analytics" }
  • 加载逻辑应封装成函数,支持失败重试、超时控制和降级策略,例如:loadRemotePlugin({ url, scope }).then(m => m.register())
  • 注意 Webpack 的 import() 动态导入不支持变量拼接 URL,所以必须用 Container API 手动初始化远程容器:__webpack_init_sharing__ + getContainer + init

shared 依赖必须协商版本,否则 React/Hooks 会崩溃

多个 remote 同时使用不同版本的 reactreact-router,会导致 Hooks 失效、Context 断裂、甚至白屏。这不是警告,是必现问题:

  • shared 配置中不能只写 ['react', 'react-dom'],必须显式指定 singleton: truerequiredVersion,例如:{ react: { singleton: true, requiredVersion: '^18.2.0' } }
  • host 必须作为 shared 的唯一提供方(即 shareScope 主控者),所有 remote 设置 import: false,强制从 host 拿实例
  • 如果某个 remote 确实需要不同版本的库(如 legacy 表单组件依赖 React 17),只能把它隔离进 iframe 或 Web Worker,不能走 Module Federation 直接共享

插件热更新和卸载需要手动清理副作用

模块联邦不处理 unmount,你 import 过的 remote 模块会一直留在内存里,state、定时器、事件监听器全都不会自动清除:

  • 每个插件的 destroy() 方法必须显式调用:clearIntervalremoveEventListenerstore.unsubscribeunmountComponentAtNode
  • React Router v6+ 不再支持动态添加路由,必须用 useRoutes + 状态驱动的路由数组,每次插件变更后重新生成 createBrowserRouter(routes)
  • 样式卸载容易被忽略:remote 中的 CSS-in-JS(如 Emotion)或 insert-css 注入的 style 标签,需在 destroy() 里手动移除对应 <style> 节点

最常被跳过的环节是插件间的类型对齐与错误边界——remote 导出的 register 返回值类型一旦和 host 期望的 Plugin 接口不一致,TS 编译通过但运行时报 Cannot read property 'routes' of undefined,这种错误不会出现在构建阶段,只会在特定插件启用时爆发。

相关文章

精彩推荐