微前端不是「解决所有问题的银弹」,而是「在巨石应用(Monolith)崩溃前的架构解药」。 当团队规模超过 50 人、技术栈需要渐进式升级、或需要独立部署多个业务线时,微前端值得考虑。但它的代价是额外的复杂度和运行时开销,需审慎评估。
一、为什么需要微前端
1.1 巨石应用的问题
单一仓库单体应用(Monolith)的痛点:
├── 构建时间 10 分钟+ → 开发体验差
├── 任何小改动需要全量回归测试
├── 技术栈锁定(无法渐进式升级 Vue2→Vue3)
├── 多个业务线团队互相阻塞代码合并
├── 发布窗口冲突(A 团队想发,B 团队还没测完)
└── 代码库 50 万行+,新人上手周期 2 个月
1.2 微前端的核心价值
| 价值 | 说明 |
|---|
| 技术栈无关 | 子应用可用不同框架(Vue2/Vue3/React/Angular) |
| 独立部署 | 每个子应用有自己的 CI/CD 流水线 |
| 团队自治 | 业务线团队独立开发、测试、发布 |
| 渐进式重构 | 老系统逐步迁移,而非重写 |
| 故障隔离 | 单个应用崩溃不拖垮整个系统 |
二、微前端架构模式对比
2.1 四种主要方案
| 方案 | 原理 | 优点 | 缺点 | 代表 |
|---|
| Module Federation | 运行时模块共享 | 真正的 JS 共享、依赖去重 | 构建配置复杂 | Webpack 5 / Vite |
| iframe | 独立文档上下文 | 完美隔离、简单 | 路由同步难、弹窗遮罩问题、体验差 | 传统方案 |
| Web Components | 标准组件化 | 原生标准、框架无关 | 生态不成熟、通信复杂 | Lit / Stencil |
| JS 沙箱(qiankun) | 动态加载 + 沙箱隔离 | 配置简单、生态成熟 | 基于 eval,性能一般 | qiankun / single-spa |
2.2 选型决策树
需要微前端吗?
├── 团队 < 20 人 + 单一技术栈?
│ └── 是 → 不需要微前端,用 Monorepo 即可
│ └── 否 → 继续
├── 必须多技术栈共存(Vue2 + Vue3 + React)?
│ └── 是 → qiankun / single-spa
│ └── 否 → 继续
├── 追求极致运行时性能 + 模块共享?
│ └── 是 → Module Federation(Webpack 5 / Vite)
│ └── 否 → 继续
├── 需要最简单配置 + 快速上线?
│ └── 是 → qiankun(国内生态最好)
│ └── 否 → single-spa
└── 长期看好 Web 标准?
└── 是 → Web Components(长期但当前生态弱)
三、Module Federation 深度解析
3.1 核心概念
Module Federation 角色:
├── Host(基座/入口应用)
│ └── 消费远程模块,负责路由整合、公共布局
├── Remote(子应用/远程模块)
│ └── 暴露自己的组件/页面,被 Host 消费
└── Shared(共享依赖)
└── React/Vue 等库只加载一次,所有应用共用
3.2 Webpack 5 配置
// 基座应用(Host)webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
// 远程应用名: '远程应用名@远程入口URL'
dashboard: 'dashboard@https://dashboard.example.com/remoteEntry.js',
settings: 'settings@https://settings.example.com/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
vue: { singleton: true, requiredVersion: '^3.4.0' },
},
}),
],
};
// 子应用(Remote)webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js', // 暴露的入口文件
exposes: {
'./App': './src/App', // 暴露整个应用
'./Widget': './src/components/Widget', // 暴露单个组件
'./utils': './src/utils', // 暴露工具模块
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
// 基座消费远程模块
import { lazy, Suspense } from 'react';
// 动态导入远程模块
const DashboardApp = lazy(() => import('dashboard/App'));
const SettingsApp = lazy(() => import('settings/App'));
function Layout() {
return (
<div className="layout">
<nav>{/* 全局导航 */}</nav>
<main>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard/*" element={<DashboardApp />} />
<Route path="/settings/*" element={<SettingsApp />} />
</Routes>
</Suspense>
</main>
</div>
);
}
3.3 Vite Module Federation
npm install -D @originjs/vite-plugin-federation
// Host vite.config.ts
import federation from '@originjs/vite-plugin-federation';
export default {
plugins: [
federation({
name: 'host',
remotes: {
remote_app: 'http://localhost:3001/assets/remoteEntry.js',
},
shared: ['vue', 'vue-router', 'pinia']
})
],
build: {
target: 'esnext', // 必须指定
minify: false,
cssCodeSplit: false
}
};
// Remote vite.config.ts
import federation from '@originjs/vite-plugin-federation';
export default {
plugins: [
federation({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button.vue',
'./App': './src/App.vue'
},
shared: ['vue', 'vue-router']
})
],
build: {
target: 'esnext',
minify: false,
cssCodeSplit: true
}
};
四、qiankun 实战
4.1 基座配置
// main/src/main.ts(Vue 3 基座)
import { createApp } from 'vue';
import { registerMicroApps, start } from 'qiankun';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router).mount('#app');
// 注册微应用
registerMicroApps([
{
name: 'vue3-app',
entry: '//localhost:3001',
container: '#micro-app-container',
activeRule: '/vue3',
props: { brand: 'MyCompany' }, // 传递数据给子应用
},
{
name: 'react-app',
entry: '//localhost:3002',
container: '#micro-app-container',
activeRule: '/react',
},
{
name: 'legacy-app', // 甚至可以是纯 HTML
entry: '//localhost:3003',
container: '#micro-app-container',
activeRule: '/legacy',
},
], {
// 生命周期钩子
beforeLoad: (app) => console.log('before load', app.name),
afterMount: (app) => console.log('after mount', app.name),
});
start({
sandbox: {
strictStyleIsolation: true, // Shadow DOM 样式隔离
experimentalStyleIsolation: true // Scoped CSS 样式隔离
},
prefetch: 'all', // 预加载所有子应用
});
4.2 子应用改造(Vue 3 示例)
// vue3-app/src/main.ts
import { createApp } from 'vue';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import App from './App.vue';
import router from './router';
let app: ReturnType<typeof createApp>;
function render(props: any = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
app.mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render();
}
// qiankun 生命周期
renderWithQiankun({
mount(props) {
render(props);
console.log('vue3-app mounted', props);
},
bootstrap() {
console.log('vue3-app bootstrap');
},
unmount() {
app.unmount();
},
update(props) {
console.log('vue3-app update', props);
}
});
// vue3-app/vite.config.ts
import qiankun from 'vite-plugin-qiankun';
export default {
base: '/vue3/', // 子应用前缀
plugins: [
qiankun('vue3-app', { useDevMode: true })
],
server: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*' // qiankun fetch 需要 CORS
}
}
};
五、样式隔离方案
5.1 方案对比
| 方案 | 实现 | 优点 | 缺点 |
|---|
| CSS Modules | 编译时 hash | 零运行时开销 | 需要改造 |
| Scoped CSS(Vue) | 属性选择器 | Vue 项目原生 | 仅 Vue |
| Shadow DOM | Web 标准隔离 | 最强隔离 | qiankun strictStyleIsolation |
| CSS-in-JS | JS 运行时 hash | 自动隔离 | 运行时开销 |
| 命名前缀约定 | BEM / 团队规范 | 简单 | 靠纪律 |
| PostCSS 前缀插件 | 自动加前缀 | 自动化 | 需构建配置 |
5.2 推荐组合
微前端样式隔离策略:
├── 基座 + 子应用全部 Vue → Scoped CSS / CSS Modules
├── 基座 React + 子应用混合 → CSS Modules + CSS-in-JS
├── 强隔离需求 → qiankun Shadow DOM(strictStyleIsolation)
└── 全局样式冲突(如 Ant Design + Element Plus)→ CSS Modules + 全局命名空间
六、跨应用通信
6.1 方案选择
| 方案 | 适合 | 示例 |
|---|
| Props 向下传递 | 基座→子应用 | qiankun 的 props |
| 自定义 Events | 子应用→基座 | window.dispatchEvent |
| 全局状态共享 | 频繁双向通信 | 共享 Redux / Pinia / Zustand |
| URL 参数 | 状态可分享 | 路由 query params |
6.2 基于 EventBus 的通信
// shared-utils/eventBus.ts
class EventBus {
private events: Map<string, Set<Function>> = new Map();
on(event: string, handler: Function) {
if (!this.events.has(event)) this.events.set(event, new Set());
this.events.get(event)!.add(handler);
}
off(event: string, handler: Function) {
this.events.get(event)?.delete(handler);
}
emit(event: string, data?: any) {
this.events.get(event)?.forEach(fn => fn(data));
}
}
export const microEventBus = new EventBus();
// 基座使用
import { microEventBus } from '@my-org/shared-utils';
microEventBus.on('user:login', (user) => {
console.log('User logged in:', user);
});
// 子应用使用
microEventBus.emit('user:login', { id: 'u1', name: 'Alice' });
6.3 共享状态(推荐)
// shared-utils/globalStore.ts
import { reactive, readonly } from 'vue';
const state = reactive({
user: null as User | null,
theme: 'light' as 'light' | 'dark',
notifications: [] as Notification[],
});
export const globalStore = {
state: readonly(state),
setUser(user: User) { state.user = user; },
setTheme(theme: 'light' | 'dark') { state.theme = theme; },
addNotification(n: Notification) { state.notifications.push(n); }
};
// 所有应用引用同一实例(通过 shared deps 或 window 挂载)
七、DevOps 与独立部署
7.1 独立部署架构
┌─────────────┐
│ CDN / Nginx │
│ (统一入口) │
└──────┬──────┘
│
┌────────────┼────────────┐
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│ 基座 │ │ 子应用A │ │ 子应用B │
│ Host │ │ Remote │ │ Remote │
└────────┘ └────────┘ └────────┘
↑ ↑ ↑
└────────────┼────────────┘
│
┌──────┴──────┐
│ 各自 CI/CD │
└─────────────┘
基座部署 → 更新路由表、公共布局
子应用A 部署 → 仅更新 //app-a.example.com/remoteEntry.js
子应用B 部署 → 仅更新 //app-b.example.com/remoteEntry.js
7.2 CI/CD 流水线(GitHub Actions)
# 子应用 A: .github/workflows/deploy.yml
name: Deploy Micro App A
on:
push:
branches: [main]
paths: ['apps/app-a/**']
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm --filter app-a build
- name: Deploy to CDN
run: |
aws s3 sync apps/app-a/dist s3://my-cdn/app-a/${{ github.sha }}/
aws s3 cp apps/app-a/dist/remoteEntry.js s3://my-cdn/app-a/latest/
八、微前端 Checklist
| 检查项 | 状态 | 说明 |
|---|
| 确定是否真的需要微前端 | ☐ | 团队>30人?多技术栈?独立部署需求? |
| 选定架构方案 | ☐ | Module Federation / qiankun / iframe |
| 公共依赖共享 | ☐ | React/Vue singleton,版本兼容 |
| 样式隔离 | ☐ | Shadow DOM / CSS Modules / Scoped |
| 路由整合 | ☐ | 基座路由 + 子应用路由不冲突 |
| 跨应用通信 | ☐ | Props / EventBus / 共享状态 |
| 错误边界 | ☐ | 子应用崩溃不影响基座 |
| 加载策略 | ☐ | prefetch / lazy / 按需加载 |
| 独立部署验证 | ☐ | 子应用单独发布,基座无感知 |
| 监控 | ☐ | 每个子应用独立监控 + 基座汇总 |
参考与延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。