Webpack Module Federation

面试题

Module Federation 如何实现微前端?hostremoteshared 分别是什么?

核心回答

Module Federation 是 Webpack 5 提供的运行时模块共享能力。它允许一个应用在运行时加载另一个应用暴露出来的模块,而不是像 npm 包一样在构建时把代码打进同一个 Bundle。

在微前端场景中:

  • Host:宿主应用,负责组装页面、声明要消费哪些远程应用,并在运行时加载远程模块。
  • Remote:远程应用,独立构建、独立部署,通过 remoteEntry.js 暴露组件、路由、工具函数等模块。
  • exposes:Remote 对外暴露的模块清单。
  • remotes:Host 声明远程应用名称和远程入口地址。
  • shared:多个应用之间共享的依赖,例如 reactreact-domvue,用于减少重复加载并约束单例和版本。

一句话总结:Host 通过远程入口拿到 Remote 的模块容器,Remote 通过容器暴露模块,shared 通过共享作用域解决公共依赖复用和版本协商。

基本配置

Remote 暴露模块:

// remote webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'product',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductList': './src/ProductList',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};

Host 消费模块:

// host webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        product: 'product@https://cdn.example.com/product/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};

业务代码里像普通异步模块一样使用:

const ProductList = React.lazy(() => import('product/ProductList'));

运行时加载流程

面试中只说“运行时加载”通常不够,最好能讲清楚链路:

  1. Host 执行到 import('product/ProductList')
  2. Webpack runtime 发现 product 是 remote,先加载 product@.../remoteEntry.js
  3. remoteEntry.js 注册一个容器对象,里面包含 getinit 方法。
  4. Host 调用 container.init(__webpack_share_scopes__.default),初始化共享作用域。
  5. Host 调用 container.get('./ProductList'),拿到模块工厂函数。
  6. 执行工厂函数得到真实模块,再交给 React/Vue 渲染。

所以 Module Federation 的本质不是 iframe,也不是简单的 script 标签拼接,而是 Webpack runtime 之间通过 container 协议进行模块级协作

shared 的版本协商

shared 是最容易被深挖的点。它主要解决两个问题:

  • 避免多个微前端重复下载同一个大依赖;
  • 对 React、Vue 这类运行时状态敏感的库保持单例,避免出现多个实例导致 Hooks、Context、响应式系统异常。

常见参数:

  • singleton: true:全局共享作用域里只使用一个实例,适合 reactreact-domvue
  • requiredVersion:声明当前应用期望的版本范围。
  • strictVersion: true:版本不满足时直接报错,而不是尽量降级使用。
  • eager: true:构建时直接把共享依赖放进当前入口,不再异步获取。它会牺牲按需加载能力,通常只在非常明确的基础依赖场景使用。

版本选择的大致逻辑是:各应用把自己能提供和需要的共享依赖注册到 share scope,Webpack runtime 根据版本范围选择可用版本。如果没有兼容版本,默认可能回退到本地版本或给出 warning;开启 strictVersion 后会变成更强约束。

面试回答时可以补一句:shared 不是天然保证安全的银弹,它只能在运行时做依赖复用和版本匹配,真正的兼容性仍然需要团队约定、测试和发布治理。

和 npm 包、iframe 的区别

和 npm 包相比

npm 包是构建时依赖。Host 升级某个业务包后,需要重新安装、重新构建、重新发布。

Module Federation 是运行时依赖。Remote 发布后,只要远程入口地址不变,Host 下次加载就可能拿到新的远程模块,宿主不一定需要重新构建。

代价是:运行时网络、缓存、版本兼容、灰度发布、错误兜底都会更复杂。

和 iframe 相比

iframe 是页面级隔离,样式、JS 运行时、路由和通信都相对隔离,集成成本更像“嵌入一个子页面”。

Module Federation 是模块级集成,Remote 暴露的是组件或模块,可以直接进入 Host 的组件树,体验更一体化,但样式污染、依赖冲突和运行时错误也更容易相互影响。

工程落地要点

远程入口缓存

