文章

qiankun原理深度解析

qiankun 基于 single-spa 封装,补上了 HTML Entry(子应用零改造接入)与沙箱隔离(Proxy 拦截 window、样式作用域隔离)两块拼图。 理解 qiankun 本质是理解浏览器里如何实现应用级隔离,面试常考沙箱机制与 JS、CSS 隔离方案。

qiankun原理深度解析

一句话概括

qiankun 是基于 single-spa 封装的微前端框架,它补上了 single-spa 缺失的两块拼图:HTML Entry(子应用几乎零改造接入)和沙箱隔离(Proxy 拦截 window 读写、样式作用域隔离)。理解 qiankun,本质是理解”浏览器里如何实现应用级隔离”这个底层问题。

核心知识点

1. HTML Entry:子应用零改造接入

single-spa 的 JS Entry 要求子应用自己打包成 UMD,接入成本高。qiankun 的 HTML Entry 直接 fetch 子应用的 HTML,解析出 JS/CSS 动态加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// HTML Entry 核心流程(简化)
async function loadEntry(appName, entryUrl) {
  const html = await fetch(entryUrl).then(r => r.text());
  const doc = new DOMParser().parseFromString(html, 'text/html');
  const base = entryUrl.slice(0, entryUrl.lastIndexOf('/') + 1);

  // 提取 script 的 src,相对路径转绝对
  const scripts = [...doc.querySelectorAll('script')]
    .map(s => s.src)
    .filter(Boolean)
    .map(src => src.startsWith('http') ? src : base + src);

  // 提取样式
  const styles = [...doc.querySelectorAll('link[rel="stylesheet"]')]
    .map(l => l.href.startsWith('http') ? l.href : base + l.href);

  return { template: doc.body.innerHTML, scripts, styles };
}
// 关键价值:子应用保持原有构建产物,无需为微前端单独打包

2. Proxy 沙箱:拦截 window 读写

沙箱的核心是”让子应用以为自己在操作 window,实际写进隔离对象”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Proxy 沙箱:active 时激活,inactive 时还原
class ProxySandbox {
  active() {
    this.fakeWindow = {};
    this.proxy = new Proxy(window, {
      get: (target, key) =>
        key in this.fakeWindow ? this.fakeWindow[key] : target[key],
      set: (target, key, value) => {
        this.fakeWindow[key] = value; // 写进隔离对象,不污染真实 window
        return true;
      },
    });
  }
  inactive() {
    this.fakeWindow = {}; // 退出后销毁,全局零残留
  }
}
// 不支持 Proxy 的旧浏览器回退到快照沙箱(进入存快照、退出还原)

3. 样式隔离:作用域前缀

子应用样式互相污染是高频坑。qiankun 提供两种隔离,核心都是”给选择器加前缀”:

1
2
3
4
// experimentalStyleIsolation:给子应用样式规则加前缀
// 原始:.title { color: red }
// 转换后:div[data-qiankun="app1"] .title { color: red }
// strictStyleIsolation:用 Shadow DOM 天然隔离(但要注意弹窗等默认挂 body 的问题)

4. 生命周期与通信

子应用遵循 bootstrap/mount/unmount 契约,通信用 initGlobalState 的发布订阅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { initGlobalState } from 'qiankun';

// 基座侧:初始化全局状态
const actions = initGlobalState({ user: null });
actions.onGlobalStateChange((state, prev) => {
  console.log('用户信息变化:', state.user);
});
actions.setGlobalState({ user: { name: '张三' } });

// 子应用侧:接收/更新全局状态
export async function mount(props) {
  props.onGlobalStateChange((state) => console.log('子应用收到:', state.user));
  props.setGlobalState({ cart: 3 }); // 子应用也能反向更新
}

其实你每天都在用

  1. 支付宝里点击不同 Tab 切换的业务模块:每个 Tab 可能就是一个 qiankun 子应用,切换时触发 mount/unmount,各自加载卸载。
  2. 中后台系统里”打开一个新菜单,页面秒开”:子应用资源被 qiankun 缓存,二次进入走缓存,无需重新 fetch HTML。
  3. 同一页面里两个风格完全不同的模块:一个 React 写的、一个 Vue 写的,样式互不干扰,背后就是 Proxy 沙箱 + 样式前缀在起作用。
  4. 老系统渐进式升级:公司那个用了 8 年的 AngularJS 后台,新页面用 qiankun 挂 React 子应用一点点替换,不用整体重写。
  5. 登录态在多个子应用间自动同步:你登录一次,购物车、订单、会员中心各个子应用都拿到用户信息,靠的就是 initGlobalState 的全局状态广播。

常见误解(FAQ)

❌ 误区一:qiankun 和 single-spa 是竞争关系。 错误。qiankun 本身就是基于 single-spa 二次封装——single-spa 负责路由调度,qiankun 在其上加了 HTML Entry 和沙箱隔离。说”qiankun 比 single-spa 强”不准确,应该说”qiankun 是 single-spa 的开箱即用增强版”。

❌ 误区二:qiankun 的沙箱能隔离一切。 错误。Proxy 沙箱隔离的是 JS 全局变量,但隔离不了三类东西:直接操作 document 的代码、全局 CSS 选择器(需额外样式隔离)、以及 localStorage / cookie 这类真实共享存储。沙箱不是万能保险箱。

❌ 误区三:qiankun 子应用完全零改造。 错误。”零改造”是针对构建产物而言(不用重新打包 UMD),但业务代码通常要改:子应用要导出生命周期函数、要处理 publicPath、要避免污染全局、弹窗/全局样式要特殊处理。零改造是理想,小改造是现实。

❌ 误区四:用 Shadow DOM 做样式隔离就一劳永逸了。 错误。Shadow DOM 隔离彻底,但副作用明显:弹窗、下拉菜单默认挂载到 document.body,会跑到 Shadow Root 外导致样式失效;第三方组件库(如 antd 的 Modal)也常踩这个坑。所以 qiankun 默认用作用域前缀方案而非 Shadow DOM。

一句话总结

qiankun 的价值在于把微前端从”要自己造轮子”降到了”配置即用”——HTML Entry 降低接入成本、Proxy 沙箱解决变量污染、样式前缀解决样式冲突,而它的本质,是浏览器环境里应用级隔离的工程化答案。

本文由作者按照 CC BY 4.0 进行授权