详细介绍:Module Federation
下面,我们来系统的梳理关于 微前端:Module Federation 的基本知识点:
一、微前端与 Module Federation 概述
1.1 什么是微前端?
微前端是一种将前端应用分解为更小、更简单、可以独立开发、测试和部署的架构模式。
1.2 Module Federation 的核心价值
| 特性 | 传统微前端 | Module Federation |
|---|---|---|
| 部署独立性 | ✅ | ✅ |
| 技术栈无关 | ✅ | ✅ |
| 运行时集成 | ❌ | ✅ |
| 共享依赖 | 困难 | 内置支持 |
| 开发体验 | 复杂 | 优秀 |
1.3 架构对比
二、Module Federation 核心概念
2.1 基本术语
- Host(主机):消费其他应用模块的应用
- Remote(远程):被其他应用消费的应用
- Exposes(暴露):Remote 应用对外提供的模块
- Remotes(远程模块):Host 应用从 Remote 消费的模块
- Shared(共享):应用间共享的依赖
2.2 工作原理
三、Webpack 5 Module Federation 配置
3.1 基础配置结构
// webpack.config.js (Host 应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: {
singleton: true, eager: true
},
'react-dom': {
singleton: true, eager: true
},
},
}),
],
};
// webpack.config.js (Remote 应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./Header': './src/components/Header',
},
shared: {
react: {
singleton: true
},
'react-dom': {
singleton: true
},
},
}),
],
};
3.2 完整配置示例
// host-app/webpack.config.js
const { ModuleFederationPlugin
} = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
// ...其他配置
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
remoteApp: `remoteApp@${process.env.REMOTE_APP_URL || 'http://localhost:3001'
}/remoteEntry.js`,
authApp: `authApp@${process.env.AUTH_APP_URL || 'http://localhost:3002'
}/remoteEntry.js`,
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
eager: true,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
eager: true,
},
'react-router-dom': {
singleton: true,
requiredVersion: deps['react-router-dom'],
},
},
}),
],
};
四、React 应用集成实战
4.1 Host 应用配置
// src/bootstrap.js (Host 入口)
import('./bootstrap');
// src/App.js
import React, { Suspense
} from 'react';
import ErrorBoundary from './components/ErrorBoundary';
// 动态导入 Remote 组件
const RemoteButton = React.lazy(() =>
import('remoteApp/Button'));
const RemoteHeader = React.lazy(() =>
import('remoteApp/Header'));
function App() {
return (
<div>
<ErrorBoundary>
<Suspense fallback={
<div>Loading Remote Components...<
/div>
}>
<RemoteHeader title="Host Application" />
<
/Suspense>
<
/ErrorBoundary>
<main>
<h1>Host App Content<
/h1>
<ErrorBoundary>
<Suspense fallback={
<div>Loading Button...<
/div>
}>
<RemoteButton onClick={
() => console.log('Clicked from remote')
}>
Remote Button
<
/RemoteButton>
<
/Suspense>
<
/ErrorBoundary>
<
/main>
<
/div>
);
}
export default App;
4.2 Remote 应用组件
// remote-app/src/components/Button.js
import React from 'react';
import './Button.css';
const Button = ({ children, onClick, variant = 'primary'
}) =>
{
return (
<button
className={
`btn btn-${variant
}`
}
onClick={onClick
}
>
{children
}
<
/button>
);
};
export default Button;
// remote-app/src/components/Header.js
import React from 'react';
const Header = ({ title
}) =>
{
return (
<header style={
{
padding: '1rem', background: '#f0f0f0'
}
}>
<h1>
{title
}<
/h1>
<nav>
<a href="/">Home<
/a>
| <a href="/about">About<
/a>
<
/nav>
<
/header>
);
};
export default Header;
4.3 错误边界组件
// src/components/ErrorBoundary.js
import React from 'react';
class ErrorBoundary
extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false, error: null
};
}
static getDerivedStateFromError(error) {
return {
hasError: true, error
};
}
componentDidCatch(error, errorInfo) {
console.error('Remote component error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={
{
padding: '1rem', border: '1px solid #ff6b6b', color: '#ff6b6b'
}
}>
<h3>Remote Component Failed to Load<
/h3>
<p>
{
this.state.error?.message
}<
/p>
<button onClick={
() =>
this.setState({
hasError: false, error: null
})
}>
Retry
<
/button>
<
/div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
五、路由集成与状态管理
5.1 跨应用路由配置
// host-app/src/App.js
import { BrowserRouter as Router, Routes, Route, Link
} from 'react-router-dom';
const RemoteHome = React.lazy(() =>
import('remoteApp/HomePage'));
const RemoteAbout = React.lazy(() =>
import('remoteApp/AboutPage'));
function App() {
return (
<Router>
<nav>
<Link to="/">Home<
/Link>
|
<Link to="/about">About<
/Link>
|
<Link to="/remote">Remote Home<
/Link>
|
<Link to="/remote/about">Remote About<
/Link>
<
/nav>
<Routes>
<Route path="/" element={
<HomePage />
} />
<Route path="/about" element={
<AboutPage />
} />
<Route
path="/remote/*"
element={
<Suspense fallback={
<div>Loading Remote App...<
/div>
}>
<RemoteAppRouter />
<
/Suspense>
}
/>
<
/Routes>
<
/Router>
);
}
// Remote 应用的路由包装器
function RemoteAppRouter() {
return (
<Routes>
<Route path="/" element={
<RemoteHome />
} />
<Route path="/about" element={
<RemoteAbout />
} />
<
/Routes>
);
}
5.2 状态管理集成
// shared-state/src/store.js (共享状态库)
import { createStore
} from 'redux';
const initialState = {
user: null,
cart: [],
theme: 'light'
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case 'SET_USER':
return {
...state, user: action.payload
};
case 'ADD_TO_CART':
return {
...state, cart: [...state.cart, action.payload]
};
case 'SET_THEME':
return {
...state, theme: action.payload
};
default:
return state;
}
}
export const store = createStore(rootReducer);
// 在各个应用的 webpack 配置中共享
shared: {
'shared-state': {
singleton: true,
requiredVersion: require('../shared-state/package.json').version
}
}
六、高级配置与优化
6.1 动态 Remote 配置
// src/utils/dynamicRemotes.js
class DynamicRemoteLoader
{
constructor() {
this.remotes = new Map();
}
async loadRemote(remoteName, remoteUrl) {
if (this.remotes.has(remoteName)) {
return this.remotes.get(remoteName);
}
try {
await this.__loadRemoteEntry(remoteUrl);
const container = window[remoteName];
await container.init(__webpack_share_scopes__.default);
this.remotes.set(remoteName, container);
return container;
} catch (error) {
console.error(`Failed to load remote ${remoteName
}:`, error);
throw error;
}
}
async __loadRemoteEntry(url) {
return new Promise((resolve, reject) =>
{
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
}
export const remoteLoader = new DynamicRemoteLoader();
// 使用动态 Remote
const loadRemoteComponent = async (remoteName, modulePath) =>
{
const remoteConfig = await fetch('/api/remotes-config').then(r => r.json());
const remoteUrl = remoteConfig[remoteName];
await remoteLoader.loadRemote(remoteName, remoteUrl);
const container = window[remoteName];
const factory = await container.get(modulePath);
return factory();
};
6.2 性能优化配置
// webpack.config.js 优化配置
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: `promise new Promise(resolve => {
const remoteUrl = new URL('./remoteEntry.js', import.meta.url);
const script = document.createElement('script');
script.src = remoteUrl;
script.onload = () => {
const proxy = {
get: (request) => window.remoteApp.get(request),
init: (arg) => {
try {
return window.remoteApp.init(arg);
} catch (e) {
console.log('Remote container already initialized');
}
}
};
resolve(proxy);
};
document.head.appendChild(script);
})`,
},
shared: {
react: {
singleton: true,
requiredVersion: deps.react,
eager: false, // 延迟加载
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
eager: false,
},
},
}),
七、开发环境与工具链
7.1 开发服务器配置
// host-app/webpack.config.js
devServer: {
port: 3000,
historyApiFallback: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
allowedHosts: 'all',
},
// remote-app/webpack.config.js
devServer: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
},
7.2 TypeScript 支持
// types/federation.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.css';
declare module 'remoteApp/Button' {
import { ComponentType
} from 'react';
interface ButtonProps {
children: React.ReactNode;
onClick?: () =>
void;
variant?: 'primary' | 'secondary';
}
const Button: ComponentType<ButtonProps>
;
export default Button;
}
declare module 'remoteApp/Header' {
import { ComponentType
} from 'react';
interface HeaderProps {
title: string;
}
const Header: ComponentType<HeaderProps>
;
export default Header;
}
八、测试策略
8.1 单元测试配置
// jest.config.js
module.exports = {
moduleNameMapper: {
'^remoteApp/(.*)$': '<rootDir>/__mocks__/remoteApp/$1',
},
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};
// __mocks__/remoteApp/Button.js
import React from 'react';
const MockButton = ({ children, onClick
}) =>
(
<button onClick={onClick
} data-testid="mock-button">
{children
}
<
/button>
);
export default MockButton;
8.2 集成测试
// src/__tests__/App.integration.js
import { render, screen, waitFor
} from '@testing-library/react';
import * as RemoteApp from 'remoteApp/Button';
jest.mock('remoteApp/Button', () =>
({
__esModule: true,
default: jest.fn(() =>
<button>Mock Remote Button<
/button>
)
}));
describe('App Integration', () =>
{
it('should load remote component', async () =>
{
render(<App />
);
await waitFor(() =>
{
expect(screen.getByText('Mock Remote Button')).toBeInTheDocument();
});
expect(RemoteApp.default).toHaveBeenCalled();
});
});
九、部署与生产环境
9.1 生产环境配置
// 环境特定的配置
const getRemoteUrl = (appName) =>
{
const baseUrl = process.env.NODE_ENV === 'production'
? 'https://cdn.yourcompany.com'
: 'http://localhost';
const ports = {
remoteApp: process.env.NODE_ENV === 'production' ? '' : ':3001',
authApp: process.env.NODE_ENV === 'production' ? '' : ':3002',
};
return `${baseUrl
}${ports[appName]
}/${appName
}/remoteEntry.js`;
};
new ModuleFederationPlugin({
remotes: {
remoteApp: `remoteApp@${getRemoteUrl('remoteApp')
}`,
authApp: `authApp@${getRemoteUrl('authApp')
}`,
},
}),
9.2 CDN 部署策略
# 部署脚本示例
#!/bin/bash
# 构建各个应用
cd remote-app &&
npm run build &&
cd ..
cd host-app &&
npm run build &&
cd ..
# 上传到 CDN
aws s3 sync remote-app/dist/ s3://your-bucket/remote-app/ --delete
aws s3 sync host-app/dist/ s3://your-bucket/host-app/ --delete
# 刷新 CDN 缓存
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/remote-app/*" "/host-app/*"
十、监控与错误处理
10.1 性能监控
// src/utils/performanceMonitor.js
export const monitorRemoteLoading = () =>
{
const originalGet = window.webpackChunkhostApp.get;
window.webpackChunkhostApp.get = function(remoteName) {
const startTime = performance.now();
return originalGet.apply(this, arguments).then(container =>
{
const loadTime = performance.now() - startTime;
console.log(`Remote ${remoteName
} loaded in ${loadTime
}ms`);
// 发送到监控系统
sendMetricsToMonitoring({
event: 'remote_loaded',
remote: remoteName,
duration: loadTime,
timestamp: Date.now()
});
return container;
});
};
};
// 在应用启动时调用
monitorRemoteLoading();
10.2 错误追踪
// src/utils/errorHandler.js
export const setupErrorHandling = () =>
{
// 捕获远程模块加载错误
window.addEventListener('error', (event) =>
{
if (event.filename && event.filename.includes('remoteEntry.js')) {
const remoteName = event.filename.split('/')[2];
logError({
type: 'REMOTE_LOAD_ERROR',
remote: remoteName,
message: event.error?.message,
stack: event.error?.stack
});
}
});
// 捕获未处理的 Promise rejection
window.addEventListener('unhandledrejection', (event) =>
{
if (event.reason && event.reason.message.includes('Remote')) {
logError({
type: 'REMOTE_MODULE_ERROR',
message: event.reason.message,
stack: event.reason.stack
});
}
});
};
// 初始化错误处理
setupErrorHandling();
十一、安全最佳实践
11.1 CSP 配置
<!-- public/index.html -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' http://localhost:3001 http://localhost:3002;
style-src 'self' 'unsafe-inline';
connect-src 'self' http://localhost:3001 http://localhost:3002;">
11.2 安全头配置
// Express 服务器配置
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "trusted-cdn.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
connectSrc: ["'self'", "api.yourdomain.com"],
},
},
crossOriginEmbedderPolicy: false,
}));
十二、常见问题与解决方案
12.1 版本冲突解决
// webpack.config.js
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
strictVersion: true,
eager: true,
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
strictVersion: true,
eager: true,
},
}
12.2 循环依赖处理
// 使用异步初始化避免循环依赖
new ModuleFederationPlugin({
name: 'app1',
exposes: {
'./Component': './src/Component'
},
shared: {
react: {
singleton: true, eager: false
},
},
}),
十三、总结
Module Federation 为微前端架构提供了强大的运行时模块共享能力,具有以下优势:
核心价值
- 真正的独立部署:各个应用可以独立开发、测试和部署
- 运行时集成:不需要重新构建整个应用
- 依赖共享:避免重复加载相同的依赖
- 技术栈无关:支持不同框架的应用集成
- 渐进式采用:可以逐步迁移到微前端架构
适用场景
- 大型企业应用:多个团队协作开发的大型项目
- 遗留系统迁移:逐步替换老旧系统
- 多框架共存:React、Vue、Angular 混合开发
- 独立部署需求:不同功能模块需要独立发布
浙公网安备 33010602011771号