remoteEntry.js 通常不适合强缓存太久,因为它描述了当前 Remote 的模块映射和 chunk 地址。比较常见的策略是:

  • remoteEntry.js 使用短缓存或 no-cache,确保能拿到最新映射;
  • 具体 chunk 文件使用内容哈希和长期缓存;
  • 发布时保证 remoteEntry.js 引用的 chunk 文件已经上传完成。

如果先更新入口、后上传 chunk,Host 可能拿到新入口但请求不到对应 chunk,产生线上白屏。

部署顺序和兼容性

Remote 可以独立发布,但不能随意破坏 Host 依赖的契约。对外暴露的模块路径、组件 props、事件协议、shared 版本都应该看作 API。

常见治理方式:

  • Remote 暴露稳定的模块名,不频繁改 exposes key;
  • 对暴露组件做契约测试或集成测试;
  • 灰度发布 Remote,出现异常时能回滚远程入口;
  • Host 对远程模块加载失败提供降级 UI。

故障兜底

运行时加载天然会遇到网络失败、入口 404、版本不兼容、远程模块执行报错等问题。

React 场景通常会结合:

  • React.lazySuspense 处理加载态;
  • ErrorBoundary 捕获渲染错误;
  • 动态 remote loader 设置超时、重试和降级;
  • 监控 remoteEntry 加载失败率、chunk 加载失败率和远程模块异常。

常见追问

Q:Module Federation 为什么能做到独立部署?

因为 Host 构建产物里并不包含 Remote 的真实业务代码,只包含远程应用名称、入口地址和运行时加载逻辑。Remote 独立构建后更新自己的 remoteEntry.js 和 chunk,Host 在运行时通过远程入口解析并加载最新模块。

但独立部署不等于完全独立演进。Remote 暴露给 Host 的模块接口仍然需要保持兼容。

Q:为什么 React 要配置 singleton?

React 的 Hooks、Context、调度和渲染依赖同一份 React/ReactDOM 实例。如果 Host 和 Remote 各自加载一份 React,就可能出现 Hooks 调用异常、Context 不互通、组件树行为不一致等问题。

所以 React 微前端通常会把 reactreact-dom 配成 singleton: true,并用 requiredVersionstrictVersion 约束版本。

Q:Remote 更新后 Host 一定能立刻生效吗?

不一定。是否生效取决于 remoteEntry.js 和 chunk 的缓存策略、CDN 刷新、浏览器缓存以及 Host 是否已经加载过这个远程容器。

已经打开的页面通常不会自动替换已加载模块;刷新或重新进入加载链路后,才更可能拿到新的远程入口。

Q:Module Federation 会不会影响性能?

会,需要权衡。

收益是多个应用可以共享依赖、业务模块可以独立按需加载,减少主应用包体积。成本是首用远程模块时多了 remoteEntry.js 和异步 chunk 请求,还增加了运行时初始化和版本协商。

优化方向包括预加载关键 Remote、合理拆分 chunk、控制 remote 数量、共享大依赖、给远程入口设置合适缓存策略。

Q:它适合所有微前端场景吗?

不适合。Module Federation 更适合同技术栈、需要组件级复用、希望独立部署但又追求一体化体验的系统。

如果团队技术栈差异很大、隔离要求强、只是嵌入外部系统,iframe 或 Web Components 可能更简单。如果只是复用稳定工具库,npm 包反而更直接。

面试回答模板

Module Federation 是 Webpack 5 的运行时模块共享方案。Remote 通过 exposes 把模块暴露到 remoteEntry.js,Host 通过 remotes 声明远程入口,在运行时加载 remoteEntry,初始化 shared share scope,再通过 container.get 拿到远程模块。shared 用来复用公共依赖,并通过 singleton、requiredVersion 等配置处理 React/Vue 这类依赖的单例和版本问题。它相比 npm 包的优势是 Remote 可以独立构建和发布,Host 不一定要重新打包;代价是运行时网络、缓存、版本兼容、降级和发布顺序都需要治理。真正落地时要重点关注 remoteEntry 缓存、chunk 上传顺序、契约测试、错误边界和加载失败兜底。