一、Module Federation解决的核心问题
传统微前端方案(iframe、single-spa)都面临一个根本矛盾:应用之间无法共享运行时依赖,导致巨大的bundle重复和加载时间问题。Module Federation(模块联邦)从根本上打破了这一限制。
1.1 共享依赖的困境与联邦方案的突破
// 传统方案:独立构建,共享依赖时产生重复
//
// App Shell (host)
//
// app-vendor.js ← lodash + react + react-dom = 200KB
// app-main.js ← 业务代码 = 150KB
// Total: 350KB
//
// ┌──────────────────────────────────────┐
// │ 子应用A (remote) │
// │ a-vendor.js ← lodash+react = 200KB 重复! │
// │ a-main.js ← 业务代码 = 80KB │
// │ Total: 280KB(200KB浪费了) │
// └──────────────────────────────────────┘
// ┌──────────────────────────────────────┐
// │ 子应用B (remote) │
// │ b-vendor.js ← lodash+react = 200KB 重复! │
// │ b-main.js ← 业务代码 = 120KB │
// │ Total: 320KB(200KB浪费了) │
// └──────────────────────────────────────┘
// 总体:950KB(其中400KB是重复的)
// Module Federation的架构:
//
// ┌──────────────────────────────────────────────┐
// │ webpack Module Federation │
// │ │
// │ Host App │
// │ ┌────────────────────────────────────┐ │
// │ │ @module-federation/manifest │ │
// │ │ <script src="http://remote/remote.js">│ │
// │ │ 在运行时拉取远程模块 │ │
// │ └────────────────────────────────────┘ │
// │ │
// │ Shared: react ★ shared │
// │ ↑ 仅加载一次,多应用共用 │
// │ │
// │ ┌──────────┐ ┌──────────┐ ┌──────────┐│
// │ │ Host App │ │ Remote A │ │ Remote B ││
// │ │ 350KB │ │ 80KB │ │ 120KB ││
// │ └──────────┘ └──────────┘ └──────────┘│
// │ ↑ ↑ ↑ │
// │ └───────────┴───────────┘ │
// │ react/react-dom = 200KB (only 1x) │
// │ Total: 770KB → 节省 180KB + N*200KB │
// └──────────────────────────────────────────────┘
二、联邦模块的配置模型
2.1 Host与Remote的完整配置
// Host(宿主应用)webpack配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
publicPath: 'auto', // 'auto'让webpack自动选择CDN路径
},
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
// 暴露自身哪些模块给其他应用使用
exposes: {
'./Header': './src/components/Header.jsx',
'./Store': './src/store/index.js',
},
// 共享依赖声明(自动加载和版本冲突解决)
shared: {
react: {
singleton: true, // 强制单例(只允许一个版本)
requiredVersion: '^18.0.0',
eager: false, // false=懒加载,不影响首屏速度
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
},
// 共享多个版本时允许不同版本共存
'lodash': {
singleton: false, // 允许多版本共存
strictVersion: false,
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
2.2 Remote(远程应用)配置
// Remote(被消费应用)webpack配置
const { ModuleFederationPlugin } = require('webpack').container;
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/bootstrap.js', // 入口改为bootstrap
output: {
publicPath: 'https://remote-a.example.com/',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote_app_a',
// 暴露给宿主使用的模块
filename: 'remoteEntry.js', // 生成的远程入口文件
exposes: {
'./ProductList': './src/components/ProductList.jsx',
'./ProductDetail': './src/components/ProductDetail.jsx',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
// bootstrap.js(代替index.js作为入口)
import('react').then(React => {
import('./App.jsx').then(({ default: App }) => {
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
});
});
// 为什么要用bootstrap.js?
// 懒加载react(不在script标签里同步加载)
// 这样host可以先加载自己的react版本
三、运行时加载与版本协商机制
3.1 动态远程模块加载
// 在Host应用中动态消费Remote模块
// 方法一:同步导入(必须在bootstrap前声明)
// remoteEntry.js已暴露ProductList
// import ProductList from 'remote_app_a/ProductList';
// 方法二:运行时动态加载(更灵活)
// src/dynamicRemote.js
const loadRemote = async (scope, module) => {
// 初始化容器(每个remote容器只初始化一次)
await __webpack_init_sharing__('default');
const container = window[scope]; // window.remote_app_a
if (!container) throw new Error(`Container ${scope} not found`);
// 获取Remote共享的模块(如react)
await container.init(__webpack_share_scopes__.default);
// 加载指定模块
const factory = await window[scope].get(module);
// factory = () => require('./src/components/ProductList.jsx')
const Module = factory();
return Module.default; // 导出default
};
// 使用示例:
async function App() {
const { ProductList } = await loadRemote('remote_app_a', './ProductList');
return <ProductList />;
}
// 手动实现一个简化版容器加载器(理解原理):
const loadRemoteModule = async (url, scope, modulePath) => {
// ① 加载remoteEntry.js
await loadScript(url);
// ② 注册共享模块
await __webpack_init_sharing__(scope);
const container = window[scope];
await container.init(window.__webpack_share_scopes__[scope]);
// ③ 获取模块
const moduleFactory = await container.get(modulePath);
return moduleFactory();
};
3.2 版本冲突解决策略
// 版本冲突解决的四种策略:
//
// 策略一:singleton(单例,最严格)
shared: {
react: { singleton: true }
}
// → 强制使用最高版本(语义版本兼容)
// react@18.2.0 vs react@18.0.0 → 使用18.2.0
// react@17 vs react@18 → ❌ 版本冲突,无法加载
// 策略二:requiredVersion(范围兼容)
shared: {
'react-dom': { requiredVersion: '^18.0.0' }
}
// → 18.1.0/18.2.0/18.9.0都兼容,17.x ❌
// 策略三:strictVersion(严格相等)
shared: {
axios: { strictVersion: true }
}
// → 必须完全相同版本才共享,否则各自加载
// 策略四:单例+版本范围(生产推荐)
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
// 允许版本范围的"最宽"解析
}
}
// ⚠️ 常见问题:host和remote的react版本不一致
// Host: react@18.2.0 (singleton)
// Remote: react@18.0.0
// → Module Federation会选择18.2.0(取最高兼容版本)
// → Remote会使用Host加载的react实例(共享成功)
// 调试版本冲突:
// 浏览器控制台执行:
window.__webpack_share_scopes__
四、生产环境部署架构
// 生产部署拓扑
//
// CDN / Nginx (反向代理)
// ├── / → host_app
// ├── /remote-a/ → remote_app_a (独立部署)
// ├── /remote-b/ → remote_app_b (独立部署)
// └── /remote-c/ → remote_app_c (独立部署)
//
// 每个应用独立CI/CD,互不影响
// Host的nginx配置(支持多应用路由)
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://host_app:3000;
}
# Remote应用的路径
location /remote-a/ {
proxy_pass http://remote_app_a:3001/;
add_header Access-Control-Allow-Origin *;
}
location /remote-b/ {
proxy_pass http://remote_app_b:3002/;
add_header Access-Control-Allow-Origin *;
}
}
// Host的webpack publicPath
output: {
publicPath: 'auto', // 自动适配当前域名
// 或明确指定:
// publicPath: 'https://cdn.example.com/host/',
}
// Remote的webpack filename(必须能被CDN访问)
plugins: [
new ModuleFederationPlugin({
filename: 'remoteEntry.js', // 访问路径:/remote-a/remoteEntry.js
// 在Nginx中需要配置跨域:
// add_header Access-Control-Allow-Origin *;
})
]
// ⚠️ 生产环境注意事项:
// 1. remoteEntry.js不应被浏览器长期缓存(更新版本时需刷新)
// → 使用contenthash:filename: 'remoteEntry.[contenthash:8].js'
// 2. 所有应用使用相同的webpack版本(MF的兼容性保证)
// 3. 使用SSR时,Module Federation需要Node构建目标