微前端架构设计模式完全指南
什么是微前端
微前端是一种将前端应用分解为更小、更简单的部分的架构模式。每个部分可以由不同的团队独立开发、测试和部署。
传统单体前端 vs 微前端架构:
传统单体前端:
┌─────────────────────────────────────────────────────────────┐
│ 大型单体应用 │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ 功能A + 功能B + 功能C + 功能D + 功能E + ... ││
│ │ 共享状态、共享依赖、共享构建 ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ 问题: │
│ • 代码耦合严重 │
│ • 构建时间长 │
│ • 部署风险高 │
│ • 团队协作困难 │
└─────────────────────────────────────────────────────────────┘
微前端架构:
┌─────────────────────────────────────────────────────────────┐
│ 容器/主应用 │
├─────────────┬─────────────┬─────────────┬───────────────────┤
│ 子应用 A │ 子应用 B │ 子应用 C │ 子应用 D │
│ (团队 A) │ (团队 B) │ (团队 C) │ (团队 D) │
│ Vue 3 │ React │ Angular │ Nuxt │
│ 独立部署 │ 独立部署 │ 独立部署 │ 独立部署 │
└─────────────┴─────────────┴─────────────┴───────────────────┘
优势:
• 技术栈无关
• 独立开发部署
• 增量升级
• 团队自治
何时需要微前端
适用场景评估:
✅ 适合微前端的场景:
• 大型企业级应用(100+ 页面)
• 多团队并行开发
• 需要集成遗留系统
• 不同业务线技术栈不统一
• 需要独立部署和发布
❌ 不适合微前端的场景:
• 小型项目(<50 页面)
• 单人或小团队开发
• 对性能要求极高
• 技术栈统一且稳定
• 业务耦合度高
微前端实现方案对比
主流方案对比
方案对比表:
┌──────────────────┬────────────────┬────────────────┬─────────────────┐
│ 方案 │ Module │ qiankun │ single-spa │
│ │ Federation │ │ │
├──────────────────┼────────────────┼────────────────┼─────────────────┤
│ 实现原理 │ Webpack 5 原生 │ JS 沙箱隔离 │ 路由劫持 │
│ 学习成本 │ 中 │ 低 │ 高 │
│ 隔离性 │ 弱 │ 强 │ 中 │
│ 性能 │ 优 │ 良 │ 良 │
│ 技术栈限制 │ Webpack 5+ │ 无限制 │ 无限制 │
│ 通信机制 │ 共享模块 │ 全局状态 │ 自定义事件 │
│ 适用场景 │ 新项目 │ 企业级应用 │ 需要高度自定义 │
└──────────────────┴────────────────┴────────────────┴─────────────────┘
Module Federation 方案
基本概念
Module Federation(模块联邦)是 Webpack 5 的内置功能,允许多个独立的构建组成一个应用。
// 核心概念
const concepts = {
// Host:消费远程模块的应用
host: '主应用,加载和使用其他应用暴露的模块',
// Remote:提供模块的应用
remote: '子应用,将自己的模块暴露给其他应用使用',
// Shared:共享依赖
shared: '多个应用共享的依赖,避免重复加载',
// Exposes:暴露的模块
exposes: '子应用向外暴露的模块',
// Remotes:远程模块配置
remotes: '主应用中配置的远程子应用'
};
主应用配置
// host-app/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
// 远程应用配置
remotes: {
// 格式:别名: '应用名@远程入口URL'
productApp: 'productApp@http://localhost:3001/remoteEntry.js',
userApp: 'userApp@http://localhost:3002/remoteEntry.js',
orderApp: 'orderApp@http://localhost:3003/remoteEntry.js'
},
// 共享依赖
shared: {
react: {
singleton: true, // 单例模式
requiredVersion: '^18.0.0',
eager: true // 立即加载
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
eager: true
},
// 共享状态管理
zustand: {
singleton: true,
requiredVersion: '^4.0.0'
}
}
})
]
};
子应用配置
// product-app/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'productApp',
// 远程入口文件名
filename: 'remoteEntry.js',
// 暴露的模块
exposes: {
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail',
'./ProductStore': './src/stores/productStore'
},
// 共享依赖
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
zustand: { singleton: true, requiredVersion: '^4.0.0' }
}
})
]
};
动态加载远程模块
// host-app/src/utils/loadRemote.ts
// 动态加载远程模块
async function loadRemoteModule<T>(
remoteName: string,
modulePath: string
): Promise<T> {
// 等待远程容器初始化
await __webpack_init_sharing__('default');
const container = (window as any)[remoteName];
// 初始化远程容器
await container.init(__webpack_share_scopes__.default);
// 获取模块工厂
const factory = await container.get(modulePath);
// 返回模块
return factory();
}
// 使用示例
const ProductList = React.lazy(() =>
loadRemoteModule('productApp', './ProductList')
);
// 在组件中使用
function App() {
return (
<Suspense fallback={<Loading />}>
<ProductList />
</Suspense>
);
}
运行时动态配置
// host-app/src/bootstrap.ts
// 运行时获取远程应用配置
interface RemoteConfig {
name: string;
url: string;
scope: string;
}
async function loadRemoteConfig(): Promise<RemoteConfig[]> {
const response = await fetch('/api/micro-apps/config');
return response.json();
}
async function loadRemoteContainer(remote: RemoteConfig) {
// 动态创建 script 标签
const script = document.createElement('script');
script.src = remote.url;
script.type = 'text/javascript';
script.async = true;
return new Promise<void>((resolve, reject) => {
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(new Error(`Failed to load remote: ${remote.name}`));
};
document.head.appendChild(script);
});
}
// 初始化所有远程应用
async function initRemotes() {
const remotes = await loadRemoteConfig();
await Promise.all(
remotes.map(remote => loadRemoteContainer(remote))
);
console.log('All remotes loaded');
}
qiankun 方案
为什么选择 qiankun
qiankun 是蚂蚁金服开源的微前端框架,基于 single-spa 封装,提供了开箱即用的功能:
- JS 沙箱隔离
- CSS 样式隔离
- 预加载
- 全局状态管理
主应用配置
// main-app/src/main.ts
import { registerMicroApps, start, initGlobalState } from 'qiankun';
// 注册子应用
registerMicroApps([
{
name: 'product-app',
entry: '//localhost:3001',
container: '#subapp-container',
activeRule: '/products',
props: {
// 传递给子应用的数据
globalStore: store,
utils: { request, message }
}
},
{
name: 'user-app',
entry: '//localhost:3002',
container: '#subapp-container',
activeRule: '/user',
loader: (loading) => {
// 加载状态回调
console.log('Loading:', loading);
}
},
{
name: 'order-app',
entry: '//localhost:3003',
container: '#subapp-container',
activeRule: '/orders'
}
], {
// 生命周期钩子
beforeLoad: [
async (app) => {
console.log('Before load:', app.name);
}
],
beforeMount: [
async (app) => {
console.log('Before mount:', app.name);
}
],
afterUnmount: [
async (app) => {
console.log('After unmount:', app.name);
}
]
});
// 全局状态管理
const initialState = {
user: null,
token: null,
theme: 'light'
};
const actions = initGlobalState(initialState);
// 监听全局状态变化
actions.onGlobalStateChange((state, prevState) => {
console.log('Global state changed:', state, prevState);
});
// 修改全局状态
actions.setGlobalState({ theme: 'dark' });
// 启动 qiankun
start({
prefetch: 'all', // 预加载策略
sandbox: {
strictStyleIsolation: true, // 严格样式隔离(Shadow DOM)
experimentalStyleIsolation: true // 实验性样式隔离(作用域 CSS)
}
});
子应用改造
// product-app/src/main.ts (Vue 3 子应用)
import { createApp, App as VueApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './routes';
let app: VueApp | null = null;
let router = null;
// 渲染函数
function render(props: any = {}) {
const { container } = props;
router = createRouter({
history: createWebHistory(
(window as any).__POWERED_BY_QIANKUN__ ? '/products' : '/'
),
routes
});
app = createApp(App);
app.use(router);
// 挂载到指定容器或 #app
const mountEl = container
? container.querySelector('#app')
: document.getElementById('app');
app.mount(mountEl);
}
// 独立运行时直接渲染
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
// qiankun 生命周期钩子
export async function bootstrap() {
console.log('Product app bootstrapped');
}
export async function mount(props: any) {
console.log('Product app mounting with props:', props);
// 接收主应用传递的数据
if (props.globalStore) {
// 使用全局 store
}
// 监听全局状态
props.onGlobalStateChange?.((state: any) => {
console.log('Global state in product app:', state);
});
render(props);
}
export async function unmount() {
console.log('Product app unmounting');
if (app) {
app.unmount();
app = null;
}
if (router) {
router = null;
}
}
Vite 子应用配置
// product-app/vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';
export default defineConfig({
plugins: [
vue(),
qiankun('product-app', {
useDevMode: true
})
],
server: {
port: 3001,
cors: true,
origin: 'http://localhost:3001'
},
build: {
target: 'esnext',
minify: 'terser'
}
});
应用间通信
全局状态通信
// shared/globalState.ts
interface GlobalState {
user: User | null;
token: string | null;
permissions: string[];
theme: 'light' | 'dark';
}
// 使用发布订阅模式
class GlobalStateManager {
private state: GlobalState;
private listeners: Set<(state: GlobalState) => void>;
constructor(initialState: GlobalState) {
this.state = initialState;
this.listeners = new Set();
}
getState(): GlobalState {
return { ...this.state };
}
setState(partial: Partial<GlobalState>) {
this.state = { ...this.state, ...partial };
this.notify();
}
subscribe(listener: (state: GlobalState) => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notify() {
this.listeners.forEach(listener => listener(this.getState()));
}
}
// 单例
export const globalState = new GlobalStateManager({
user: null,
token: null,
permissions: [],
theme: 'light'
});
事件总线通信
// shared/eventBus.ts
type EventCallback = (...args: any[]) => void;
class EventBus {
private events: Map<string, Set<EventCallback>>;
constructor() {
this.events = new Map();
}
on(event: string, callback: EventCallback) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
off(event: string, callback: EventCallback) {
this.events.get(event)?.delete(callback);
}
emit(event: string, ...args: any[]) {
this.events.get(event)?.forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
once(event: string, callback: EventCallback) {
const wrappedCallback = (...args: any[]) => {
callback(...args);
this.off(event, wrappedCallback);
};
this.on(event, wrappedCallback);
}
}
// 全局事件总线
export const eventBus = new EventBus();
// 使用示例
// 子应用 A 发送事件
eventBus.emit('user:login', { userId: 123, name: 'John' });
// 子应用 B 监听事件
eventBus.on('user:login', (user) => {
console.log('User logged in:', user);
});
基于 Props 的通信
// 主应用传递共享上下文
registerMicroApps([
{
name: 'sub-app',
entry: '//localhost:3001',
container: '#container',
activeRule: '/sub',
props: {
// 共享服务
services: {
http: axiosInstance,
auth: authService,
notification: notificationService
},
// 共享状态
getGlobalState: () => globalState.getState(),
setGlobalState: (state) => globalState.setState(state),
// 事件通信
eventBus,
// 导航方法
navigateTo: (path: string) => {
router.push(path);
}
}
}
]);
// 子应用使用
export async function mount(props) {
const { services, eventBus, navigateTo } = props;
// 使用共享的 HTTP 服务
const data = await services.http.get('/api/data');
// 发送事件
eventBus.emit('data:loaded', data);
// 跨应用导航
navigateTo('/other-app/page');
}
样式隔离方案
Shadow DOM 隔离
// qiankun 配置严格样式隔离
start({
sandbox: {
strictStyleIsolation: true // 使用 Shadow DOM
}
});
// 注意:Shadow DOM 会导致一些问题
// 1. 弹窗组件可能无法正常工作
// 2. 全局样式无法穿透
// 3. 第三方组件库可能不兼容
CSS Scoped 隔离
// 实验性样式隔离(推荐)
start({
sandbox: {
experimentalStyleIsolation: true
}
});
// 原理:为子应用的所有样式添加特殊选择器前缀
// .container { color: red; }
// 变为:
// div[data-qiankun="sub-app"] .container { color: red; }
命名空间约定
// 子应用样式使用命名空间
// product-app/styles/main.scss
$namespace: 'product-app';
.#{$namespace} {
// 所有样式都在命名空间下
&-container {
padding: 20px;
}
&-header {
font-size: 24px;
}
// 组件样式
.button {
// 自动被命名空间限定
}
}
// 或使用 CSS Modules
// ProductList.module.css
.container { ... }
.item { ... }
路由管理
主应用路由配置
// main-app/src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
component: MainLayout,
children: [
{
path: '',
component: HomePage
},
// 子应用路由占位
{
path: '/products/:pathMatch(.*)*',
component: MicroAppContainer,
meta: { microApp: 'product-app' }
},
{
path: '/user/:pathMatch(.*)*',
component: MicroAppContainer,
meta: { microApp: 'user-app' }
},
{
path: '/orders/:pathMatch(.*)*',
component: MicroAppContainer,
meta: { microApp: 'order-app' }
}
]
},
{
path: '/login',
component: LoginPage
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.microApp) {
// 微应用路由处理
console.log('Navigating to micro app:', to.meta.microApp);
}
next();
});
export default router;
子应用路由配置
// product-app/src/router/index.ts
// 判断是否在 qiankun 环境中
const isQiankun = (window as any).__POWERED_BY_QIANKUN__;
// 动态设置基础路径
const baseUrl = isQiankun ? '/products' : '/';
const routes = [
{
path: '/',
component: ProductLayout,
children: [
{ path: '', component: ProductList },
{ path: 'detail/:id', component: ProductDetail },
{ path: 'category/:category', component: ProductCategory }
]
}
];
export function createAppRouter() {
return createRouter({
history: createWebHistory(baseUrl),
routes
});
}
共享依赖管理
依赖外部化
// 使用 externals 共享依赖
// webpack.config.js
module.exports = {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
pinia: 'Pinia'
}
};
// 在 HTML 中加载共享依赖
// index.html
<script src="https://cdn.example.com/vue@3.3.0/dist/vue.global.prod.js"></script>
<script src="https://cdn.example.com/vue-router@4.2.0/dist/vue-router.global.prod.js"></script>
共享依赖版本管理
// shared-deps/package.json
{
"name": "@myorg/shared-deps",
"version": "1.0.0",
"dependencies": {
"vue": "^3.3.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"axios": "^1.4.0"
},
"peerDependencies": {
"vue": "^3.3.0"
}
}
// 子应用使用共享依赖包
// product-app/package.json
{
"dependencies": {
"@myorg/shared-deps": "^1.0.0"
}
}
部署策略
独立部署架构
微前端独立部署架构:
┌─────────────────┐
│ CDN / 负载 │
│ 均衡器 │
└────────┬────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
↓ ↓ ↓
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 主应用 │ │ 产品子应用 │ │ 用户子应用 │
│ 端口: 80 │ │ 端口: 3001 │ │ 端口: 3002 │
│ 路径: / │ │ 路径: /prod │ │ 路径: /user │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└────────────────────┼────────────────────┘
│
┌────────┴────────┐
│ 容器编排 │
│ (K8s/Docker) │
└─────────────────┘
Nginx 配置
# nginx.conf
http {
upstream main-app {
server localhost:3000;
}
upstream product-app {
server localhost:3001;
}
upstream user-app {
server localhost:3002;
}
server {
listen 80;
server_name example.com;
# 主应用
location / {
proxy_pass http://main-app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 产品子应用静态资源
location /product-app/ {
alias /var/www/product-app/;
try_files $uri $uri/ /product-app/index.html;
# 允许跨域
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
# 用户子应用静态资源
location /user-app/ {
alias /var/www/user-app/;
try_files $uri $uri/ /user-app/index.html;
add_header Access-Control-Allow-Origin *;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
Docker Compose 部署
# docker-compose.yml
version: '3.8'
services:
main-app:
build: ./packages/main-app
ports:
- "3000:80"
environment:
- NODE_ENV=production
depends_on:
- product-app
- user-app
networks:
- micro-frontend
product-app:
build: ./packages/product-app
ports:
- "3001:80"
environment:
- NODE_ENV=production
networks:
- micro-frontend
user-app:
build: ./packages/user-app
ports:
- "3002:80"
environment:
- NODE_ENV=production
networks:
- micro-frontend
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- main-app
- product-app
- user-app
networks:
- micro-frontend
networks:
micro-frontend:
driver: bridge
性能优化
预加载策略
// 空闲时预加载子应用
import { prefetchApps } from 'qiankun';
// 在主应用加载完成后预加载
window.addEventListener('load', () => {
// 使用 requestIdleCallback 在空闲时执行
requestIdleCallback(() => {
prefetchApps([
{ name: 'product-app', entry: '//localhost:3001' },
{ name: 'user-app', entry: '//localhost:3002' }
]);
});
});
// 或者根据用户行为预加载
document.querySelector('.products-link')?.addEventListener('mouseenter', () => {
prefetchApps([{ name: 'product-app', entry: '//localhost:3001' }]);
});
缓存优化
// 子应用资源缓存
const cache = new Map();
async function loadApp(name: string, entry: string) {
// 检查缓存
if (cache.has(name)) {
return cache.get(name);
}
// 加载并缓存
const app = await fetchAppResources(entry);
cache.set(name, app);
return app;
}
// 使用 Service Worker 缓存
// sw.js
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// 缓存子应用静态资源
if (url.pathname.includes('/micro-apps/')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('micro-apps-v1').then((cache) => {
cache.put(event.request, clone);
});
return response;
});
})
);
}
});
最佳实践总结
微前端架构最佳实践:
架构设计:
✓ 明确边界,按业务域划分子应用
✓ 保持子应用的独立性和自治性
✓ 统一技术规范和通信协议
✓ 建立共享组件库和工具包
技术选型:
✓ 新项目考虑 Module Federation
✓ 遗留系统集成使用 qiankun
✓ 统一依赖版本管理
✓ 做好技术栈兼容性评估
通信设计:
✓ 最小化应用间通信
✓ 使用事件总线或全局状态
✓ 定义清晰的通信接口
✓ 避免直接 DOM 操作跨应用
样式管理:
✓ 使用 CSS Modules 或命名空间
✓ 评估 Shadow DOM 的适用性
✓ 统一设计系统和样式变量
✓ 避免全局样式污染
性能优化:
✓ 实施预加载策略
✓ 共享公共依赖
✓ 使用缓存减少重复加载
✓ 监控各子应用性能指标
部署运维:
✓ 实现独立构建和部署
✓ 配置 CORS 和资源跨域
✓ 实施灰度发布策略
✓ 建立监控和告警机制
微前端不是银弹,它适合特定的场景和团队规模。在采用之前,要充分评估其带来的复杂性是否值得,以及团队是否具备相应的技术能力来驾驭这种架构。


