webpack模块联邦

30 阅读2分钟

每个接入模块联邦的应用都会被webpack构建为一个独立的模块容器,容器包含两部分:

  1. 容器入口文件,默认命名remoteEntry.js 该文件包含了所有共享模块的名字和映射地址
  2. 业务代码chunk

使用消费共享联邦模块的应用会在 使用该共享模块的页面按需懒加载对应模块, 根据 remotes 里面配置URL,以动态script标签的形式远程加载remoteEntry.js 并执行

关键配置参数: singleton共享池用到单一实例,eager:true表示优先本地依赖,再去共享池查找依赖

远程暴露模块:

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

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp', // 远程应用名称
      filename: 'remoteEntry.js', // 容器文件名
      exposes: {
        './Button': './src/components/Button', // 暴露的组件
        './utils': './src/utils/common' // 暴露的工具函数
      },
      shared: { // 共享依赖
        react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

宿主应用模块:

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

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp', // 宿主应用名称
      remotes: {
        // 声明要使用的远程应用:[应用名]@[远程应用容器文件URL]
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
      },
      shared: { // 共享依赖,与远程应用保持一致
        react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

Tips:

  • 单例模式(singleton: true) :核心依赖(如 Vue、React、Pinia)建议开启,强制整个应用生态中同一依赖仅存在一个实例,避免因多实例导致的状态混乱、组件通信失败
  • 版本不兼容的处理:如果宿主和远程的依赖版本无法兼容(如宿主用 Vue3.2,远程用 Vue2),Webpack 会分别加载两个版本,隔离运行,避免语法 / API 冲突,保证应用正常运行

webpack的版本协商的基础是Webpack在运行时创建的全局共享依赖池,所有接入模块联邦的应用都会把自己的共享依赖注册到这个池,池子主要包含:

  • 依赖名称(如react)
  • 已加载的版本(如18.2.0)
  • 依赖的实例(如React的全局对象)
  • 依赖的加载器(用于动态加载缺失版本)

协商的本质是,远程请求依赖的时候,先查这个池子,再按照规则决定使用共享依赖池还是新加载一个新的依赖。