qiankun 样式隔离完全指南:用原生 CSS @scope 为微前端划定样式边界
2026/9/21 15:50:11 网站建设 项目流程

qiankun 样式隔离完全指南:用原生 CSS @scope 为微前端划定样式边界

【免费下载链接】qiankun📦 🚀 Blazing fast, simple and complete solution for micro frontends.项目地址: https://gitcode.com/gh_mirrors/qi/qiankun

微前端架构中最常见也最棘手的问题之一,就是子应用(micro-app)的 CSS 泄漏到宿主页面或兄弟应用,造成全局样式互相污染。qiankun v3 提供了一套基于浏览器原生 CSS@scope的运行时样式隔离机制:通过sandbox.styleIsolation一个开关,即可将子应用声明的所有样式规则限定在其容器内,无需 Shadow DOM。本文将从概念模型、覆盖范围、边界与限制、启用方式到源码级实现原理,完整讲解这套机制,读完你既能正确地在项目中开启样式隔离,也能理解其底层工作方式并规避常见坑点。

什么是 qiankun 样式隔离

样式隔离(Style Isolation)的目标是:阻止子应用声明的 CSS 匹配到其容器之外的元素。qiankun 的这套机制有四个关键特征:

  • 按需开启(opt-in):默认关闭,只有显式配置才生效;
  • 按应用粒度(scoped per app):每个应用独立决定是否隔离,隔离与未隔离的应用可以共存;
  • 基于浏览器原生 CSS@scope:不做任何 polyfill,也不依赖 Shadow DOM;
  • 挂在 sandbox 对象内:因为运行时动态注入的样式依赖沙箱的 DOM 拦截能力——如果只隔离 CSS 而没有 JS 沙箱,动态插入的样式会悄悄泄漏出去。

该选项位于sandbox配置对象中:JS 沙箱默认开启,而sandbox.styleIsolation默认值为false(见 AppConfiguration 中的配置参考)。

核心模型:一个 @scope 块

开启sandbox: { styleIsolation: true }后,qiankun 会把应用的所有样式规则限制在以其应用名(app name)标识的容器内。概念上,应用的样式变成这样:

@scope ([data-name="catalog"]) { /* 该微应用的所有规则 */ }

qiankun 会给每个应用容器打上等于注册应用名的data-name属性,并从这个属性推导出 scope root 选择器。scope root 固定为[data-name="<appName>"],不可自定义——没有任何配置项允许你传入自己的作用域根选择器。

需要特别强调:应用仍然停留在宿主文档中,不会被移入 Shadow DOM。因此既有的文档级集成(如document.querySelector、全局事件、门户等)依然可以正常工作,但需要遵循下面将要讲到的"单向边界"约束。

隔离覆盖范围

开启样式隔离后,qiankun 覆盖以下三类样式来源:

样式来源隔离开启时的行为
内联<style>其规则被限定到应用容器内。
外部<link rel="stylesheet">qiankun 先读取样式表内容并完成 scoping,再使其生效。
运行时插入的规则与常见 CSS-in-JS 输出与子应用关联的规则在插入时即被 scoping。

两个容易踩坑的细节:

  • 外部样式表内的相对资源 URL(如url(...)@import)会以该样式表自身的 URL 为基准进行解析,因此可以安全使用相对路径;
  • 内联<style>内容不会针对微应用入口做 URL 重写。当宿主与微应用文档 base URL 不同时,内联样式中的相对资源请改用绝对 URL(或data:/blob:URL)。

另外,如果某个样式表无法被安全地 scoping(例如跨域获取失败),qiankun不会回退为全局应用未 scoping 的样式,而是直接丢弃该样式表——这是刻意设计的安全选择,详见下文"外部<link>的 blob-link 方案"。

一条单向边界

样式隔离阻止的是微应用的规则向外泄漏(leak out)。它不阻止宿主样式、继承属性(inherited properties)、浏览器默认样式以及共享的 CSS 自定义属性向内流入(flow in)子应用。

