深入浅出微前端架构
当单体前端的构建、发布与协作都撞上规模天花板,微前端把业务拆成可独立交付的子应用再运行时组合——本文从原理讲到四大主流方案对比,再落到 qiankun 落地与工程实践里的那些坑。
从巨石应用到分布式前端,一文读懂微前端的核心原理、主流方案与工程实践。
概述
当一个前端项目的代码量突破百万行、协作团队超过 10 个、业务线横跨数十个场景时,传统的单体前端架构就会走到它的极限。构建时间从秒级膨胀到分钟级,一次发布需要协调十几个团队的排期,一个小功能的改动可能触发全量回归测试,这不是一个团队的技术债,而是架构层面的系统性问题。
微前端(Micro Frontends)就是为解决这一类问题而诞生的架构思想。它将后端微服务的理念延伸到前端领域,把一个庞大的前端应用拆分成多个独立、自治的子应用,每个子应用可以独立开发、独立测试、独立部署,最终在运行时组合成一个完整的产品。
为什么我们需要微前端?
简单来说有三个核心驱动力:
-
组织架构适配:当团队规模增长到一定程度,康威定律开始发挥作用,系统设计的产出等同于组织沟通结构的镜像。微前端让每个业务团队可以端到端地负责自己的前端产品,从而减少跨团队协调成本。
-
技术栈异构:不同业务线有不同的技术选型,有的团队用 React,有的用 Vue,有的甚至还在维护 AngularJS 老项目。微前端则允许这些异构应用在同一个产品中共存,而不必强行统一技术栈。
-
增量演进:对于遗留系统,微前端提供了一条渐进式迁移的路径。我们不需要推倒重来,而是可以用新技术栈开发新功能,逐步替换旧模块,最终完成系统升级。
但微前端不是银弹。它在解决巨石应用的痛点问题时,也同样的引入了分布式系统的所有复杂性:应用间通信、样式隔离、状态共享、版本管理、性能开销等等。
接下来,我会从架构原理出发,系统梳理微前端的技术体系,帮助你判断什么时候该用微前端,以及该选哪种方案,希望你能在阅读完本文之后有所收获。
历史背景
在微前端概念出现之前,前端架构经历了几个明显的演进阶段。理解这个演进脉络,有助于我们看清微前端到底解决了什么问题,又继承了哪些遗留问题。
MPA 时代
在前后端分离之前,前端本质上是后端模板的附属物。每个页面对应一个后端路由,页面之间通过超链接跳转。这种架构天然就是微分的,即,每个页面独立,互不干扰。
但它的问题也很明显:页面切换是全量刷新的,用户体验差;公共组件(导航、侧边栏)在每个页面重复渲染,一致性难以保证;前后端耦合严重,前端无法独立迭代。
SPA 的崛起
随着 AngularJS、React、Vue 等框架的出现,单页应用(SPA)成为主流。前端接管了路由控制权,页面切换不再刷新浏览器,用户体验得到大幅提升。前后端通过 API 解耦,使得前端可以独立开发和部署,这时候也产生了前后端分离的设计,让职责边界更加清晰。
然而,SPA 架构发展到后期,也遇到了自己的瓶颈:
-
构建膨胀:代码量增长导致 Webpack 构建时间越来越长,大型项目的 CI/CD 流水线动辄需要十几分钟。
-
部署耦合:所有功能模块打包在一个应用里,任何一个小改动都需要全量发布,发布风险高、回滚成本大。
-
团队协作困难:多个团队在同一个代码仓库里开发,代码冲突、版本依赖、发布协调成为日常痛点。
-
技术栈锁定:一旦选定了框架,几乎没有迁移的可能。React 项目想转 Vue?那几乎等于重写。
微前端前夕
在微前端这个词被正式提出之前,业界就已经在积极探索各种拆分方案:
-
iframe 嵌入:最简单直接的方式,用 iframe 把不同系统拼在一起。但体验割裂、通信复杂、性能开销大,始终是个将就能用的方案。
-
Nginx 路由分发:通过 Nginx 把不同路径的请求转发到不同的前端项目。本质上还是多页应用,只是用反向代理做了一层域名统一。
-
Web Components:把组件封装成自定义元素,实现框架无关的复用。但 Web Components 更适合组件级复用,而不是应用级拆分。
-
模块联邦(Module Federation):Webpack 5 推出的特性,允许在运行时动态加载其他应用的模块。它更偏向构建层面的模块共享,而非完整的微前端方案。
这些方案各有侧重,但都没有系统性地解决独立开发、独立部署、运行时组合、体验一致这四个微前端的核心诉求。直到 single-spa、qiankun 等框架的出现,微前端才真正形成了一套完整的方法论和技术生态。
核心体系
什么是微前端
微前端是一种架构风格,将独立交付的前端应用组合成更大的整体,可以用一句话概括:
微前端就是将不同的功能模块,按照一定的规则拆分成独立的子应用,每个子应用可以独立开发、独立测试、独立部署,最终在主应用的框架下运行时集成,对外呈现为一个统一的产品。
这个定义中有几个关键词需要特别注意:
-
独立交付:每个微应用有自己的仓库、自己的 CI/CD 流水线、自己的发布节奏,不依赖其他应用的发布周期。
-
运行时集成:集成发生在浏览器端,而不是构建时。这意味着子应用的更新不需要重新构建主应用。
-
统一呈现:对用户来说,整个产品是一个整体,感知不到背后的微前端架构。跳转流畅、样式一致、状态共享。
微前端的核心原则
参考微服务的设计原则,结合前端领域的特点,微前端架构通常遵循以下原则:
-
独立自治:每个微应用应该尽可能独立,减少对其他应用的依赖。包括技术栈独立、数据独立、团队独立。
-
去中心化:避免出现一个超级应用掌控所有逻辑。主应用只负责应用调度和公共能力提供,业务逻辑应该下沉到各个微应用内部。
-
容错隔离:一个微应用的崩溃不应该影响整个应用的运行。需要有完善的错误边界和降级机制。
-
统一规范:虽然技术栈可以异构,但在接口约定、通信方式、样式规范、埋点标准等方面需要有统一的约定,否则集成成本会指数级上升。
工作原理
微前端的核心工作流程可以概括为:注册 → 加载 → 渲染 → 卸载。下面这张图展示了完整的生命周期:
flowchart TD
A[用户访问主应用] --> B[主应用启动 & 初始化]
B --> C[注册微应用配置表]
C --> D[监听路由变化]
D --> E{路由匹配?}
E -->|否| F[保持当前状态]
E -->|是| G[加载微应用资源]
G --> H[执行微应用 bootstrap 钩子]
H --> I[执行微应用 mount 钩子]
I --> J[微应用渲染到指定容器]
J --> K[用户交互 / 路由跳转]
K --> L{切换到其他微应用?}
L -->|否| K
L -->|是| M[执行当前应用 unmount 钩子]
M --> N[卸载 DOM & 清理副作用]
N --> G
我们来对每个阶段的关键动作展开分析:
1. 注册阶段
主应用在启动时,需要一份微应用配置表,声明所有子应用的基本信息:名称、路由匹配规则、资源入口地址、挂载容器等。这份配置表可以是硬编码的,也可以是从配置中心动态拉取的。
// 微应用配置表示例
const microApps = [
{
name: 'app-order', // 子应用名称,唯一标识
entry: '//cdn.example.com/order/', // 子应用资源入口
activeRule: '/order', // 路由匹配规则
container: '#subapp-container', // 挂载容器选择器
props: { token: 'xxx' } // 传递给子应用的 props
},
{
name: 'app-user',
entry: '//cdn.example.com/user/',
activeRule: '/user',
container: '#subapp-container',
}
];2. 加载阶段
当路由变化时,主应用根据 activeRule 判断应该激活哪个微应用。如果匹配到新的子应用,就开始加载它的资源。
加载方式因方案而异:
-
HTML Entry:直接加载子应用的 HTML 文件,解析其中的 JS 和 CSS(qiankun 采用这种方式)。
-
JS Entry:只加载一个 JS 入口文件,由这个 JS 再去加载其他资源(single-spa 原生方式)。
-
模块联邦:通过 Webpack 的 Module Federation 机制,在运行时远程加载模块。
3. 渲染阶段
资源加载完成后,主应用调用子应用暴露的
mount钩子函数,把子应用渲染到指定的 DOM 容器中。这个过程中,主应用可以通过props向子应用传递初始数据(如用户信息、全局配置等)。4. 卸载阶段
当用户离开当前子应用的路由时,主应用调用子应用的
unmount钩子,子应用需要清理自己的 DOM、事件监听、定时器、全局变量等副作用,避免内存泄漏。
微前端的架构分层
一个完整的微前端系统通常分为三层:
graph TB
subgraph 主应用层 Base App
A[应用调度器]
B[路由管理]
C[公共组件库]
D[全局状态管理]
E[统一登录 / 权限]
end
subgraph 微应用层 Micro Apps
F[订单应用]
G[用户应用]
H[营销应用]
I[数据看板应用]
end
subgraph 基础设施层 Infrastructure
J[构建部署流水线]
K[配置中心]
L[监控 & 埋点]
M[公共服务 API]
end
A --> F & G & H & I
B --> F & G & H & I
J --> F & G & H & I
K --> A
L --> F & G & H & I
M --> F & G & H & I
-
主应用层:也叫基座应用(Base App),负责应用调度、路由管理、公共组件、全局状态、统一登录等横切关注点。主应用应该尽可能做薄,即只做协调和公共能力提供,不去承载具体的业务逻辑。
-
微应用层:各个独立的业务子应用,每个应用对应一个业务域。它们有自己的代码仓库、开发团队、部署流水线。
-
基础设施层:支撑整个微前端体系的底层设施,包括构建部署工具、配置中心、监控系统、公共 API 服务等。
主流方案
目前业界主流的微前端方案可以分为四大类:iframe 方案、基于路由的运行时组合方案(single-spa / qiankun)、Web Components 方案、模块联邦方案。每种方案有各自的适用场景和权衡取舍。
iframe 原生方案
iframe 是浏览器原生支持的最古老的微前端方案。它的原理非常简单:在主页面中嵌入一个 iframe,iframe 加载另一个完整的 HTML 页面。
优点
-
实现成本极低,几乎零配置
-
天然的样式隔离和 JS 隔离,完全不用担心污染
-
技术栈完全无关,iframe 里可以是任何技术栈的应用
缺点
-
体验割裂:iframe 内部的路由变化不会同步到浏览器地址栏,刷新页面会丢失状态
-
通信复杂:父子页面之间只能通过
postMessage通信,数据传递效率低 -
性能开销大:每个 iframe 都是一个独立的浏览器上下文,内存占用高
-
布局困难:iframe 高度自适应、弹窗遮罩等场景处理起来非常麻烦
-
无法共享 DOM 结构:全局弹窗、Toast 等公共组件无法统一
路由驱动方案
这是目前业界最主流、最成熟的微前端方案。single-spa 是鼻祖,qiankun 是蚂蚁金服基于 single-spa 封装的增强版,在国内应用最广泛。
核心思想:通过路由匹配来决定加载哪个微应用,所有微应用共享同一个页面上下文,渲染在同一个 DOM 树中。
single-spa 提供了一些基础能力:
-
基于路由的应用注册和激活机制
-
各框架的适配层(single-spa-react、single-spa-vue 等)
-
应用生命周期管理(bootstrap / mount / unmount)
qiankun 在 single-spa 基础上增加了一些能力:
-
HTML Entry 加载方式(相比 single-spa 的 JS Entry 更易接入)
-
JS 沙箱(多种沙箱策略隔离全局变量)
-
样式隔离(scoped CSS / Shadow DOM)
-
资源预加载
-
更完善的通信机制
Web Components 方案
这一方案利用了浏览器原生的 Custom Elements 和 Shadow DOM 标准,将每个微应用封装成一个自定义元素,主应用通过 <my-app-order></my-app-order> 这样的标签来使用子应用。
优点
-
基于 Web 标准,框架无关
-
Shadow DOM 提供了天然的样式隔离
-
组件化粒度灵活,可以是应用级也可以是组件级
缺点
-
技术生态不够成熟,大型项目落地案例少
-
框架与 Web Components 的互操作有额外成本
-
路由、状态管理等需要自己实现
-
浏览器兼容性需要考虑(IE 完全不支持)
模块联邦(Module Federation)方案
Webpack 5 推出的特性,允许多个 Webpack 构建产物在运行时互相引用模块。它不是一个完整的微前端框架,但可以作为微前端的底层技术支撑。
核心思想:每个应用既是宿主也是远程模块提供者。应用 A 可以直接 import 应用 B 暴露出来的组件、工具函数甚至整个页面,这些模块在运行时从远程加载。
优点
-
模块共享非常灵活,粒度可大可小
-
构建时就确定了模块依赖关系,运行时加载高效
-
可以和其他微前端方案结合使用
缺点
-
强依赖 Webpack 5,技术栈锁定
-
版本依赖管理复杂,共享库的版本协调是个难题
-
本身不是完整的微前端方案,缺少应用生命周期管理、沙箱等能力
综合对比
我这里也对四种方案在各个维度进行了横向对比:
| 对比维度 | iframe | qiankun / single-spa | Web Components | 模块联邦 |
|---|---|---|---|---|
| 接入成本 | 极低 | 中 | 中高 | 中高 |
| 样式隔离 | 完美 | 较好(沙箱/作用域) | 完美(Shadow DOM) | 弱(需自行处理) |
| JS 隔离 | 完美 | 较好(JS 沙箱) | 一般 | 弱 |
| 路由同步 | 差 | 完美 | 需自行实现 | 需自行实现 |
| 通信效率 | 低(postMessage) | 高(直接调用) | 中(CustomEvent) | 高(直接引用) |
| 性能开销 | 高 | 中 | 低 | 低 |
| 技术栈无关 | 是 | 基本是(适配层) | 是 | 否(Webpack 5) |
| 生态成熟度 | 原生支持 | 高 | 中 | 中 |
| 适用场景 | 完全独立的系统嵌入 | 中大型企业级应用 | 组件级复用/跨框架 | 模块共享/构建优化 |
选型建议
这里只是通常的使用参考,实际开发中还是要以我们所面临的情况来决定,比如 ROI 极低,我们可能都不需要考虑微前端,如果就是领导看看,一个 iframe 丢过去就拉倒。
-
如果是临时方案或者两个完全无关的系统需要拼凑 → 选 iframe,简单直接
-
如果是中大型企业级应用,有多团队协作、多技术栈共存的需求 → 选 qiankun / single-spa,这是目前最成熟的方案
-
如果侧重组件级复用、需要跨框架共享 UI 组件 → 选 Web Components
-
如果已经在用 Webpack 5,主要需求是模块共享和构建优化 → 选模块联邦,或者与 qiankun 搭配使用
方案应用
下面以目前国内最主流的 qiankun 方案为例,我们实践一下微前端系统的整体应用落地过程。
架构总览
graph LR
subgraph 主应用基座
Router[Vue Router]
Register[应用注册中心]
Sandbox[JS 沙箱 / 样式隔离]
GlobalState[全局状态 store]
Layout[主框架布局]
end
subgraph 子应用 A React
RA[React App]
RM[React 路由]
RS[React 状态]
end
subgraph 子应用 B Vue
VA[Vue App]
VM[Vue 路由]
VS[Vue 状态]
end
subgraph 子应用 C 静态页
SA[静态 HTML]
end
Router --> Register
Register -->|加载 / 卸载| Sandbox
Sandbox --> RA & VA & SA
GlobalState -->|props 下发| RA & VA
Layout -->|提供容器| Register
主应用(基座)实现
主应用我们用 Vue 3 来构建,它负责整体布局、路由分发和应用调度。
第一步:安装依赖
npm install qiankun第二步:注册微应用
// main.js - 主应用入口
import { createApp } from 'vue';
import { registerMicroApps, start, initGlobalState } from 'qiankun';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
// 1. 初始化全局状态(主应用与子应用共享的状态)
const { onGlobalStateChange, setGlobalState } = initGlobalState({
user: {
id: 1001,
name: '张三',
token: 'eyJhbGciOiJIUzI1NiIs...',
},
theme: 'light',
sidebarCollapsed: false,
});
// 监听全局状态变化
onGlobalStateChange((value, prev) => {
console.log('[主应用] 全局状态变化:', prev, '→', value);
}, true);
// 2. 注册微应用
registerMicroApps(
[
{
name: 'app-order', // 子应用名称
entry: '//localhost:8081', // 子应用开发环境地址
// entry: '//cdn.example.com/order/', // 生产环境 CDN 地址
container: '#subapp-container', // 子应用挂载容器
activeRule: '/order', // 激活规则
props: {
// 初始传递给子应用的数据
platform: 'main-app',
version: '1.0.0',
},
},
{
name: 'app-user',
entry: '//localhost:8082',
container: '#subapp-container',
activeRule: '/user',
},
],
{
// 全局生命周期钩子
beforeLoad: [
(app) => {
console.log('[主应用] 加载应用前:', app.name);
return Promise.resolve();
},
],
beforeMount: [
(app) => {
console.log('[主应用] 挂载应用前:', app.name);
return Promise.resolve();
},
],
afterUnmount: [
(app) => {
console.log('[主应用] 卸载应用后:', app.name);
return Promise.resolve();
},
],
}
);
// 3. 启动 qiankun
start({
sandbox: {
experimentalStyleIsolation: true, // 开启样式隔离(实验性)
},
prefetch: 'all', // 预加载所有子应用资源
singular: true, // 同一时间只显示一个子应用
});第三步:主应用布局
<!-- App.vue - 主应用布局 -->
<template>
<div class="main-layout">
<!-- 顶部导航栏(主应用渲染,始终存在) -->
<header class="header">
<div class="logo">微前端平台</div>
<nav class="nav">
<router-link to="/home">首页</router-link>
<router-link to="/order">订单中心</router-link>
<router-link to="/user">用户中心</router-link>
</nav>
<div class="user-info">
{{ globalState.user?.name }}
</div>
</header>
<div class="body">
<!-- 侧边栏(主应用渲染) -->
<aside class="sidebar">
<!-- 侧边菜单 -->
</aside>
<!-- 主内容区:子应用渲染在这里 -->
<main class="content">
<!-- 主应用自己的路由页面 -->
<router-view v-if="$route.path === '/home'"></router-view>
<!-- 子应用容器 -->
<div id="subapp-container"></div>
</main>
</div>
</div>
</template>子应用(React)改造
子应用需要做一些改造才能被 qiankun 加载,核心是导出三个生命周期钩子:bootstrap、mount、unmount。
第一步:配置 webpack 输出
// config-overrides.js - React 子应用的 webpack 配置
const { name } = require('./package');
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
config.output.publicPath = '/'; // 开发环境,生产环境需配置为 CDN 地址
return config;
},
devServer: (configFunction) => {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.headers = {
'Access-Control-Allow-Origin': '*', // 允许跨域,主应用要加载子应用资源
};
config.historyApiFallback = true;
config.hot = true;
return config;
};
},
};第二步:导出生命周期钩子
// src/public-path.js - 动态设置 publicPath
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}// src/index.js - React 子应用入口
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
// 独立运行时的渲染
function render(props) {
const { container } = props;
const root = container
? container.querySelector('#root')
: document.getElementById('root');
ReactDOM.render(
<React.StrictMode>
<BrowserRouter
basename={window.__POWERED_BY_QIANKUN__ ? '/order' : '/'}
>
<App {...props} />
</BrowserRouter>
</React.StrictMode>,
root
);
}
// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
/**
* bootstrap 只会在微应用初始化的时候调用一次
* 通常可以在这里做一些全局变量的初始化
*/
export async function bootstrap() {
console.log('[子应用 order] bootstrap 执行');
}
/**
* mount 在应用每次进入时都会调用
* 通常在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('[子应用 order] mount 执行,收到 props:', props);
// 监听全局状态变化
props.onGlobalStateChange((state, prev) => {
console.log('[子应用 order] 全局状态变化:', prev, '→', state);
}, true);
render(props);
}
/**
* unmount 在应用切出/卸载时调用
* 通常在这里清空微应用的副作用
*/
export async function unmount(props) {
console.log('[子应用 order] unmount 执行');
const { container } = props;
const root = container
? container.querySelector('#root')
: document.getElementById('root');
ReactDOM.unmountComponentAtNode(root);
}应用间通信
微前端中,应用间通信是一个非常重要的问题。常见的通信方式有以下几种:
| 通信方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Props 传递 | 主→子,初始化数据 | 简单直接,类型可控 | 单向传递,子应用无法主动通知主应用 |
| 全局状态(Actions) | 跨应用状态共享 | 响应式,双向同步 | 状态容易滥用,调试困难 |
| CustomEvent | 任意应用间事件通知 | 解耦,基于浏览器标准 | 数据传递能力有限,类型不安全 |
| postMessage | iframe 场景,跨域通信 | 兼容性好 | 性能差,只能传序列化数据 |
| URL / 路由参数 | 轻量数据传递,跳转带参 | 天然支持,刷新不丢失 | 数据量小,只能传字符串 |
| 共享存储(localStorage) | 持久化数据共享 | 简单,持久化 | 容量有限,无法实时响应 |
在 qiankun 体系中,可以考虑使用 Props + 全局状态(Actions) 的组合方案:
-
主应用通过 props 下发初始数据和全局状态的读写方法
-
子应用通过
setGlobalState修改全局状态,所有应用都会收到通知 -
子应用之间不直接通信,都通过主应用的全局状态中转
安全防护与工程实践
微前端虽然解决了协作和部署的问题,但也对前端工程带来一定的挑战性。下面是开发过程中最常遇到的坑以及对应的解决方案。
样式隔离问题
样式污染是微前端最常见的问题之一。由于所有子应用渲染在同一个 DOM 树下,一个应用的全局样式很可能影响到另一个应用。
解决方案对比:
| 方案 | 原理 | 隔离程度 | 性能影响 | 适用场景 |
|---|---|---|---|---|
| BEM / 命名约定 | 用统一前缀约定类名 | 低(靠人维护) | 无 | 小团队、规范严格 |
| CSS Modules | 构建时生成唯一类名 | 中 | 无 | 单项目内隔离 |
| CSS-in-JS | JS 动态生成样式,自动加前缀 | 中高 | 有运行时开销 | React 技术栈 |
| Shadow DOM | 浏览器原生隔离机制 | 高 | 低 | Web Components 方案 |
| qiankun 样式沙箱 | 运行时改写样式选择器,添加前缀 | 中高 | 有运行时开销 | qiankun 项目 |
最佳实践:
-
所有子应用统一使用 CSS Modules 或 CSS-in-JS,从源头上避免类名冲突
-
全局样式(如 reset、全局字体)只在主应用中引入一次,子应用不要重复引入
-
第三方 UI 库的样式前缀要做区分(比如一个用
el-,一个用ant-) -
避免使用标签选择器和通配符选择器(
*),影响范围太大
JS 沙箱与全局变量污染
JavaScript 是单线程的,所有脚本共享同一个全局作用域。如果子应用不小心修改了全局变量,就可能影响其他应用甚至主应用的运行。
qiankun 提供了三种沙箱策略:
-
SnapshotSandbox(快照沙箱):在应用激活前对
window做一次快照,应用卸载时把window恢复成快照状态。兼容性最好,但性能差(需要遍历整个 window 对象),且同一时间只能激活一个子应用。 -
LegacySandbox(单例沙箱):通过 Proxy 代理 window 对象,记录对 window 的所有修改,卸载时恢复。性能比快照沙箱好,但仍然是单例的。
-
ProxySandbox(代理沙箱):每个子应用有自己独立的 fakeWindow,所有操作都在 fakeWindow 上进行,互不影响。支持多应用同时激活,但兼容性稍差(依赖 Proxy API)。
常见问题
-
子应用往
window上挂全局变量,卸载时没有清理 -
使用
document.cookie、localStorage等全局 API,数据互相覆盖 -
定时器(setInterval、setTimeout)在应用卸载后没有清理
-
全局事件监听(resize、scroll 等)没有解绑
防护建议
javascript// 子应用卸载时务必清理所有副作用 export async function unmount(props) { // 1. 卸载组件 ReactDOM.unmountComponentAtNode(root); // 2. 清理定时器 timers.forEach(id => clearTimeout(id)); timers.clear(); // 3. 解绑全局事件 window.removeEventListener('resize', handleResize); document.removeEventListener('click', handleClickOutside); // 4. 清理全局变量(虽然沙箱会处理,但主动清理更安全) delete window.__MY_APP_STATE__; }
性能优化
微前端的性能问题主要来自两个方面:资源加载的额外开销和应用切换的延迟。
资源加载优化
-
资源预加载:qiankun 的
prefetch配置可以在主应用空闲时预加载子应用资源,用户点击时直接从缓存取 -
公共依赖提取:React、Vue、axios 等公共库通过 externals 排除,由主应用统一引入,避免重复加载
-
HTTP 缓存:子应用资源加上 hash 文件名,充分利用浏览器缓存
-
按需加载:子应用内部也要做代码分割和路由懒加载
应用切换优化
-
应用保活(Keep-Alive):对于切换频繁的子应用,可以不卸载而是隐藏,下次切换时直接显示。但要注意内存占用
-
骨架屏:子应用加载期间展示骨架屏,减少用户等待的焦虑感
-
渐进式加载:先加载首屏关键资源,非关键资源延后加载
版本管理与部署
微前端的一个核心优势是独立部署,但这也同样的带来了版本管理的复杂度。
最佳实践
-
资源地址固定化:每个子应用有固定的资源入口地址(如
cdn.example.com/order/index.html),主应用只配置这个地址,不关心具体版本 -
灰度发布:通过配置中心控制不同用户看到不同版本的子应用,实现灰度放量
-
版本回滚:每个版本的资源都保留在 CDN 上,回滚只需要改配置中心的版本号
-
入口 HTML 不缓存:子应用的入口 HTML 设置
Cache-Control: no-cache,确保每次都拿到最新版本;而 JS/CSS 资源文件名带 hash,可以长期缓存
错误边界与容错
分布式系统中,故障是常态。所以在微前端架构中必须考虑到子系统挂了,对主系统及其他系统完全无感。
容错策略
-
资源加载失败降级:子应用资源加载超时时,展示清晰的错误提示和重试按钮
-
JS 运行时错误隔离:用 Error Boundary(React)或
errorCaptured(Vue)捕获子应用内部的错误 -
全局错误监听:主应用监听
window.onerror和unhandledrejection,统一上报和处理 -
健康检查:主应用定期(或在激活前)检查子应用的可用性,不可用时展示降级页面
几个方向
虽然目前AI开始重构编程范式了,但是微前端架构技术本身仍在演进的状态,如果想对微前端的应用再多一些了解,可以从下面这几个方向入手:
-
浏览器原生能力的增强
随着 Web Components、Shadow DOM、Import Maps 等浏览器原生标准的成熟,微前端也越来越多地依赖浏览器原生能力,而不是框架层面的 hack。比如 Import Maps 允许在浏览器中直接映射模块名到 URL,这意味着子应用可以直接
import 'react',浏览器会从指定的 CDN 地址加载,不需要构建工具做特殊处理。这将大幅简化微前端的模块共享机制(但是目前还没有听到有这种应用的)。 -
构建时与运行时的融合
构建时确定模块依赖关系,运行时动态加载和调度,而模块联邦代表了构建时组合的思路,qiankun 代表了运行时组合的思路,这两种方案融合后可以极大增强微前端能力。
现在 Webpack 5 + qiankun 的组合已经在很多项目中落地了,比如用模块联邦解决公共组件和工具函数的共享问题,用 qiankun 解决应用级的生命周期管理和沙箱隔离问题。两者互补,形成了更完整的解决方案。
-
微前端 + 低代码
现在的低代码平台就有类似的应用,即,通过可视化的方式配置各个微应用的路由、布局、通信关系,不需要写代码就能完成微前端应用的组装。
-
服务端渲染(SSR)的微前端化
目前微前端的实践大多集中在客户端渲染(CSR)场景,但 SSR / SSG 场景下的微前端方案还不够成熟。随着 Next.js、Nuxt 等 SSR 框架的普及,服务端的微前端(也叫微同构)也会逐步开始卷起来,比如:
-
服务端组件(Server Components)的拆分与组合
-
边缘计算(Edge Computing)场景下的流式 HTML 拼接
-
SSR 框架级别的微前端支持(如 Next.js 的 Module Federation 支持)
-
-
标准化与规范化
国内的微前端方案目前基本已经没什么可选的了,不是 qiankun 就是 garfish ,其他大厂的方案很少会再被提起,而微前端的规范,如:
-
微前端应用的接口规范(生命周期、通信协议)
-
微前端应用的注册与发现机制(类似后端的服务注册中心)
-
微前端应用的可观测性标准(监控、日志、链路追踪)
基本都已经是比较完善的状态了了,我们在自建微前端体系的时候不需要再像之前翻看各种文档来排坑,基本上install+ctrlc/v可以解决大多数问题,更别说有 AI 加入的 vibe coding 了。
-