每个接入模块联邦的应用都会被webpack构建为一个独立的模块容器,容器包含两部分:
- 容器入口文件,默认命名remoteEntry.js 该文件包含了所有共享模块的名字和映射地址
- 业务代码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的全局对象)
- 依赖的加载器(用于动态加载缺失版本)
协商的本质是,远程请求依赖的时候,先查这个池子,再按照规则决定使用共享依赖池还是新加载一个新的依赖。