因此需要注意:

  • 宿主的全局选择器仍然可能命中微应用内部的元素——这是设计如此,不是 bug;
  • 继承的colorfont等属性依然会从宿主传给子应用根元素;
  • 共享的 CSS 自定义属性(variables)同样可以穿透边界。

Portal 需要特别处理。菜单、对话框、tooltip 等如果渲染在document.body下而不是微应用容器内,就处于 scope root 之外,应用的 scoped 选择器将无法匹配到它们。推荐做法是:将 portal 的挂载根放在props.container之内,或者为容器外的这块界面显式编写样式。

隔离是按应用配置的。隔离的应用与未隔离的应用可以共存,但未隔离应用的 CSS 仍然可能影响整个页面

要求与限制

在启用该选项前,请逐条核对以下硬性要求与已知限制:

  • 必须支持原生 CSS@scope。qiankun 不提供 polyfill,也没有任何降级回退方案。在@scope未被支持的浏览器中,包裹规则是惰性的(inert),样式不会被隔离。@scope是较新的 CSS 特性,务必对照你的目标浏览器矩阵(browser matrix)确认后再启用。
  • 跨域样式表需要 CORS。qiankun 必须能够读取样式表的 CSS 内容。无法被 fetch 的样式表会被丢弃而非全局泄漏。请确保微应用服务器以及第三方样式表来源返回正确的Access-Control-Allow-Origin响应头,否则隔离应用会以无样式状态渲染(并在控制台输出警告)。
  • @font-face保持全局。字体规则被刻意保留在全局,以便字体能正常加载。两个应用声明相同的font-family名称可能产生冲突,请为每个应用使用应用特有的font-family名称。
  • 关键帧(keyframes)名称在声明于 CSS 中时会被隔离。qiankun 会以应用为前缀重命名@keyframes并同步改写animation/animation-name引用;但如果动画名称是在 JavaScript 中动态拼接构造的(而非在 CSS 中字面书写),则无法随样式表一并改写,动画可能无法解析(不生效)。
  • 应用容器之外的内容都在作用域之外。这包括 portal,以及应用代码故意移动出去的节点。
  • 作用域以应用名(name)为键,而非实例句柄。并发存在的多个实例如果复用了同一个name,它们共享同一个 scope 选择器。当这些实例的 CSS 需要彼此隔离时,请为它们使用不同的应用名。

如何启用样式隔离

在需要隔离的应用上设置该选项即可。以手动加载为例(loadMicroApp的第二个参数):

import { loadMicroApp } from 'qiankun'; const container = document.getElementById('micro-app'); if (!container) throw new Error('micro-app container not found'); const microApp = loadMicroApp( { name: 'catalog', entry: 'https://catalog.example.com', container, }, { sandbox: { styleIsolation: true }, }, ); // 当页面不再展示该应用时: await microApp.unmount();

