qiankun 样式隔离完全指南:用原生 CSS @scope 为微前端划定样式边界
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 提供了一套基于浏览器原生 CSSscope的运行时样式隔离机制通过sandbox.styleIsolation一个开关即可将子应用声明的所有样式规则限定在其容器内无需 Shadow DOM。本文将从概念模型、覆盖范围、边界与限制、启用方式到源码级实现原理完整讲解这套机制读完你既能正确地在项目中开启样式隔离也能理解其底层工作方式并规避常见坑点。什么是 qiankun 样式隔离样式隔离Style Isolation的目标是阻止子应用声明的 CSS 匹配到其容器之外的元素。qiankun 的这套机制有四个关键特征按需开启opt-in默认关闭只有显式配置才生效按应用粒度scoped per app每个应用独立决定是否隔离隔离与未隔离的应用可以共存基于浏览器原生 CSSscope不做任何 polyfill也不依赖 Shadow DOM挂在 sandbox 对象内因为运行时动态注入的样式依赖沙箱的 DOM 拦截能力——如果只隔离 CSS 而没有 JS 沙箱动态插入的样式会悄悄泄漏出去。该选项位于sandbox配置对象中JS 沙箱默认开启而sandbox.styleIsolation默认值为false见 AppConfiguration 中的配置参考。核心模型一个 scope 块开启sandbox: { styleIsolation: true }后qiankun 会把应用的所有样式规则限制在以其应用名app name标识的容器内。概念上应用的样式变成这样scope ([data-namecatalog]) { /* 该微应用的所有规则 */ }qiankun 会给每个应用容器打上等于注册应用名的data-name属性并从这个属性推导出 scope root 选择器。scope root 固定为[data-nameappName]不可自定义——没有任何配置项允许你传入自己的作用域根选择器。需要特别强调应用仍然停留在宿主文档中不会被移入 Shadow DOM。因此既有的文档级集成如document.querySelector、全局事件、门户等依然可以正常工作但需要遵循下面将要讲到的单向边界约束。隔离覆盖范围开启样式隔离后qiankun 覆盖以下三类样式来源样式来源隔离开启时的行为内联style其规则被限定到应用容器内。外部link relstylesheetqiankun 先读取样式表内容并完成 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继承的color、font等属性依然会从宿主传给子应用根元素共享的 CSS 自定义属性variables同样可以穿透边界。Portal 需要特别处理。菜单、对话框、tooltip 等如果渲染在document.body下而不是微应用容器内就处于 scope root 之外应用的 scoped 选择器将无法匹配到它们。推荐做法是将 portal 的挂载根放在props.container之内或者为容器外的这块界面显式编写样式。隔离是按应用配置的。隔离的应用与未隔离的应用可以共存但未隔离应用的 CSS 仍然可能影响整个页面。要求与限制在启用该选项前请逐条核对以下硬性要求与已知限制必须支持原生 CSSscope。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最终被布尔化为开关并随沙箱配置下发给样式转换管线。验证隔离结果启用后建议按以下步骤端到端验证在宿主和微应用中各添加一个同 class 的测试元素在微应用 CSS 中给该 class 一个明显的样式确认只有微应用容器内的元素获得该样式卸载应用确认其容器和动态插入的样式都被清理干净。如果启用隔离后应用反而失去样式优先检查两点浏览器是否支持scopeCORS 是否拦截了某个外部 CSS 请求。源码级原理样式重写管线本节对应维护者视角的内部文档 style isolation internals以及实际实现 style.ts 与 link.ts。qiankun v3 的样式隔离是一个运行时机制开启后子应用携带的每张样式表都会被重写使其规则只在应用容器内匹配。核心转换入口是transpileStyleText它先提取需要保持全局的 at-rule再做 keyframes 重命名最后把剩余内容包进scope块scope ([data-nameyour-app]) { /* 应用规则已重写 */ }样式隔离默认关闭。如果不设置sandbox.styleIsolationstyle与link节点会原样通过加载器不做任何处理。内联styletextContent 重写对于内联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 relstylesheetblob-link 方案原生scope只能包裹你能控制的 CSS 文本而浏览器加载外部样式表是不透明的——没有钩子可以在其到达时包裹它。因此在样式隔离下qiankun 会阻止浏览器原生加载link改为自己接管 fetch。实现细节在 link.ts 中解析href相对于 base URL然后移除href属性把原始地址暂存在data-href中。没有href浏览器就永远不会加载未 scoped 的样式表通过应用装饰后的fetch获取 CSS执行与内联样式相同的scope包裹转换这一次会传入已解析的样式表 URL 作为 base因此相对url(...)与import都能正确解析以blob:URL 形式喂回同一个link元素转换后的 CSS 变成Blob其对象 URL 重新设置为元素的href。节点身份node identity被刻意保留——qiankun 只换href从不换元素。这让所有原生link语义免费保留media、disabled、title以及document.styleSheets中的条目都保持有效流式加载器待处理样式表阻塞后续脚本的记账逻辑仍然看到一个正常的 pending linkblob href 落地时load事件触发应用为动态注入的link挂载的onload/onerror处理器也继续工作。如果 fetch 或转换失败不会设置任何 blob href——元素本身不会发出事件。此时 qiankun 手动在 link 上派发一个error事件并丢弃该样式表而不是回退为未 scoped 加载。丢弃是刻意选择无法被 scoped 的样式表不允许泄漏到全局。此外转换后的样式表会先按 URL 缓存再按 (appName, scopeRoot) 缓存键缓存同一 URL 的并发 fetch 会被去重pendingFetches映射。因此同一个外部样式表被多个应用共享时只会被 fetch 和转换与不同 scope root 数量相等的次数。运行时 CSSOMinsertRule 拦截程序化插入的样式永远不会经过加载器因此 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 relpreload asstyle预热的响应只能被原生样式表请求复用。样式隔离开启后转换管线改用fetch()消费样式表原始 preload 会浪费。因此 qiankun 会把该 link 重写为asfetch并在非use-credentials时补上crossoriginanonymous让后续fetch()能复用预热响应见 link.ts 中postProcessPreloadLink。另外当 ESM 沙箱激活时qiankun 会把relmodulepreload重写为relpreload asfetch——因为引擎导入的是重写后的 blob URL 而非原始模块 URL。这条重写不依赖样式隔离属于 ESM 沙箱的配套行为。与 qiankun 2.x 的差异v3 的样式隔离就是上面描述的scope blob-link 机制由单个布尔开关控制。qiankun 2.x 中的sandbox.strictStyleIsolation与sandbox.experimentalStyleIsolation基于 Shadow DOM在 v3 中已不存在唯一的旋钮就是sandbox.styleIsolation。从 2.x 迁移时请参考 Migrate from qiankun 2.x。配置参考OptionTypeDefaultDescriptionsandbox.styleIsolationbooleanfalse通过scope包裹启用运行时 CSS 隔离。启用后微应用的所有样式都被 scoping 到其容器[data-nameappName]内。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),仅供参考