Module Federation
Webpack 5 内置模块联邦,支持在运行时加载远程模块。
核心概念
Host(消费者) ←→ Remote(提供者)
↓
共享依赖 shared(react, vue, lodash 等)Host(消费者) ←→ Remote(提供者)
↓
共享依赖 shared(react, vue, lodash 等)配置示例
Remote(提供者)
js
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./store': './src/store',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true },
},
}),
],
}// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./store': './src/store',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true },
},
}),
],
}Host(消费者)
js
// webpack.config.js
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
remote_app: 'remote_app@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
})// webpack.config.js
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
remote_app: 'remote_app@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
})使用远程组件
jsx
// React 中动态加载
const RemoteButton = React.lazy(() => import('remote_app/Button'))
function App() {
return (
<Suspense fallback="Loading...">
<RemoteButton />
</Suspense>
)
}// React 中动态加载
const RemoteButton = React.lazy(() => import('remote_app/Button'))
function App() {
return (
<Suspense fallback="Loading...">
<RemoteButton />
</Suspense>
)
}js
// 普通 JS 中使用
const container = await import('remote_app/store')// 普通 JS 中使用
const container = await import('remote_app/store')配置项详解
| 配置项 | 类型 | 说明 |
|---|---|---|
name | string | 模块名称,唯一标识 |
filename | string | remoteEntry 文件名 |
exposes | object | 暴露给外部的模块 |
remotes | object | 引用的远程模块 |
shared | object | 共享依赖配置 |
shared 配置
js
shared: {
react: {
singleton: true, // 全局单例(避免多实例)
requiredVersion: '^18.0.0',
eager: true, // 立即加载
strictVersion: false, // 版本严格匹配
},
// 或者使用数组
shared: ['react', 'react-dom'],
}shared: {
react: {
singleton: true, // 全局单例(避免多实例)
requiredVersion: '^18.0.0',
eager: true, // 立即加载
strictVersion: false, // 版本严格匹配
},
// 或者使用数组
shared: ['react', 'react-dom'],
}Module Federation 与 qiankun 对比
| 维度 | Module Federation | qiankun |
|---|---|---|
| 架构 | 去中心化 | 中心化(基座模式) |
| 粒度 | 模块级共享 | 应用级加载 |
| 隔离 | 无沙箱 | 有沙箱 |
| 共享依赖 | 内置共享机制 | 需手动配置 |
| 构建工具 | Webpack 5+ | 不限 |
| 学习成本 | 中等 | 低 |
实战技巧
1. 共享第三方库
js
// 所有应用配置相同的 shared,避免重复加载
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true },
'react-router-dom': { singleton: true },
}// 所有应用配置相同的 shared,避免重复加载
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true },
'react-router-dom': { singleton: true },
}2. 避免版本冲突
js
shared: {
antd: {
singleton: true,
requiredVersion: '^5.0.0',
strictVersion: false, // 允许小版本差异
},
}shared: {
antd: {
singleton: true,
requiredVersion: '^5.0.0',
strictVersion: false, // 允许小版本差异
},
}3. 动态远程加载
js
// 运行时动态加载远程模块
function loadRemote(url, scope, module) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.onload = async () => {
await __webpack_init_sharing__('default')
const container = window[scope]
await container.init(__webpack_share_scopes__.default)
const factory = await container.get(module)
resolve(factory())
}
script.onerror = reject
document.head.appendChild(script)
})
}// 运行时动态加载远程模块
function loadRemote(url, scope, module) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.onload = async () => {
await __webpack_init_sharing__('default')
const container = window[scope]
await container.init(__webpack_share_scopes__.default)
const factory = await container.get(module)
resolve(factory())
}
script.onerror = reject
document.head.appendChild(script)
})
}
Cao Note