Webpack 5 模块联邦(Module Federation)

0 阅读5分钟

在前端工程化领域,代码复用和应用集成一直是痛点。传统的 npm 包共享iframe 嵌套 方案,要么面临版本冲突,要么存在性能和体验瓶颈。Webpack 5 引入的 模块联邦(Module Federation) 彻底改变了这一现状,它允许 多个独立构建的应用共享代码,实现真正的“跨应用模块复用”,为微前端、大型应用拆分提供了原生支持。

业务场景分析:

现在有两个业务模块:电商管理平台和广告管理平台,代码是两个不同的团队维护。现在我们需要在广告模块,给某个活动配置一个商品。需要在广告项目中点击出现一个弹窗,弹出商品列表页并选择。

以前的思路,是把电商模块的商品选择组件复制到广告模块,或者把商品选择组件封装到公共组件库.(内部npm)

这样我们需要面对一些问题:代码复用率低;组件有改动需要改多次,并且需要重新build;npm的调试不够便利等等。

一、模块联邦解决了什么问题?

在模块联邦出现之前,前端应用间的代码共享主要有以下方案,均存在明显缺陷:

方案原理缺点
npm 包抽离公共代码为 npm 包版本锁定,无法实时更新,需重新安装构建
Git Submodule子仓库嵌入主仓库依赖管理复杂,构建效率低
iframe页面嵌套性能差,通信复杂,样式隔离问题
monorepo所有的业务代码、公共组件库代码都放在同一个仓库仅仅在编译的时候共用了代码,打出生产包内部依然有需要重复加载的模块

模块联邦的创新点

  • 运行时动态加载:远程模块在运行时按需加载,无需编译时依赖。
  • 共享依赖自动协商:不同应用共享同一依赖时,自动选择合适版本,避免重复加载。
  • 双向共享能力:组件可以作为“宿主”加载远程模块,也可以作为“远程”暴露模块给其他应用。

二、核心概念与工作原理

1. 核心术语

  • 宿主应用(Host):加载并使用远程模块的应用(如主应用)。
  • 远程应用(Remote):暴露模块供其他应用使用的应用(如微应用)。
  • 远程入口文件(Remote Entry):远程应用生成的特殊文件,包含模块暴露信息(通常命名为 remoteEntry.js)。
  • 共享作用域(Shared Scope):所有应用共享的依赖池,用于协商依赖版本。

2. 工作流程

  1. 远程应用暴露模块:通过 exposes 配置声明要共享的模块,Webpack 生成 remoteEntry.js
  2. 宿主应用加载远程入口:通过 remotes 配置指定远程应用的 remoteEntry.js URL。
  3. 运行时动态加载:宿主应用在需要时,通过 import('remote/app/Module') 动态加载远程模块。
  4. 依赖共享协商:若宿主和远程应用共享同一依赖(如 React),共享作用域会自动选择一个版本加载,避免重复。

三、核心配置详解

模块联邦的能力通过 ModuleFederationPlugin 插件实现,以下是关键配置项的详细说明:

基础配置模板

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 1. 应用标识(必填)
      name: 'app1', // 全局唯一名称,作为远程应用时会挂载到 window[app1]

      // 2. 远程应用配置(暴露模块)
      filename: 'remoteEntry.js', // 生成的远程入口文件名(宿主通过此文件加载模块)
      exposes: {
        './Button': './src/components/Button', // 暴露路径: 本地模块路径
        './utils': './src/utils',
      },

      // 3. 宿主应用配置(加载远程模块)
      remotes: {
        app2: 'app2@http://localhost:3002/remoteEntry.js', // 别名: "远程应用名@远程入口URL"
      },

      // 4. 共享依赖配置(可选)
      shared: {
        react: {
          singleton: true, // 强制单例(同一依赖只加载一次)
          requiredVersion: '^17.0.0', // 要求的版本范围
          eager: false, // 是否在初始化时立即加载(默认 false,按需加载)
        },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

关键配置解析

1. name

应用的唯一标识,当作为远程应用时,会将模块暴露到 window[name] 全局变量(如 window.app2)。宿主应用通过此变量访问远程模块。

2. exposes(远程应用)

声明要暴露的模块,格式为 { 暴露路径: 本地模块路径 }

  • 暴露路径:宿主应用加载时使用的路径(如 import('app2/Button'))。
  • 本地模块路径:相对于 Webpack 配置文件的模块路径。

3. remotes(宿主应用)

声明要加载的远程应用,格式为 { 别名: "远程应用名@远程入口URL" }

  • 别名:宿主应用中引用远程模块的前缀(如 import('app2/Button') 中的 app2)。
  • 远程入口 URL:远程应用 remoteEntry.js 的访问地址(必须是绝对路径)。

4. shared(共享依赖)

解决多应用间依赖共享问题,避免重复加载(如 React、Lodash 等)。

  • singleton: true:强制依赖单例,若多个应用请求同一依赖,只加载一次。
  • requiredVersion:指定依赖版本范围,若版本不兼容会报错(可省略,自动协商)。
  • eager: true:依赖在应用初始化时立即加载,而非按需加载(适合基础库)。

对于技术栈统一的微前端项目,共享依赖会极大地提升性能。 子项目本身甚至都不需要安装太多依赖,直接去引用主项目的共享依赖;多个项目使用同一个公共库,如Vue、ant-design-vue等,都使用同一个。

六、局限性与注意事项

1. 依赖版本冲突

若共享依赖版本不兼容(如 React 16 和 17),Webpack 会加载多个版本,可能导致运行时错误。解决方案

  • 统一依赖版本,或使用 requiredVersion 严格限制版本范围。

2. 生产环境部署

  • 确保远程应用的 publicPath 是可访问的绝对路径(如 CDN 地址)。
  • 配置 CORS 允许跨域请求,远程应用服务器需设置 Access-Control-Allow-Origin。(在绝大部分实际场景下,微服务的应用都部署在同一个目录下,不会出现跨域问题)

官网例子运行:

github.com/beat-the-bu…