模块联邦架构设计与微前端场景分析
Webpack 5引入的模块联邦(Module Federation)能力允许多个独立构建的应用在运行时共享模块,无需重新打包即可加载远程组件。与single-spa等微前端方案相比,模块联邦不需要额外的框架依赖,直接利用Webpack原生能力实现跨应用模块共享。核心场景包括:多个独立团队开发的前端应用需要共享公共组件库、大型单体前端应用拆分为可独立部署的子应用、不同技术栈应用间的组件复用。模块联邦解决了npm包发版滞后和运行时按需加载的问题。
Host应用配置与远程模块加载
Host应用是消费远程模块的一方。通过ModuleFederationPlugin配置需要远程加载的模块及其来源。
// host/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index.ts',
mode: 'production',
output: {
publicPath: 'auto',
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
// 远程模块配置
remote_dashboard: 'dashboard_app@https://dashboard.example.com/remoteEntry.js',
remote_settings: 'settings_app@https://settings.example.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'] }
}
})
],
devServer: {
port: 3000,
historyApiFallback: true
}
};
在应用入口文件中,需要动态加载远程模块的入口文件,并等待共享依赖初始化完成后再渲染应用:
// host/src/index.ts
import('./bootstrap');
// host/src/bootstrap.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = createRoot(document.getElementById('root')!);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
入口文件拆分为index.ts和bootstrap.tsx是模块联邦的必要模式。Webpack需要先加载远程模块的依赖映射,再执行应用代码,import(‘./bootstrap’)确保了这一加载顺序。
Remote应用配置与模块导出
Remote应用是提供模块的一方。配置exposes字段声明哪些模块对外暴露:
// remote-dashboard/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
output: {
publicPath: 'auto',
filename: '[name].[contenthash].js'
},
plugins: [
new ModuleFederationPlugin({
name: 'dashboard_app',
filename: 'remoteEntry.js',
exposes: {
'./Dashboard': './src/components/Dashboard',
'./ChartWidget': './src/components/ChartWidget',
'./DataPanel': './src/components/DataPanel'
},
shared: {
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] }
}
})
]
};
在Host应用中使用React.lazy加载远程组件:
// host/src/App.tsx
import React, { Suspense, lazy } from 'react';
const Dashboard = lazy(() => import('remote_dashboard/Dashboard'));
const ChartWidget = lazy(() => import('remote_dashboard/ChartWidget'));
function App() {
return (
<div>
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
</Suspense>
<Suspense fallback={<div>加载图表...</div>}>
<ChartWidget />
</Suspense>
</div>
);
}
export default App;
共享依赖版本协商与单例模式
shared配置是模块联邦的关键机制。singleton: true确保React等核心库在整个应用中只加载一个实例,避免Hooks失效和状态隔离问题。requiredVersion指定所需版本范围,Webpack在运行时协商使用满足所有应用需求的版本。
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
eager: false // 非急切加载,运行时按需
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0'
},
'react-router-dom': {
singleton: true,
requiredVersion: '^6.0.0'
},
// 非单例共享,允许不同版本共存
lodash: {
singleton: false,
requiredVersion: '^4.0.0'
},
// 自定义共享库
'@company/ui-components': {
singleton: true,
requiredVersion: '^2.0.0',
import: false // 仅共享不导入
}
}
eager: true会将共享依赖打入主bundle,适用于首屏关键依赖。import: false表示该模块只声明共享但不引入,适合公共库不需要在当前应用中直接使用的情况。
路由级懒加载与独立部署方案
模块联邦天然支持路由级代码分割。配合React Router的动态路由配置,实现子应用按路由懒加载:
// host/src/routes.tsx
import { lazy } from 'react';
const routes = [
{
path: '/dashboard',
element: lazy(() => import('remote_dashboard/Dashboard'))
},
{
path: '/settings',
element: lazy(() => import('remote_settings/SettingsPanel'))
},
{
path: '/analytics',
element: lazy(() => import('remote_dashboard/DataPanel'))
}
];
每个Remote应用独立部署到各自的CDN路径,remoteEntry.js文件作为模块入口。部署时只需更新remoteEntry.js及其关联chunk文件,Host应用无需重新构建。版本管理策略:Remote应用在publicPath中使用版本号路径(如/v2.1.0/),实现灰度发布和快速回滚。
跨应用通信与状态共享方案
模块联邦不提供内置的状态共享机制。常用的跨应用通信方案有三种:基于CustomEvent的发布订阅、基于共享模块的Store注入、基于URL参数的状态传递。
// shared/event-bus.ts(作为shared模块被所有应用共享)
type EventHandler = (data: any) => void;
class EventBus {
private handlers = new Map<string, Set<EventHandler>>();
on(event: string, handler: EventHandler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
return () => this.handlers.get(event)?.delete(handler);
}
emit(event: string, data: any) {
this.handlers.get(event)?.forEach(h => h(data));
}
}
export const eventBus = new EventBus();
// Host应用发送事件
import { eventBus } from 'shared/event-bus';
eventBus.emit('user-login', { userId: '123', token: 'xxx' });
// Remote应用监听事件
import { eventBus } from 'shared/event-bus';
eventBus.on('user-login', (data) => {
console.log('用户已登录:', data.userId);
});
eventBus作为shared模块被所有应用共享同一实例,实现跨应用通信。对于复杂状态管理场景,可以将Zustand或Redux store作为shared模块导出,Host应用创建store实例,Remote应用通过shared依赖获取同一store引用。生产环境构建时需要确保shared模块的版本一致性,避免因版本差异导致实例隔离。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webpack5-mo-kuai-lian-bang-wei-qian-duan-jia-gou-kua-ying/