下面,我们来系统的梳理关于 微前端:Module Federation 的基本知识点:
一、微前端与 Module Federation 概述
1.1 什么是微前端?
微前端是一种将前端应用分解为更小、更简单、可以独立开发、测试和部署的架构模式。
1.2 Module Federation 的核心价值
特性 | 传统微前端 | Module Federation |
---|---|---|
部署独立性 | ✅ | ✅ |
技术栈无关 | ✅ | ✅ |
运行时集成 | ❌ | ✅ |
共享依赖 | 困难 | 内置支持 |
开发体验 | 复杂 | 优秀 |
1.3 架构对比
传统微前端 构建时集成 iframe 隔离 重复依赖 Module Federation 运行时集成 依赖共享 动态加载
二、Module Federation 核心概念
2.1 基本术语
- Host(主机):消费其他应用模块的应用
- Remote(远程):被其他应用消费的应用
- Exposes(暴露):Remote 应用对外提供的模块
- Remotes(远程模块):Host 应用从 Remote 消费的模块
- Shared(共享):应用间共享的依赖
2.2 工作原理
Host 应用 Remote 应用 浏览器 加载 Host 应用 执行 Host 代码 动态请求 Remote 模块 返回 Remote 模块 集成 Remote 模块 Host 应用 Remote 应用 浏览器
三、Webpack 5 Module Federation 配置
3.1 基础配置结构
javascript
// 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 },
},
}),
],
};
javascript
// 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 完整配置示例
javascript
// 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 应用配置
javascript
// 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 应用组件
javascript
// 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 错误边界组件
javascript
// 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 跨应用路由配置
javascript
// 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 状态管理集成
javascript
// 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 配置
javascript
// 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 性能优化配置
javascript
// 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 开发服务器配置
javascript
// 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 支持
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 单元测试配置
javascript
// 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 集成测试
javascript
// 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 生产环境配置
javascript
// 环境特定的配置
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 部署策略
bash
# 部署脚本示例
#!/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 性能监控
javascript
// 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 错误追踪
javascript
// 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 配置
html
<!-- 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 安全头配置
javascript
// 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 版本冲突解决
javascript
// 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 循环依赖处理
javascript
// 使用异步初始化避免循环依赖
new ModuleFederationPlugin({
name: 'app1',
exposes: { './Component': './src/Component' },
shared: {
react: { singleton: true, eager: false },
},
}),
十三、总结
Module Federation 为微前端架构提供了强大的运行时模块共享能力,具有以下优势:
核心价值
- 真正的独立部署:各个应用可以独立开发、测试和部署
- 运行时集成:不需要重新构建整个应用
- 依赖共享:避免重复加载相同的依赖
- 技术栈无关:支持不同框架的应用集成
- 渐进式采用:可以逐步迁移到微前端架构
适用场景
- 大型企业应用:多个团队协作开发的大型项目
- 遗留系统迁移:逐步替换老旧系统
- 多框架共存:React、Vue、Angular 混合开发
- 独立部署需求:不同功能模块需要独立发布
决策考虑
项目需求 评估因素 团队结构 技术栈多样性 部署独立性需求 性能要求 多个独立团队 多种技术栈 独立部署需求 高性能要求 适合 Module Federation