微前端架构
大约 10 分钟约 3027 字
微前端架构
简介
微前端将大型前端应用拆分为多个独立开发、独立部署的子应用。每个子应用可以使用不同的框架和技术栈,最终组合成一个完整的应用。适合大型团队和多业务线的项目。
微前端借鉴了微服务的理念:将单体前端应用拆分为多个小型、独立、可组合的前端应用。每个子应用拥有自己的开发、测试、部署流程,可以由不同的团队独立维护。
微前端的核心价值:
- 团队自治:不同团队可以独立开发、测试、部署各自的子应用
- 技术无关:子应用可以使用不同的框架(Vue、React、Angular)
- 增量迁移:老项目可以逐步重构,不需要一次性重写
- 独立部署:子应用独立部署,互不影响
- 故障隔离:一个子应用的崩溃不应影响整个应用
微前端不是银弹:
- 中小项目(< 5 人团队)不需要微前端,模块化 + 组件库即可
- 微前端引入的复杂度(通信、样式隔离、共享状态)需要足够的技术能力
- 部署和运维成本显著增加
特点
微前端方案
方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| qiankun | JS 沙箱 + 路由 | 蚂蚁金服出品 | 样式隔离不完善 |
| Module Federation | Webpack 5 模块共享 | 官方方案 | 依赖 Webpack |
| single-spa | 路由级子应用 | 通用性强 | 配置复杂 |
| iframe | 原生隔离 | 完全隔离 | 性能差、通信复杂 |
| wujie | WebComponent + iframe | 腾讯出品 | 较新 |
什么时候需要微前端
判断清单:
[ ] 团队规模超过 10 人,分为 3 个以上业务组?
[ ] 应用代码量超过 10 万行,构建时间超过 3 分钟?
[ ] 不同模块需要不同框架(如历史遗留 Angular + 新 Vue3)?
[ ] 不同模块发布节奏差异大(有的每天发布,有的每月发布)?
[ ] 不同模块由不同团队负责,需要独立迭代?
以上满足 3 项以上,建议考虑微前端。
仅满足 1-2 项,建议用 Monorepo + 模块化。iframe 方案(最简单的隔离)
<!-- iframe 天然提供 JS 沙箱和样式隔离 -->
<!-- 适合不需要深度集成的场景 -->
<iframe
src="http://localhost:8081"
style="width: 100%; height: 600px; border: none;"
id="sub-app"
></iframe>
<script>
// 父页面与 iframe 通信
const iframe = document.getElementById('sub-app')
// 父页面 -> iframe
iframe.contentWindow.postMessage(
{ type: 'SET_TOKEN', token: 'xxx' },
'http://localhost:8081'
)
// iframe -> 父页面
window.addEventListener('message', (event) => {
if (event.origin !== 'http://localhost:8081') return
console.log('收到 iframe 消息:', event.data)
})
// iframe 内部监听
window.addEventListener('message', (event) => {
if (event.origin !== 'http://localhost:3000') return
if (event.data.type === 'SET_TOKEN') {
localStorage.setItem('token', event.data.token)
}
})
</script>qiankun 实践
主应用配置
// npm install qiankun
import { registerMicroApps, start, initGlobalState } from 'qiankun'
// 注册子应用
registerMicroApps([
{
name: 'user-app',
entry: '//localhost:8081',
container: '#subapp-container',
activeRule: '/user',
props: {
token: localStorage.getItem('token'),
apiUrl: 'https://api.example.com'
}
},
{
name: 'order-app',
entry: '//localhost:8082',
container: '#subapp-container',
activeRule: '/order',
props: {
token: localStorage.getItem('token')
}
},
{
name: 'report-app',
entry: '//localhost:8083',
container: '#subapp-container',
activeRule: '/report'
}
])
// 全局状态管理
const { onGlobalStateChange, setGlobalState } = initGlobalState({
user: null,
token: null
})
onGlobalStateChange((state, prev) => {
console.log('主应用状态变化:', state)
})
// 启动
start({
prefetch: 'all', // 预加载
sandbox: { experimentalStyleIsolation: true }, // 样式隔离
singular: false // 允许多个子应用同时存在
})qiankun 生命周期
// qiankun 子应用必须导出三个生命周期钩子
// 1. bootstrap —— 子应用初始化(只执行一次)
export async function bootstrap() {
console.log('[user-app] bootstrap')
// 初始化全局配置、注册全局事件等
}
// 2. mount —— 子应用挂载(每次进入都执行)
export async function mount(props: any) {
console.log('[user-app] mount', props)
// props 包含:
// - container: 挂载容器 DOM
// - name: 子应用名称
// - setGlobalState: 设置全局状态的方法
// - onGlobalStateChange: 监听全局状态变化
render(props)
}
// 3. unmount —— 子应用卸载(每次离开都执行)
export async function unmount() {
console.log('[user-app] unmount')
// 清理定时器、事件监听、全局状态等
app?.unmount()
app = null
}
// 4. update(可选)—— 主应用手动更新子应用
export async function update(props: any) {
console.log('[user-app] update', props)
}主应用路由
<!-- 主应用 App.vue -->
<template>
<div id="main-app">
<nav class="main-nav">
<router-link to="/">首页</router-link>
<router-link to="/user">用户管理</router-link>
<router-link to="/order">订单管理</router-link>
<router-link to="/report">报表中心</router-link>
</nav>
<main>
<!-- 主应用内容 -->
<router-view v-if="$route.path === '/'" />
<!-- 子应用容器 -->
<div id="subapp-container"></div>
</main>
</div>
</template>子应用配置(Vue3)
// 子应用 main.ts
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
let app: any = null
function render(props: any = {}) {
const { container } = props
const router = createRouter({
history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/user' : '/'),
routes: [
{ path: '/', component: () => import('./pages/List.vue') },
{ path: '/:id', component: () => import('./pages/Detail.vue') }
]
})
app = createApp(App)
app.use(router)
app.mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
// qiankun 生命周期
export async function bootstrap() {
console.log('user-app bootstrap')
}
export async function mount(props: any) {
console.log('user-app mount', props)
render(props)
}
export async function unmount() {
console.log('user-app unmount')
app?.unmount()
app = null
}子应用 Vite 配置
// vite.config.ts
export default defineConfig({
plugins: [vue()],
server: {
port: 8081,
cors: true,
origin: 'http://localhost:8081'
},
base: window.__POWERED_BY_QIANKUN__ ? '/user' : '/',
})子应用通信
// 主应用:初始化全局状态
import { initGlobalState, MicroAppStateActions } from 'qiankun'
const actions: MicroAppStateActions = initGlobalState({
user: null,
token: null,
theme: 'light',
language: 'zh',
})
// 监听状态变化
actions.onGlobalStateChange((state, prevState) => {
console.log('[主应用] 状态变化:', state)
console.log('[主应用] 前一个状态:', prevState)
})
// 修改全局状态
actions.setGlobalState({
user: { name: '张三', role: 'admin' },
token: 'new-token',
})
// 子应用:接收和修改全局状态
export async function mount(props: any) {
const { onGlobalStateChange, setGlobalState } = props
// 监听主应用的状态变化
onGlobalStateChange((state, prevState) => {
console.log('[子应用] 全局状态变化:', state)
})
// 子应用也可以修改全局状态
setGlobalState({ theme: 'dark' })
}样式隔离方案
// qiankun 的三种样式隔离方案
// 1. strictStyleIsolation —— 使用 Shadow DOM(严格隔离)
start({
sandbox: {
strictStyleIsolation: true,
},
})
// 优点:完全隔离,子应用样式不影响主应用
// 缺点:第三方组件库可能不兼容(如 Element Plus 的 Popper)
// 全局样式(如 body、:root)不生效
// 2. experimentalStyleIsolation —— 添加作用域前缀(推荐)
start({
sandbox: {
experimentalStyleIsolation: true,
},
})
// 原理:给子应用的 CSS 选择器添加 [data-qiankun="app-name"] 前缀
// .container -> div[data-qiankun="user-app"] .container
// 优点:兼容性好,第三方组件库正常工作
// 缺点:不是 100% 隔离,全局样式仍可能泄漏
// 3. 手动隔离 —— 约定 CSS 命名空间
// 子应用的根元素添加特定 class
// .user-app { /* 所有子应用样式包裹在此 */ }
// .user-app .button { ... }Module Federation
Webpack 5 模块联邦
// webpack.config.js(主应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
userApp: 'userApp@http://localhost:8081/remoteEntry.js',
orderApp: 'orderApp@http://localhost:8082/remoteEntry.js',
},
shared: {
vue: { singleton: true },
'vue-router': { singleton: true }
}
})
]
}// webpack.config.js(子应用)
new ModuleFederationPlugin({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserList': './src/components/UserList.vue',
'./UserService': './src/services/userService'
},
shared: {
vue: { singleton: true }
}
})<!-- 主应用中使用远程组件 -->
<template>
<UserList />
</template>
<script setup>
// 动态导入远程组件
const UserList = defineAsyncComponent(() => import('userApp/UserList'))
</script>Module Federation 高级用法
// 动态远程加载(运行时决定加载哪个远程模块)
// 适用于多环境、A/B 测试等场景
async function loadRemoteComponent(remoteUrl, scope, module) {
const container = window[scope]
await __webpack_init_sharing__('default')
await container.init(__webpack_share_scopes__.default)
const factory = await window[scope].get(module)
return factory()
}
// 使用
const UserList = await loadRemoteComponent(
'http://localhost:8081/remoteEntry.js',
'userApp',
'./UserList'
)微前端公共依赖管理
// 共享依赖的策略
// 1. 通过 qiankun 的 props 传递共享工具
// 主应用
registerMicroApps([
{
name: 'sub-app',
props: {
sharedUtils: {
request: axios,
dayjs,
lodash,
eventBus: mitt(),
}
}
}
])
// 子应用
export async function mount(props) {
const { request, dayjs } = props.sharedUtils
// 使用共享的 axios 和 dayjs 实例
}
// 2. 通过外部 CDN 加载共享依赖
// 所有子应用的 index.html 中引入相同的 CDN
// <script src="https://cdn.example.com/vue@3.3.0.js"></script>
// <script src="https://cdn.example.com/axios@1.5.0.js"></script>
// 3. Module Federation 的 shared 配置
shared: {
vue: {
singleton: true, // 只加载一个 Vue 实例
requiredVersion: '^3.3.0', // 版本要求
eager: true, // 立即加载(避免异步加载闪烁)
},
'vue-router': {
singleton: true,
requiredVersion: '^4.2.0',
},
pinia: {
singleton: true,
},
axios: {
singleton: false, // 允许多个实例
},
}错误边界与降级
// 主应用中添加子应用加载失败的降级处理
import { addGlobalUncaughtErrorHandler, start } from 'qiankun'
// 全局错误捕获
addGlobalUncaughtErrorHandler((event: Event | string) => {
const { message } = event as any
console.error('[微前端] 子应用加载失败:', message)
if (message?.includes('died in status')) {
// 子应用加载失败,显示降级 UI
const container = document.getElementById('subapp-container')
if (container) {
container.innerHTML = `
<div style="text-align: center; padding: 100px;">
<h2>模块加载失败</h2>
<p>${message}</p>
<button onclick="location.reload()">重试</button>
</div>
`
}
}
})
// 子应用加载超时处理
start({
timeout: 5000, // 5 秒超时
prefetch: 'all',
})优点
缺点
总结
微前端核心价值:大型应用拆分为独立子应用,团队自治开发。方案选择:qiankun 适合 JS 沙箱隔离(蚂蚁金服方案),Module Federation 适合 Webpack 项目共享模块。主应用负责路由调度和全局状态,子应用遵循生命周期(bootstrap/mount/unmount)。建议中小项目不要过早引入微前端,模块化 + 组件库即可。
决策建议:
- 3-5 人团队:Monorepo(pnpm workspace)+ 模块化
- 5-15 人团队:Monorepo + NPM 包 + 组件库
- 15+ 人团队、多业务线:考虑微前端(qiankun 或 Module Federation)
- 遗留系统迁移:qiankun 渐进式接入
- 全新项目、统一技术栈:Module Federation
关键知识点
- 先判断主题更偏浏览器原理、框架机制、工程化还是性能优化。
- 前端问题很多看似是页面问题,实际源头在构建、缓存、状态流或接口协作。
- 真正成熟的前端方案一定同时考虑首屏、交互、可维护性和线上诊断。
项目落地视角
- 把组件边界、状态归属、网络层规范和错误处理先定下来。
- 上线前检查包体积、缓存命中、接口失败路径和关键交互降级策略。
- 如果主题和性能有关,最好用 DevTools、Lighthouse 或埋点验证。
常见误区
- 只盯框架 API,不理解浏览器和运行时成本。
- 把状态、请求和 UI 更新混成一层,后期难维护。
- 线上问题出现时没有日志、埋点和性能基线可对照。
进阶路线
- 继续补齐 SSR、边缘渲染、设计系统和监控告警能力。
- 把主题和后端接口约定、CI/CD、缓存策略一起思考。
- 沉淀组件规范、页面模板和性能基线,减少团队差异。
适用场景
- 当你准备把《微前端架构》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合中后台应用、门户站点、组件库和实时交互页面。
- 当需求涉及状态流、路由、网络缓存、SSR/CSR 或性能治理时,这类主题很关键。
落地建议
- 先定义组件边界和状态归属,再落地 UI 细节。
- 对核心页面做首屏、体积、缓存和错误路径检查。
- 把安全、兼容性和可访问性纳入默认交付标准。
排错清单
- 先用浏览器 DevTools 看请求、性能面板和控制台错误。
- 检查依赖版本、构建配置、环境变量和静态资源路径。
- 如果是线上问题,优先确认缓存、CDN 和构建产物是否一致。
复盘问题
- 如果把《微前端架构》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《微前端架构》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《微前端架构》最大的收益和代价分别是什么?