其他接入方式同样支持:

  • React / Vue 的<MicroApp>组件:通过其settingsprop 传入相同配置(settings: { sandbox: { styleIsolation: true } });
  • 路由驱动模式(registerMicroApps:把sandbox: { styleIsolation: true }放在该应用的configuration字段中。

从源码看,loadApp.ts 会把sandbox配置解构并规范化:typeof sandbox === 'object' ? { enabled: true, styleIsolation: Boolean(sandboxCfg.styleIsolation) } : sandboxCfg,即styleIsolation最终被布尔化为开关,并随沙箱配置下发给样式转换管线。

验证隔离结果

启用后,建议按以下步骤端到端验证:

  1. 在宿主和微应用中各添加一个同 class 的测试元素
  2. 在微应用 CSS 中给该 class 一个明显的样式;
  3. 确认只有微应用容器内的元素获得该样式;
  4. 卸载应用,确认其容器和动态插入的样式都被清理干净。

如果启用隔离后应用反而失去样式,优先检查两点:浏览器是否支持@scope;CORS 是否拦截了某个外部 CSS 请求。

源码级原理:样式重写管线

本节对应维护者视角的内部文档 style isolation internals,以及实际实现 style.ts 与 link.ts。

qiankun v3 的样式隔离是一个运行时机制:开启后,子应用携带的每张样式表都会被重写,使其规则只在应用容器内匹配。核心转换入口是transpileStyleText,它先提取需要保持全局的 at-rule,再做 keyframes 重命名,最后把剩余内容包进@scope块:

@scope ([data-name="your-app"]) { /* 应用规则,已重写 */ }

样式隔离默认关闭。如果不设置sandbox.styleIsolation<style><link>节点会原样通过加载器,不做任何处理。

内联<style>:textContent 重写

对于内联<style>元素,qiankun 读取其textContent,执行转换,再把 scoped 后的结果写回同一个节点。除了外层@scope包裹,转换还做了几件纯包裹做不对的事:

  • @font-face@namespace被提升(hoist)出@scope块并保持全局。把@font-face放进 scope 会破坏字体加载,@namespace必须处于文档级,因此两者都会被提升到样式表顶部(源码中extractAtRules负责抽取、transpileStyleTextSync负责拼接)。
  • @keyframes被按应用加前缀重命名,规则为__qk_<appName>_<name>(前缀常量QIANKUN_KEYFRAMES_PREFIX = '__qk_',见 style.ts)。同时所有animation/animation-name引用都会被改写匹配。这样两个应用都定义spin关键帧时不会互相覆盖——因为@scope只能 scoping 选择器,无法 scoping 全局的 keyframes 命名空间。
  • 内联样式的相对url(...)不会 rebase。当前内联样式路径不会向转换器传入样式表 base URL,因此当宿主与微应用共享同一文档 base 时无碍,否则请使用绝对、data:blob:URL。
  • @import被递归内联。每个被导入的样式表都通过应用装饰后的fetch拉取、以相同方式转换后拼接进来,并用 visited 集合去重。内联样式路径同样不会把导入 URL 解析到微应用入口,请使用绝对导入 URL。

由于内联@import可能需要网络往返,qiankun 会先同步清空<style>的 textContent,等所有内容解析完成后再填回 scoped CSS——这就避免了在 fetch 窗口期内未 scoped 的原始样式先全局生效。

外部<link rel="stylesheet">:blob-link 方案

原生@scope只能包裹你能控制的 CSS 文本,而浏览器加载外部样式表是"不透明"的——没有钩子可以在其到达时包裹它。因此在样式隔离下,qiankun 会阻止浏览器原生加载<link>,改为自己接管 fetch。实现细节在 link.ts 中:

  1. 解析href(相对于 base URL),然后移除href属性,把原始地址暂存在data-href中。没有href,浏览器就永远不会加载未 scoped 的样式表;
  2. 通过应用装饰后的fetch获取 CSS,执行与内联样式相同的@scope包裹转换(这一次会传入已解析的样式表 URL 作为 base,因此相对url(...)@import都能正确解析);
  3. blob:URL 形式喂回同一个<link>元素:转换后的 CSS 变成Blob,其对象 URL 重新设置为元素的href

节点身份(node identity)被刻意保留——qiankun 只换href,从不换元素。这让所有原生<link>语义免费保留:mediadisabledtitle以及document.styleSheets中的条目都保持有效;流式加载器"待处理样式表阻塞后续脚本"的记账逻辑仍然看到一个正常的 pending link(blob href 落地时load事件触发);应用为动态注入的<link>挂载的onload/onerror处理器也继续工作。

如果 fetch 或转换失败,不会设置任何 blob href——元素本身不会发出事件。此时 qiankun 手动在 link 上派发一个error事件并丢弃该样式表,而不是回退为未 scoped 加载。丢弃是刻意选择:无法被 scoped 的样式表不允许泄漏到全局

此外,转换后的样式表会先按 URL 缓存,再按 (appName, scopeRoot) 缓存键缓存;同一 URL 的并发 fetch 会被去重(pendingFetches映射)。因此同一个外部样式表被多个应用共享时,只会被 fetch 和转换与不同 scope root 数量相等的次数。

运行时 CSSOM:insertRule 拦截

程序化插入的样式永远不会经过加载器,因此 qiankun 在 CSSOM 层面对其进行拦截。当样式隔离生效时,CSSStyleSheet.prototype.insertRule会被 monkey-patch(带引用计数:只要还有任一样式隔离应用存活就保持安装,最后一个应用卸载时移除)。实现位于 forStandardSandbox.ts 的patchCSSOM中:

const patchedInsertRule = function insertRule(this: CSSStyleSheet, rule: string, index?: number): number { const ownerNode = this.ownerNode as HTMLElement | null; if (ownerNode) { const config = resolveStyleOwnerConfig(ownerNode); if (config?.styleIsolation) { const scopedRule = transpileStyleRule(rule, config.styleIsolation); return nativeInsertRule.call(this, scopedRule, index); } } return nativeInsertRule.call(this, rule, index); };

这条同步路径(transpileStyleRule)会跳过已经@scope包裹的规则以避免重复包裹,并让@font-face/@namespace保持全局,与静态转换保持一致。正是它保证了 CSS-in-JS 库和框架在运行时构建的样式表同样被 scoping。

Preload 重写

通过<link rel="preload" as="style">预热的响应只能被原生样式表请求复用。样式隔离开启后,转换管线改用fetch()消费样式表,原始 preload 会浪费。因此 qiankun 会把该 link 重写为as="fetch",并在非use-credentials时补上crossorigin="anonymous",让后续fetch()能复用预热响应(见 link.ts 中postProcessPreloadLink)。

另外,当 ESM 沙箱激活时,qiankun 会把rel="modulepreload"重写为rel="preload" as="fetch"——因为引擎导入的是重写后的 blob URL 而非原始模块 URL。这条重写不依赖样式隔离,属于 ESM 沙箱的配套行为。

与 qiankun 2.x 的差异

v3 的样式隔离就是上面描述的@scope+ blob-link 机制,由单个布尔开关控制。qiankun 2.x 中的sandbox.strictStyleIsolationsandbox.experimentalStyleIsolation(基于 Shadow DOM)在 v3 中已不存在,唯一的旋钮就是sandbox.styleIsolation。从 2.x 迁移时请参考 Migrate from qiankun 2.x。

配置参考

OptionTypeDefaultDescription
sandbox.styleIsolationbooleanfalse通过@scope包裹启用运行时 CSS 隔离。启用后,微应用的所有样式都被 scoping 到其容器([data-name="<appName>"])内。

styleIsolation是应用配置中sandbox对象内的按应用字段,作为第二个参数传给 loadMicroApp;完整字段列表见 AppConfiguration,任务导向的实操步骤见 Enable CSS style isolation。

测试与回归保障

仓库为样式隔离提供了完整的测试覆盖,可作为验证和参考:

  • 端到端测试style-isolation.spec.ts 包含对照组(未开启时子应用全局 CSS 泄漏进宿主)与开启后(CSS 停留在容器内)的断言,并覆盖无 body、多脚本、patch-append 等边界场景;
  • 单元测试link.test.ts 与 link-performance.test.ts 验证外部样式表的转换与缓存行为;
  • 沙箱侧测试attribution.test.ts 与 lifecycle.test.ts 验证insertRule按样式表当前 DOM 位置 scoping,以及 patch 的安装/卸载生命周期。

一句话总结sandbox.styleIsolation: true用浏览器原生@scope把子应用 CSS 关进以应用名命名的容器里,方向单一、无 polyfill、无回退;启用前核对浏览器支持与 CORS,启用后注意 portal、@font-face与动态 keyframes 三个边界,即可获得干净可靠的微前端样式隔离。

【免费下载链接】qiankun📦 🚀 Blazing fast, simple and complete solution for micro frontends.项目地址: https://gitcode.com/gh_mirrors/qi/qiankun

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询