模块联邦微前端架构设计原理
Webpack 5引入的Module Federation(模块联邦)允许多个独立构建的应用在运行时共享模块,实现了真正的微前端运行时集成。与NPM包共享不同,模块联邦不需要在构建时打包共享代码,而是在浏览器运行时动态加载远程模块,各子应用独立开发、独立部署、独立运行,通过HTTP协议暴露和消费模块。
模块联邦有三个核心概念:Host(宿主应用)是消费远程模块的应用;Remote(远程应用)是暴露模块给其他应用消费的应用;Shared(共享依赖)是多个应用之间共享的第三方库(如React、Vue),避免重复加载。一个应用可以同时扮演Host和Remote角色,形成去中心化的微前端网络。
与qiankun等基于iframe或沙箱隔离的微前端方案相比,Module Federation在同一个JavaScript运行时中加载模块,应用间通信通过原生import/export完成,没有跨域和沙箱开销。但这也意味着子应用之间需要保持依赖版本兼容,共享依赖的版本冲突需要通过shared配置解决。
Host宿主应用配置
Host应用通过ModuleFederationPlugin配置远程应用的入口和共享依赖:
// webpack.config.js (Host应用)
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: {
publicPath: 'https://host.yunthe.com/',
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
// 远程应用配置,名称对应Remote的name字段
user_center: 'user_center@https://user.yunthe.com/remoteEntry.js',
order_system: 'order_system@https://order.yunthe.com/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
'react-router-dom': { singleton: true, requiredVersion: deps['react-router-dom'] },
},
}),
],
};
remotes字段声明远程应用的入口URL,格式为”应用名@远程入口URL”。remoteEntry.js是Remote应用构建后生成的入口文件,Host通过异步加载该文件获取远程模块的导出内容。shared字段配置共享依赖,singleton: true确保全局只加载一个React实例,避免Hooks失效等问题。requiredVersion指定共享依赖的版本要求,当Host和Remote的版本不兼容时,Webpack会加载各自版本的依赖。
Remote远程应用配置与模块暴露
Remote应用配置暴露的组件和模块:
// webpack.config.js (Remote应用 - 用户中心)
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: {
publicPath: 'https://user.yunthe.com/',
},
plugins: [
new ModuleFederationPlugin({
name: 'user_center',
filename: 'remoteEntry.js',
exposes: {
// 暴露的模块路径 -> 模块实际位置
'./UserProfile': './src/components/UserProfile',
'./UserList': './src/components/UserList',
'./useUserService': './src/hooks/useUserService',
},
shared: {
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
},
}),
],
};
exposes字段定义远程应用对外暴露的模块。key是消费者引用的路径(如user_center/UserProfile),value是模块在Remote应用中的实际路径。filename指定生成的远程入口文件名,Host应用通过加载该文件获取exposes配置的映射关系。
Remote应用需要保留一个异步入口边界,避免在入口文件中直接引入远程模块:
// src/index.js (Remote应用入口)
import('./bootstrap').then(({ default: bootstrap }) => bootstrap());
// src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render( );
index.js中使用动态import延迟加载bootstrap.js,确保共享依赖在远程模块加载前完成初始化。如果直接在index.js中引入React,Webpack会在共享依赖解析完成前执行模块代码,导致”Shared module is not available for eager consumption”错误。
Host应用动态加载远程组件
Host应用中使用React.lazy和Suspense动态加载Remote应用暴露的组件:
// src/App.js (Host应用)
import React, { Suspense, lazy } from 'react';
// 动态加载远程组件
const UserProfile = lazy(() => import('user_center/UserProfile'));
const UserList = lazy(() => import('user_center/UserList'));
// 加载远程Hook
const useUserService = lazy(() => import('user_center/useUserService'));
function App() {
return (
加载中... }>
加载中...