微前端架构:qiankun 沙箱隔离与样式冲突完全指南
作者: 前端技术探索者
阅读时长: 15-20分钟
难度等级: 中高级
源码版本: qiankun@2.10.16
📚 目录
- 一、微前端架构概述
- [二、qiankun 核心架构设计](#二、qiankun 核心架构设计)
- 三、沙箱隔离机制深度解析
- 四、样式隔离方案详解
- 五、应用生命周期管理
- 六、通信机制设计
- 七、性能优化实战策略
- 八、完整实践案例
- 九、常见问题与解决方案
一、微前端架构概述
1.1 什么是微前端
微前端是一种将单体前端应用拆分为多个小型、独立的前端应用的架构模式。每个子应用可以独立开发、测试、部署,最终组合成一个完整的应用。
核心价值:
微前端架构
技术栈无关
独立开发部署
增量升级
团队自治
Vue + React + Angular共存
降低发布风险
平滑技术迁移
提升团队效率
1.2 主流微前端方案对比
| 方案 | 技术原理 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| qiankun | 基于single-spa,沙箱隔离 | 成熟稳定、社区活跃、文档完善 | 配置相对复杂 | 中大型项目、多团队协作 |
| single-spa | 应用路由 + 生命周期管理 | 灵活度高、底层控制力强 | 需要手动处理样式隔离 | 对技术细节要求高的团队 |
| iframe | 浏览器原生隔离 | 完全隔离、简单直接 | 性能差、通信复杂、体验不佳 | 快速集成遗留系统 |
| Module Federation | Webpack5模块联邦 | 运行时共享依赖、性能好 | 需要Webpack5、配置复杂 | 现代化构建工具链 |
| wujie | WebComponent + iframe | 接入简单、兼容性好 | 相对较新、生态较小 | Vue3项目为主 |
1.3 qiankun vs single-spa
需要开箱即用
需要极致灵活
微前端需求
选择方案
qiankun
single-spa
✅ 自动JS沙箱
✅ 样式隔离
✅ 预加载机制
✅ HTML Entry
❌ 手动实现沙箱
❌ 手动处理样式
❌ 手动优化加载
✅ JS Entry配置
二、qiankun 核心架构设计
2.1 整体架构
qiankun 的核心架构由以下几个关键部分组成:
主应用 Main App
qiankun Framework
Loader 加载器
Sandbox 沙箱
Router 路由
生命周期管理
HTML Entry解析
资源加载
快照沙箱 SnapshotSandbox
代理沙箱 ProxySandbox
严格沙箱 StrictSandbox
微应用1 Vue
微应用2 React
微应用3 Angular
2.2 核心工作流程
浏览器 微应用 qiankun 主应用 用户 浏览器 微应用 qiankun 主应用 用户 访问路由 /app1 registerMicroApps() 加载HTML Entry 返回HTML资源 创建JS沙箱 执行beforeMount生命周期 挂载微应用 渲染页面 切换路由 /app2 unmount微应用1 销毁沙箱、清理样式 加载微应用2 切换完成
2.3 HTML Entry 机制
qiankun 采用 HTML Entry 作为应用入口,相比 JS Entry 有以下优势:
| 对比项 | HTML Entry (qiankun) | JS Entry (single-spa) |
|---|---|---|
| 接入成本 | ⭐⭐⭐⭐⭐ 低,只需提供HTML | ⭐⭐⭐ 中,需要改造导出逻辑 |
| 资源处理 | ✅ 自动提取JS/CSS | ❌ 需要手动配置 |
| 样式隔离 | ✅ 自动处理 | ❌ 需要手动实现 |
| 依赖管理 | ✅ 自动处理公共依赖 | ❌ 需要手动优化 |
| 灵活性 | ⭐⭐⭐⭐ 约定大于配置 | ⭐⭐⭐⭐⭐ 极度灵活 |
HTML Entry 解析流程:
javascript
// qiankun@2.10.16 源码简化版
function importEntry(entry) {
const htmlLoader = fetch(entry).then(res => res.text());
return htmlLoader.then(html => {
const { template, scripts, styles } = processHtml(html);
// 转换相对路径为绝对路径
const assetPublicPath = getAssetPublicPath(entry);
return {
template: template.replace(/src=["']([^"']+)["']/g,
(match, src) => `src="${assetPublicPath + src}"`),
scripts: scripts.map(script => ({
src: script.src ? assetPublicPath + script.src : undefined,
content: script.content
})),
styles: styles.map(style => ({
src: style.src ? assetPublicPath + style.src : undefined,
content: style.content
}))
};
});
}
三、沙箱隔离机制深度解析
3.1 沙箱的三种实现方式
qiankun 根据浏览器支持情况,自动选择最优的沙箱实现:
支持 Proxy
不支持 Proxy
单实例
多实例
启动微应用
浏览器支持
ProxySandbox
SnapshotSandbox
应用模式
ProxySandbox
StrictSandbox/LegacySandbox
3.2 沙箱方案详细对比
| 沙箱类型 | 实现原理 | 隔离性 | 性能开销 | 兼容性 | 应用场景 |
|---|---|---|---|---|---|
| SnapshotSandbox | 激活时快照window,卸载时恢复 | ⭐⭐ 弱,仅在卸载时恢复 | ⭐⭐⭐ 高,全量拷贝 | ✅ 全兼容 | 老版本IE浏览器 |
| ProxySandbox (单例) | Proxy拦截window操作 | ⭐⭐⭐⭐⭐ 强,运行时隔离 | ⭐⭐⭐⭐ 低,按需代理 | ES6+ | 现代浏览器单实例 |
| StrictSandbox (多例) | Proxy + window Diff | ⭐⭐⭐⭐⭐ 强,支持多实例 | ⭐⭐⭐ 中,额外Diff | ES6+ | 现代浏览器多实例 |
3.3 ProxySandbox 源码解析
javascript
// qiankun@2.10.16 src/sandbox/proxySandbox.ts
// 核心:通过 Proxy 拦截全局对象的读写操作
export class ProxySandbox implements SandBox {
private name: string;
private proxy: WindowProxy;
private addedPropsMapInSandbox = new Map<PropertyKey, any>();
private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any>();
constructor(name: string) {
this.name = name;
const fakeWindow = Object.create(null) as Window;
// 核心 Proxy 配置
this.proxy = new Proxy(fakeWindow, {
set: (target: Window, p: PropertyKey, value: any): boolean => {
// 记录新增属性
if (!target.hasOwnProperty(p)) {
this.addedPropsMapInSandbox.set(p, value);
}
// 记录修改的原值
if (!this.modifiedPropsOriginalValueMapInSandbox.has(p)) {
const originalValue = (window as any)[p];
this.modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
}
// 设置到沙箱
target[p] = value;
// 同步到真实 window (需要注意!)
(window as any)[p] = value;
return true;
},
get: (target: Window, p: PropertyKey): any => {
// 优先从沙箱读取
if (target.hasOwnProperty(p)) {
return target[p];
}
// 回退到真实 window
const value = (window as any)[p];
// 处理不可配置属性
if (typeof value === 'function' && !isConstructable(value)) {
value = value.bind(window);
}
return value;
}
});
}
// 激活沙箱
active() {
// 恢复之前修改的属性
this.modifiedPropsOriginalValueMapInSandbox.forEach((originalValue, p) => {
(window as any)[p] = this.proxy[p];
});
// 添加新增属性
this.addedPropsMapInSandbox.forEach((value, p) => {
(window as any)[p] = value;
});
}
// 卸载沙箱
inactive() {
// 删除新增属性
this.addedPropsMapInSandbox.forEach((_, p) => {
delete (window as any)[p];
});
// 恢复原始值
this.modifiedPropsOriginalValueMapInSandbox.forEach((originalValue, p) => {
(window as any)[p] = originalValue;
});
}
}
3.4 StrictSandbox 多实例隔离
javascript
// qiankun@2.10.16 src/sandbox/strictSandbox.ts
// 支持多个微应用同时运行,完全隔离
export class StrictSandbox implements SandBox {
private proxy: WindowProxy;
private injectedProps = new Set<PropertyKey>();
constructor(name: string) {
const fakeWindow = Object.create(null);
this.proxy = new Proxy(fakeWindow, {
set(target, p, value) {
// 只写入沙箱,不同步到真实 window
target[p] = value;
// 记录注入的属性
this.injectedProps.add(p);
return true;
},
get(target, p) {
// 优先从沙箱读取
if (p in target) {
return target[p];
}
// 从真实 window 读取(但拦截不可配置属性)
const value = (window as any)[p];
// 处理 this 指向问题
if (typeof value === 'function') {
if (value === window.alert) {
return value.bind(window);
}
if (value === window.setTimeout) {
return value.bind(window);
}
}
return value;
},
has(target, p) {
return p in target || p in window;
}
});
}
}
四、样式隔离方案详解
4.1 样式隔离的三种策略
样式隔离
动态样式表
严格样式隔离
scoped CSS
卸载时移除
添加特定属性选择器
CSS Modules/Shadow DOM
4.2 样式隔离方案对比
| 方案 | 实现方式 | 隔离效果 | 性能影响 | 局限性 | qiankun支持 |
|---|---|---|---|---|---|
| 动态样式表 | 微应用卸载时移除style标签 | ⭐⭐⭐ 只隔离时间维度 | ⭐⭐⭐⭐⭐ 低 | 无法隔离同时运行的微应用 | ✅ 默认支持 |
| 严格样式隔离 | CSS选择器添加div[data-qiankun]前缀 |
⭐⭐⭐⭐ 空间维度隔离 | ⭐⭐⭐ 中 | 无法处理动态插入的样式 | ✅ 配置开启 |
| Shadow DOM | 完全隔离的DOM树 | ⭐⭐⭐⭐⭐ 完美隔离 | ⭐⭐ 较高 | 事件冒泡、全局样式失效 | ❌ 需要手动实现 |
| CSS Modules | 编译时生成唯一类名 | ⭐⭐⭐⭐⭐ 完美隔离 | ⭐⭐⭐⭐ 低 | 需要构建工具支持 | ❌ 需要微应用改造 |
4.3 严格样式隔离实现
javascript
// qiankun@2.10.16 src/sandbox/patchers/style.ts
// 核心逻辑:重写 DOM 操作方法,给样式添加容器前缀
function strictIsolation(callback: () => void, appName: string) {
// 给样式表添加容器前缀
const containerPrefix = `div[data-qiankun="${appName}"]`;
// 重写 document.head.appendChild
const originalAppendChild = document.head.appendChild;
document.head.appendChild = function(child) {
if (child.tagName === 'STYLE' || child.tagName === 'LINK') {
// 标记为微应用样式
child.setAttribute('data-qiankun-app', appName);
// 处理样式内容
if (child.tagName === 'STYLE') {
processStyleContent(child, containerPrefix);
}
}
return originalAppendChild.call(this, child);
};
callback();
// 恢复原始方法
document.head.appendChild = originalAppendChild;
}
function processStyleContent(styleElement: HTMLStyleElement, prefix: string) {
const cssRules = styleElement.sheet?.cssRules || [];
for (let i = 0; i < cssRules.length; i++) {
const rule = cssRules[i];
if (rule.type === CSSRule.STYLE_RULE) {
// 添加前缀到选择器
const originalSelector = rule.selectorText;
const scopedSelector = `${prefix} ${originalSelector}`;
// 重写选择器(需要重新生成样式规则)
styleElement.sheet?.deleteRule(i);
styleElement.sheet?.insertRule(
`${scopedSelector} { ${rule.style.cssText} }`,
i
);
}
}
}
4.4 样式冲突实战案例
场景: 主应用使用 Element-UI,微应用使用 Ant Design,两者都有 .btn 类名冲突。
解决方案对比:
| 方案 | 配置方式 | 效果 | 推荐指数 |
|---|---|---|---|
| 关闭严格隔离 + 命名约定 | sandbox: { strictStyleIsolation: false } |
依赖人工规范 | ⭐⭐ |
| 开启严格隔离 | sandbox: { strictStyleIsolation: true } |
自动添加前缀 | ⭐⭐⭐⭐ |
| CSS Modules | 微应用改造构建配置 | 完全隔离 | ⭐⭐⭐⭐⭐ |
| Shadow DOM | 手动封装组件 | 完美隔离 | ⭐⭐⭐ |
最佳实践代码:
javascript
// 主应用配置
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'micro-app',
entry: '//localhost:7100',
container: '#subapp',
activeRule: '/micro',
// 方案1: 开启严格样式隔离
sandbox: {
strictStyleIsolation: true, // 推荐
// experimentalStyleIsolation: true // 另一种隔离方式
}
}
]);
start();
css
/* 方案2: 微应用使用 CSS Modules(推荐) */
/* App.module.css */
.container {
padding: 20px;
}
.button {
background: blue;
}
/* 编译后会变成 */
.App_container__abc123 {
padding: 20px;
}
.App_button__def456 {
background: blue;
}
五、应用生命周期管理
5.1 完整生命周期流程
注册微应用
首次激活路由
路由参数变化(可选)
离开路由
离开路由
重新进入路由
应用卸载
Bootstrap
Mount
Update
Unmount
只执行一次
加载资源、初始化
每次进入路由执行
渲染DOM、启动应用
离开路由时执行
销毁实例、清理资源
5.2 生命周期钩子详解
| 钩子函数 | 执行时机 | 执行次数 | 用途 | 注意事项 |
|---|---|---|---|---|
| bootstrap | 应用首次加载时 | 1次 | 初始化配置、资源预加载 | 不要在这里渲染DOM |
| mount | 应用激活时 | 多次 | 渲染视图、启动定时器 | 确保可重复调用 |
| update | 路由参数变化时 | 多次(可选) | 更新状态、重新渲染 | 需要手动调用 |
| unmount | 应用失活时 | 多次 | 清理定时器、销毁实例 | 必须彻底清理 |
5.3 生命周期实现示例
javascript
// React 微应用生命周期导出
// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 webpack publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './public-path';
let root = null; // 保存实例引用
// ✅ 正确的生命周期实现
export async function bootstrap() {
console.log('React app bootstrap');
// 只执行一次的初始化逻辑
// 注册全局组件、初始化配置等
}
export async function mount(props) {
console.log('React app mount', props);
// 保存容器和props
const { container } = props;
// 渲染应用
root = ReactDOM.createRoot(
container
? container.querySelector('#root')
: document.querySelector('#root')
);
root.render(
<App
// 传递 qiankun 的 props
{...props}
// 主应用通信方法
onGlobalStateChange={props.onGlobalStateChange}
setGlobalState={props.setGlobalState}
/>
);
// 注册全局状态监听
props.onGlobalStateChange?.((state, prev) => {
console.log('全局状态变化:', state, prev);
}, true);
}
export async function unmount(props) {
console.log('React app unmount');
// ✅ 彻底清理资源
if (root) {
root.unmount();
root = null;
}
// 清理副作用
// 清除定时器
// 取消事件监听
// 清空全局变量
}
// ✅ 独立运行时的支持(非 qiankun 环境)
if (!window.__POWERED_BY_QIANKUN__) {
mount({ container: null });
}
javascript
// Vue3 微应用生命周期导出
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
let instance = null;
// ✅ Vue3 生命周期实现
export async function bootstrap() {
console.log('Vue3 app bootstrap');
}
export async function mount(props) {
console.log('Vue3 app mount', props);
const { container } = props;
instance = createApp(App);
instance.use(store);
instance.use(router);
// 挂载到指定容器
const appContainer = container
? container.querySelector('#app')
: document.querySelector('#app');
instance.mount(appContainer);
// 接收主应用传递的状态
if (props.onGlobalStateChange) {
props.onGlobalStateChange((state, prev) => {
console.log('全局状态:', state);
// 更新 Vuex store
store.commit('setGlobalState', state);
}, true);
}
}
export async function unmount() {
console.log('Vue3 app unmount');
instance?.unmount();
instance = null;
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
mount({ container: null });
}
5.4 生命周期常见陷阱
| 问题 | 错误代码 | 正确做法 | 影响 |
|---|---|---|---|
| 重复挂载 | mount() 中没有保存实例引用 |
保存实例到外部变量 | 内存泄漏 |
| 未清理定时器 | unmount() 为空 |
clearInterval() 持久化ID |
内存泄漏 |
| 未清理事件监听 | 没有移除 addEventListener |
removeEventListener() |
内存泄漏 |
| 全局变量污染 | 直接修改 window.xxx |
使用 window.__MICRO_APP_NAME__xxx |
沙箱逃逸 |
| 路由守卫冲突 | 微应用 router.beforeEach 未清理 |
保存守卫引用,unmount时移除 | 路由混乱 |
六、通信机制设计
6.1 通信方案全景图
微前端通信
基于qiankun
自定义通信
GlobalState
Props传递
EventBus
LocalStorage
CustomEvents
PostMessage
actions模式
父传子
发布订阅
本地存储
浏览器事件
跨域iframe
6.2 通信方案对比
| 方案 | 适用场景 | 实时性 | 复杂度 | 局限性 |
|---|---|---|---|---|
| GlobalState | 跨应用状态共享 | ✅ 实时同步 | ⭐⭐ 中 | 仅限qiankun框架 |
| Props传递 | 主应用→微应用单向数据 | ⚡ 即时 | ⭐ 低 | 只能向下传递 |
| EventBus | 跨应用事件通信 | ✅ 实时 | ⭐⭐⭐ 较高 | 需要手动管理 |
| LocalStorage | 跨域、持久化存储 | ❌ 需轮询 | ⭐⭐ 低 | 不同步、容量小 |
| CustomEvents | 同域应用通信 | ✅ 实时 | ⭐⭐ 中 | 仅限同源 |
| PostMessage | iframe跨域通信 | ✅ 实时 | ⭐⭐⭐ 高 | 序列化开销 |
6.3 GlobalState 核心实现
javascript
// qiankun@2.10.16 src/globalState.ts
// 核心:发布订阅模式 + 观察者模式
class GlobalState {
private state: Record<string, any> = {};
private deps: Record<string, Function[]> = {};
private cloneState = (state) => JSON.parse(JSON.stringify(state));
// 初始化状态
init(state: Record<string, any>) {
this.state = this.cloneState(state);
}
// 获取状态
get(prop?: string): any {
if (prop) {
return this.state[prop];
}
return this.state;
}
// 更新状态(核心)
set(state: Record<string, any>) {
const prevState = this.cloneState(this.state);
const nextState = { ...this.state, ...state };
this.state = nextState;
// 通知所有订阅者
this.notify(prevState, nextState);
}
// 订阅状态变化
on(listener: Function): () => void {
const id = Math.random().toString(36).substr(2);
this.deps[id] = listener;
// 返回取消订阅函数
return () => delete this.deps[id];
}
// 通知所有订阅者
private notify(prevState: Record<string, any>, nextState: Record<string, any>) {
Object.keys(this.deps).forEach(id => {
const listener = this.deps[id];
listener(nextState, prevState);
});
}
}
6.4 实战:完整的通信系统
javascript
// 主应用:通信中心
// src/main/index.js
import { registerMicroApps, start, initGlobalState } from 'qiankun';
// ✅ 方案1: 使用 qiankun 的 GlobalState(推荐)
const initialState = {
user: null,
token: '',
theme: 'light',
lang: 'zh-CN',
// 应用间数据
sharedData: {}
};
// 初始化全局状态
const { onGlobalStateChange, setGlobalState } = initGlobalState(initialState);
// 监听全局状态变化
onGlobalStateChange((state, prev) => {
console.log('主应用检测到状态变化:', state, prev);
// 同步到本地状态
if (state.token !== prev.token) {
// 处理登录状态变化
}
if (state.theme !== prev.theme) {
// 切换主题
document.documentElement.setAttribute('data-theme', state.theme);
}
}, true);
// 注册微应用
registerMicroApps([
{
name: 'micro-vue',
entry: '//localhost:7101',
container: '#subapp-viewport',
activeRule: '/vue',
props: {
// ✅ 方案2: 通过 props 传递方法
routerBase: '/vue',
// 传递给微应用的方法
getGlobalData: () => getGlobalState(),
updateUserInfo: (userInfo) => {
setGlobalState({ user: userInfo });
},
// 事件总线(备选方案)
eventBus: {
emit: (event, data) => {
// 广播事件给所有微应用
microApps.forEach(app => {
app.eventBus?.emit(event, data);
});
},
on: (event, callback) => {
// 注册事件监听
}
}
}
},
{
name: 'micro-react',
entry: '//localhost:7102',
container: '#subapp-viewport',
activeRule: '/react',
props: {
routerBase: '/react',
getGlobalData: () => getGlobalState()
}
}
]);
start();
// 模拟登录
function login(userInfo) {
setGlobalState({
user: userInfo,
token: 'mock-token-123'
});
}
javascript
// 微应用 Vue3:使用全局状态
// src/main.js
export async function mount(props) {
const { container } = props;
const app = createApp(App);
const router = createRouter({
base: props.routerBase,
routes
});
// ✅ 接收全局状态
if (props.onGlobalStateChange) {
props.onGlobalStateChange((state, prev) => {
console.log('Vue微应用收到状态变化:', state);
// 更新 Vuex/Pinia
store.commit('setUser', state.user);
store.commit('setToken', state.token);
store.commit('setTheme', state.theme);
// 响应主题变化
if (state.theme !== prev.theme) {
document.documentElement.setAttribute('data-theme', state.theme);
}
}, true);
}
// ✅ 修改全局状态
if (props.setGlobalState) {
// 更新语言
const changeLanguage = (lang) => {
props.setGlobalState({ lang });
i18n.global.locale = lang;
};
// 挂载到 app 实例
app.config.globalProperties.$changeLanguage = changeLanguage;
}
// ✅ 使用 props 传递的方法
app.config.globalProperties.$getUserInfo = props.getGlobalData;
app.use(router);
app.use(store);
app.mount(container ? container.querySelector('#app') : '#app');
}
6.5 EventBus 事件总线(备选方案)
javascript
// src/utils/eventBus.js
// 发布订阅模式实现跨应用通信
class EventBus {
constructor() {
this.events = {};
}
// 订阅事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
// 取消订阅
off(event, callback) {
if (!this.events[event]) return;
if (callback) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
} else {
delete this.events[event];
}
}
// 发布事件
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
callback(data);
});
}
// 订阅一次
once(event, callback) {
const onceCallback = (data) => {
callback(data);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
// 单例模式
const eventBus = new EventBus();
export default eventBus;
// 主应用中使用
import eventBus from './utils/eventBus';
// 订阅事件
eventBus.on('user:login', (userInfo) => {
console.log('用户登录:', userInfo);
});
// 微应用中触发
eventBus.emit('user:login', { name: '张三', id: 1 });
七、性能优化实战策略
7.1 性能优化全景图
qiankun性能优化
加载优化
预加载微应用
CDN加速
资源压缩
按需加载
运行时优化
沙箱优化
样式隔离优化
公共依赖提取
缓存策略
渲染优化
骨架屏
渐进式加载
虚拟滚动
懒加载组件
监控优化
性能监控
错误上报
用户行为追踪
7.2 加载性能优化方案
| 优化手段 | 实现方式 | 效果 | 难度 | 推荐指数 |
|---|---|---|---|---|
| 预加载 | prefetch、preload |
⭐⭐⭐⭐⭐ 首次加载提升50%+ | ⭐⭐ 简单 | ⭐⭐⭐⭐⭐ |
| 公共依赖 | webpack externals | ⭐⭐⭐⭐ 重复资源减少70% | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐⭐ |
| CDN加速 | 静态资源上CDN | ⭐⭐⭐⭐ 加载速度提升3-5倍 | ⭐⭐ 简单 | ⭐⭐⭐⭐⭐ |
| 代码分割 | dynamic import | ⭐⭐⭐ 首屏体积减少40% | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ |
| SSR | 服务端渲染 | ⭐⭐⭐⭐⭐ 首屏<1s | ⭐⭐⭐⭐⭐ 复杂 | ⭐⭐⭐ |
7.3 预加载策略实现
javascript
// 主应用:配置预加载
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'micro-app1',
entry: '//localhost:7101',
container: '#subapp',
activeRule: '/app1',
// ✅ 关键配置:预加载策略
props: {
// 手动控制预加载时机
prefetch: 'all' // 或 'app1' 只预加载特定应用
}
},
{
name: 'micro-app2',
entry: '//localhost:7102',
container: '#subapp',
activeRule: '/app2'
}
]);
// ✅ 方案1: 全局预加载(推荐)
start({
prefetch: 'all', // 预加载所有微应用
// prefetch: true, // 与 'all' 相同
// prefetch: [], // 不预加载
// prefetch: ['app1'], // 只预加载指定应用
// 预加载策略
fetch: (url) => {
// 自定义 fetch 逻辑
return fetch(url, {
timeout: 10000,
headers: {
'Cache-Control': 'max-age=3600'
}
});
}
});
// ✅ 方案2: 手动控制预加载
const { prefetchApps } = qiankun;
// 在空闲时预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
prefetchApps([
{ name: 'micro-app1', url: '//localhost:7101' }
]);
});
} else {
setTimeout(() => {
prefetchApps([
{ name: 'micro-app2', url: '//localhost:7102' }
]);
}, 2000);
}
7.4 公共依赖提取(重要)
javascript
// webpack.config.js (主应用和微应用都需要配置)
// 目标:让所有应用共享同一个 React/Vue 实例
module.exports = {
// ✅ 关键配置:外部化依赖
externals: {
react: 'React',
'react-dom': 'ReactDOM',
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
'element-plus': 'ElementPlus',
antd: 'antd'
},
optimization: {
// ✅ 代码分割
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: 10,
name: 'vendors'
},
common: {
name: 'common',
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
};
// ✅ index.html (主应用) - 引入公共依赖的 CDN
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>微前端主应用</title>
<!-- 公共依赖 CDN (推荐使用 unpkg 或 cdnjs) -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- 样式库 -->
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
<link rel="stylesheet" href="https://unpkg.com/antd/dist/antd.min.css">
</head>
<body>
<div id="app"></div>
<div id="subapp-viewport"></div>
</body>
</html>
// ✅ 微应用 webpack 配置
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
// ✅ 关键:让 qiankun 能够加载 UMD 模块
globalObject: 'this'
}
};
7.5 缓存策略
javascript
// ✅ Service Worker 缓存
// public/sw.js
const CACHE_NAME = 'qiankun-v1';
const urlsToCache = [
'/',
'/main.js',
'/main.css'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中则返回缓存
if (response) {
return response;
}
return fetch(event.request);
})
);
});
// 在主应用中注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
// ✅ LocalStorage 缓存微应用资源
class MicroAppCache {
constructor() {
this.CACHE_PREFIX = 'micro_app_cache_';
this.CACHE_EXPIRE = 24 * 60 * 60 * 1000; // 24小时
}
set(key, value) {
const data = {
value,
timestamp: Date.now()
};
localStorage.setItem(
this.CACHE_PREFIX + key,
JSON.stringify(data)
);
}
get(key) {
const item = localStorage.getItem(this.CACHE_PREFIX + key);
if (!item) return null;
const data = JSON.parse(item);
if (Date.now() - data.timestamp > this.CACHE_EXPIRE) {
this.remove(key);
return null;
}
return data.value;
}
remove(key) {
localStorage.removeItem(this.CACHE_PREFIX + key);
}
}
const cache = new MicroAppCache();
export default cache;
八、完整实践案例
8.1 项目架构设计
主应用 Main App
Vue3 + Vite
用户中心子应用
React 18
订单管理子应用
Vue3
数据分析子应用
Angular 15
消息通知子应用
Vue2
公共依赖 CDN
Global State
全局状态管理
Event Bus
事件总线
8.2 最佳实践清单
markdown
## ✅ 主应用开发检查清单
### 配置
- [ ] 配置所有微应用的 `activeRule` 和 `entry`
- [ ] 开启 `prefetch: true` 预加载
- [ ] 配置 `sandbox` 沙箱隔离
- [ ] 初始化 `GlobalState` 全局状态
- [ ] 配置生命周期钩子
### 通信
- [ ] 传递 `getGlobalState` 和 `setGlobalState`
- [ ] 提供共享工具方法(如 `fetchUser`)
- [ ] 配置事件总线(可选)
### 优化
- [ ] CDN 加速公共依赖
- [ ] 配置 Service Worker 缓存
- [ ] 实现骨架屏加载
---
## ✅ 微应用开发检查清单
### 配置
- [ ] 配置 webpack `library` 和 `libraryTarget`
- [ ] 配置 `externals` 外部化依赖
- [ ] 配置开发服务器 CORS
- [ ] 实现 `public-path.js` 动态路径
### 生命周期
- [ ] 实现 `bootstrap` 钩子(初始化)
- [ ] 实现 `mount` 钩子(挂载)
- [ ] 实现 `unmount` 钩子(卸载)
- [ ] 在 `unmount` 中彻底清理资源
### 通信
- [ ] 接收并使用主应用传递的 props
- [ ] 监听 `onGlobalStateChange`
- [ ] 调用 `setGlobalState` 更新状态
### 优化
- [ ] 使用代码分割减少体积
- [ ] 懒加载非关键组件
- [ ] 优化渲染性能
---
## ✅ 样式隔离检查清单
- [ ] 主应用和微应用使用不同的 CSS 前缀
- [ ] 开启 `experimentalStyleIsolation`
- [ ] 避免使用全局样式
- [ ] 使用 CSS Modules 或 scoped CSS
- [ ] 检查第三方库样式冲突
---
## ✅ 性能优化检查清单
- [ ] 提取公共依赖到 CDN
- [ ] 开启微应用预加载
- [ ] 实现资源缓存策略
- [ ] 优化 webpack 打包配置
- [ ] 使用 performance API 监控性能
- [ ] 实现加载进度提示
九、常见问题与解决方案
9.1 问题排查全景图
白屏
样式错乱
路由冲突
通信失败
内存泄漏
微应用加载失败
问题类型
- 检查网络请求
- 检查样式隔离
- 检查路由配置
- 检查全局状态
- 检查资源清理
F12 Network 面板
跨域? 路径错误?
开启严格隔离
检查 CSS 优先级
统一路由模式
检查 activeRule
检查 GlobalState
检查 props 传递
检查 unmount 清理
使用性能分析
9.2 常见问题速查表
| 问题现象 | 可能原因 | 解决方案 | 优先级 |
|---|---|---|---|
| 白屏 | 跨域、路径错误、资源404 | 配置 CORS、检查 entry 路径 | ⭐⭐⭐⭐⭐ |
| 样式丢失 | 样式隔离、CDN 加载失败 | 开启 experimentalStyleIsolation |
⭐⭐⭐⭐ |
| 路由跳转失败 | activeRule 不匹配、路由模式冲突 | 统一 hash/history 模式 | ⭐⭐⭐⭐ |
| 全局变量污染 | 沙箱逃逸、未使用沙箱 | 确保 sandbox: true |
⭐⭐⭐⭐ |
| 内存泄漏 | 定时器未清理、事件监听未移除 | 在 unmount 中彻底清理 |
⭐⭐⭐⭐⭐ |
| 通信失效 | GlobalState 未初始化、props 未传递 | 检查生命周期钩子 | ⭐⭐⭐ |
| 加载缓慢 | 未预加载、公共依赖重复提取 | 开启 prefetch: true |
⭐⭐⭐⭐ |
| 重复挂载 | mount 钩子实现错误 |
保存实例引用、避免重复渲染 | ⭐⭐⭐ |
9.3 调试技巧
javascript
// ✅ 技巧1: 开启 qiankun 调试模式
if (process.env.NODE_ENV === 'development') {
// 在浏览器控制台可以访问
window.__QIANKUN_SHARED_GLOBAL_STATE__;
window.__POWERED_BY_QIANKUN__;
// 监听所有微应用生命周期
window.addEventListener('qiankun:error', (event) => {
console.error('[qiankun] 错误:', event.detail);
});
}
// ✅ 技巧2: 自定义错误处理
start({
errorHandler: (error) => {
console.error('[qiankun] 全局错误:', error);
// 上报错误
if (error.message.includes('Failed to fetch')) {
// 处理加载失败
alert('微应用加载失败,请检查网络');
}
}
});
// ✅ 技巧3: 性能监控
const startTime = performance.now();
registerMicroApps([
{
name: 'micro-app',
entry: '//localhost:7101',
container: '#subapp',
activeRule: '/app',
props: {
onReady: () => {
const endTime = performance.now();
console.log(`微应用加载耗时: ${endTime - startTime}ms`);
}
}
}
]);
// ✅ 技巧4: 生命周期追踪
function wrapLifeCycle(name, fn) {
return async (...args) => {
const start = performance.now();
console.log(`[${name}] 开始执行`);
try {
await fn(...args);
console.log(`[${name}] 执行成功, 耗时: ${performance.now() - start}ms`);
} catch (error) {
console.error(`[${name}] 执行失败:`, error);
throw error;
}
};
}
export const bootstrap = wrapLifeCycle('bootstrap', async () => {
console.log('微应用 bootstrap');
});
十、总结
10.1 核心要点回顾
-
架构设计
- qiankun 基于 single-spa,提供了更完善的沙箱隔离和样式隔离方案
- 支持 HTML Entry,降低了微应用的接入成本
- 提供了完整的生命周期管理和通信机制
-
沙箱隔离
- ProxySandbox(推荐):运行时隔离,性能好
- SnapshotSandbox:兼容老浏览器,性能较差
- StrictSandbox:支持多实例,完全隔离
-
样式隔离
- 动态样式表:时间维度隔离
- 严格样式隔离:空间维度隔离
- CSS Modules:编译时隔离(推荐)
-
性能优化
- 预加载微应用
- 提取公共依赖
- CDN 加速
- 代码分割
- 缓存策略
-
最佳实践
- 统一技术栈版本
- 规范化路由配置
- 彻底清理资源
- 完善的错误处理
- 持续的性能监控
10.2 学习建议
学习路径
基础概念
实战项目
源码阅读
性能优化
理解微前端架构
掌握生命周期
学习通信机制
搭建主应用
改造子应用
实现通信
Sandbox 源码
Loader 源码
通信源码
性能监控
优化策略
最佳实践
10.3 参考资源
官方资源:
- qiankun 官方文档:https://qiankun.umijs.org/
- single-spa 官方文档:https://single-spa.js.org/
- qiankun GitHub:https://github.com/umijs/qiankun
推荐阅读:
- 微前端实践:https://micro-frontends.org/
- Webpack Module Federation:https://webpack.js.org/concepts/module-federation/
- 微前端设计模式:https://patterns.microfrontends.org/
源码版本:
- 本文章基于 qiankun@2.10.16
- React 18.2.0
- Vue 3.3.0
- single-spa@5.9.0