qiankun 的 CSS 沙箱隔离机制
为什么需要CSS沙箱
在 qiankun 微前端框架中,由于每个子应用的开发和部署都是独立的,将主/子应用的资源整合到一起时,容易出现样式冲突的问题
因此,需要 CSS 沙箱来解决样式冲突问题,实现主应用以及各子应用之间的样式隔离,确保各自的样式独立运行,互不干扰
工程化手段
既然 CSS 沙箱是用来解决样式冲突的问题,那如果我通过工程化手段确保每个样式选择器名称都是唯一的,这样是不是就不需要 CSS 沙箱了?
使用工程化手段来生成唯一的 CSS 类名,常见解决方案有:
- BEM:不同项目用不同的前缀或命名规则来确保类名唯一性,避免样式冲突,详见 BEM命名规范
- CSS Module:通过构建工具配置(详见 webpack 启用 css-loader)在构建过程中自动生成唯一的类名。对了,vue3 中
<style module>标签也会被编译为 CSS Module,详见 Vue.js - 单文件组件|CSS功能 - CSS-in-JS: 在 JS 中定义 CSS 样式块,注入到 DOM 中,详见 CSS-in-JS 指南
但是这些方案都存在一些问题:
- 历史包袱:对于老旧项目,尤其是那些未采用现代工程化手段的项目,修改现有代码以支持新的样式管理方案(如 BEM 或 CSS-in-JS)需要大量的重构工作
- 第三方库:即使你确保了自己的样式选择器唯一,第三方库的样式仍可能会导致冲突
显然,工程化手段只能解决一部分问题,在实际应用中,可能需要结合使用工程化手段和 CSS 沙箱,以应对不同的样式管理需求
乾坤沙箱
乾坤目前存在三种 CSS 隔离机制,分别是动态样式隔离、影子DOM沙箱和作用域沙箱
- 动态样式隔离:qiankun 默认开启,可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离
- 影子DOM沙箱(Shadow DOM):手动开启 ,qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响
- 作用域沙箱(Scope CSS):手动开启 ,qiankun 会改写子应用所添加的样式,为所有样式规则增加一个特殊的选择器规则来限定其影响范围
你可能想问,开关在呢如何手动开启我想要的沙箱机制
在这个 乾坤API - start({ }) 中,有一个可选参数
sandbox,用于控制是否开启沙箱以及开启哪种沙箱
- true:默认值,开启动态样式隔离
- { strictStyleIsolation: true }:开启影子DOM沙箱
- { experimentalStyleIsolation: true }:开启作用域沙箱
动态样式隔离
乾坤会默认开启此沙箱
可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景子应用之间的样式隔离
实现原理是当子应用被加载时,其对应的样式会被注入到页面中;当子应用被卸载时,qiankun 会自动移除其样式,确保页面的样式环境保持干净
动态样式隔离虽然可以提供很好的隔离效果,但往往存在一些限制条件,所以在现实的使用中基本无法单独满足用户的需求
对于新的子应用,使用动态样式隔离 + 工程化手段两种方案结合的方式,基本能够解决样式冲突的问题
Shadow DOM 沙箱
手动开启,开启代码如下
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([...]) // 注册子应用
start({
sandbox: { strictStyleIsolation: true } // 开启 Shadow DOM 沙箱
})
这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响
Shadow DOM是什么?
Shadow DOM 是 Web Components 技术的一部分,它允许开发者创建一个封闭的 DOM 树,这个 DOM 树的样式和脚本与页面的主 DOM 树是隔离的。通过 Shadow DOM,可以确保子应用的样式和脚本不会影响到主应用或其他子应用,从而避免冲突和干扰
Shadow DOM,可以理解为是存在于 DOM 中的 DOM
记住!影子DOM 是独立存在的 DOM,有自己的作用域集,外部的配置不会影响到内部,内部的配置也不会影响外部
影子 DOM 允许将隐藏的 DOM 树附加到常规 DOM 树中的元素上——这个影子 DOM 始于一个影子根,在其之下你可以用与普通 DOM 相同的方式附加任何元素

这里有一些影子 DOM 术语:
- 影子宿主(Shadow host): 影子 DOM 附加到的常规 DOM 节点
- 影子树(Shadow tree): 影子 DOM 内部的 DOM 树
- 影子边界(Shadow boundary): 影子 DOM 终止,常规 DOM 开始的地方
- 影子根(Shadow root): 影子树的根节点
说了这么多,那如何创建创建影子 DOM ?
我们可以调用宿主上的 attachShadow() 来创建影子 DOM
我们结合 乾坤小demo 实际演示一下,影子DOM到底有什么作用?
ok!我们创建了一个 qiankun 项目,现在主应用和子应用根节点类名相同,都是 .App,主应用根节点背景色设置为黑色,子应用根节点背景色设置为红色

由于 qiankun 默认的动态样式隔离机制存在缺陷,无法确保主应用和子应用之间的样式隔离,我们发现,子应用污染了主应用的背景色样式
启用 Shadow DOM沙箱隔离机制,Later~,一切正常

实现原理
这里我们实现一下 Shadow DOM 沙箱机制的核心逻辑,对应乾坤的源代码在createElement方法,可以看这里 - Shadow DOM沙箱源代码
其原理也很简单,就是将子应用模板包裹在 Shadow DOM 中,使其形成一个独立的样式作用域,确保其样式隔离
<body>
<div id="root">qiankun 是一个基于 single-spa 的微前端实现库</div>
<script>
// 子应用的模版字符串
const template = `<div id="qiankun-xxx">
<div id="app">Shadow DOM 沙箱</div>
<style>div{color:red}</style>
</div>`
function createElement(appContent) {
const containerElement = document.createElement('div')
containerElement.innerHTML = appContent
const appElement = containerElement.firstChild // 影子宿主(template模版字符串转换成了真实的dom)
const shadow = appElement.attachShadow({ // 影子DOM(调用宿主上的 attachShadow() 来创建影子 DOM)
mode: 'open',
})
shadow.innerHTML = appElement.innerHTML // 给Shadow DOM附加宿主节点下的内容
appElement.innerHTML = ''
return appElement
}
document.body.appendChild(createElement(template))
</script>
</body>
虽然 Shadow DOM 是一个强大的技术 ,但在某些场景下,它并不是一个完美的解决方案
比如,越界的 DOM 操作,在实际应用中,子应用可能会有操作主文档 DOM 的需求,比如动态地向主文档document 添加全局组件、弹窗等。这些操作会创建 Shadow DOM 之外的元素,Shadow DOM 的内部样式也就无法对这些元素生效
基于 ShadowDOM 的严格样式隔离并不是一个可以无脑使用的方案,大部分情况下都需要接入应用做一些适配后才能正常在 ShadowDOM 中运行起来(比如 react 场景下需要解决这些 问题,使用者需要清楚开启了
strictStyleIsolation意味着什么 - 摘抄自 qiankun 文档
Scope CSS (Scoped CSS)
手动开启,开启代码如下
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([...]) // 注册子应用
start({
sandbox: { experimentalStyleIsolation: true } // 开启作用域沙箱
})
这是 qiankun 一个实验性的样式隔离特性,它的核心思想是通过给子应用中的所有样式选择器添加一个唯一的前缀选择 div[data-qiankun="xxx"],来限制这些样式的作用范围
对于一个选择器,如果需要限制它的作用范围,可以使用组合选择器的方式。在当前选择器A前面加一个选择器B,使得选择器A只作用在选择器B内部的节点
改写后的代码会表达为如下结构
// 假设 registerMicroApps 方法注册的子应用 name 是 react16
.app-main {
font-size: 14px;
}
// 改写后
div[data-qiankun="react16"] .app-main {
font-size: 14px;
}
实现原理
提取和解析样式:当一个子应用被加载时,qiankun 会提取子应用中的所有 <style> 标签内嵌样式和 <link> 标签引入的外部样式,并对其进行解析,获取所有的 CSS 规则
重写样式规则:qiankun 给每个子应用的包裹容器新增唯一标识符 data-qiankun 属性,值为通过 registerMicroApps API 注册子应用的 name;然后修改子应用的样式选择器,添加前缀选择器 div[data-qiankun="xxx"],重写选择器
由于作用域沙箱不能直接修改
link标签引入的外部样式,所以会把link外部样式转化为style内嵌样式,再给其添加前缀
对应乾坤源代码的入口是createElement方法,可以看这里 - Scope CSS沙箱源代码
function createElement(
appContent: string,
strictStyleIsolation: boolean,
scopedCSS: boolean,
appName: string,
): HTMLElement {
const containerElement = document.createElement('div');
containerElement.innerHTML = appContent;
const appElement = containerElement.firstChild as HTMLElement;
/**
* CSS样式冲突的处理方式
* 1. shadowDOM
* 2. scoped CSS
*/
if (strictStyleIsolation) {
// ... shadowDOM 沙箱逻辑
}
if (scopedCSS) {
// 常量 css.QiankunCSSRewriteAttr = 'data-qiankun'
const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
if (!attr) {
// 给子应用的包裹容器新增 data-qiankun 属性,值为通过 registerMicroApps 注册子应用的 name
appElement.setAttribute(css.QiankunCSSRewriteAttr, appName);
}
// 遍历子应用的所有样式,修改其样式选择器,添加前缀选择器 div[data-qiankun="xxx"]
const styleNodes = appElement.querySelectorAll('style') || [];
forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
css.process(appElement!, stylesheetElement, appName);
});
}
return appElement;
}
不足的话,应该是解析子应用的 style 样式,并为每个选择器添加前缀。这一过程在子应用的加载和渲染时会增加额外的计算开销,尤其是在样式表很大或者包含大量选择器的情况下,可能会影响页面的初始加载性能
沙箱方案
实际的工作中,选择合适的沙箱方案需要根据具体的场景和需求来决定。 以下是一些常见的场景及其对应的沙箱选择
单实例模式
单实例模式指的是一次仅加载一个子应用的场景,这种模式下子应用之间不会并发运行,避免了同时多个应用运行导致的冲突
在这种模式下,动态样式隔离+ 工程化手段(如 BEM 命名规范、CSS Modules)通常就能满足大部分需求。因为在单实例模式中,不需要担心子应用之间的样式和脚本冲突问题。
多实例模式
在多实例模式下,多个子应用可能同时加载和运行,子应用之间的样式和脚本容易产生冲突
在这种模式下,需要更强的隔离性。可以使用 作用域沙箱(Scoped CSS Sandbox)+ Shadow DOM 沙箱 的组合
参考文档
GitHub - careyke/frontend_knowledge_structure: qiankun中CSS沙箱的实现
qiankun 的 CSS 沙箱隔离机制的更多相关文章
- JVM探究(一)谈谈双亲委派机制和沙箱安全机制
JVM探究 请你谈谈你对JVM的理解?java8虚拟机和之前的变化gengxin? 什么是OOM,什么是栈溢出StackOverFlowError JVM的常用调优参数有哪些? 内存快转如何抓取,怎么 ...
- J V M(三)沙箱安全机制
沙箱安全机制 Java安全模型的核心就是Java沙箱(sandbox)什么是沙箱? 沙箱是一个限制程序运行的环境.沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本 ...
- CSS中定位机制的想法
对于一个刚刚接触css的新手而言,CSS的定位机制可能是最让人头疼的一件事情了, 接下来我们了解一下CSS的定位机制. position:static | relative | absolute | ...
- Hibernate逍遥游记-第15章处理并发问题-001事务并发问题及隔离机制介绍
1. 2.第一类丢失更新 3.脏读 4.虚读.幻读 5.不可重复读 6.第二类丢失更新 7.数据库的锁机制 8.数据库事务的隔离机制
- css的定位机制
牛腩新闻发不系统中遇到了CSS(Cascading style sheets),第一次接触,比较陌生还!因为CSS很多关于元素定位的问题,并且很多情况下元素的位置以像素精度计.一个不小心就很头疼,为此 ...
- Yarn的资源隔离机制
源调度和资源隔离是YARN作为一个资源管理系统,最重要和最基础的两个功能.资源调度由ResourceManager完成,而资源隔离由各个NodeManager实现,在文章“Hadoop YARN中内存 ...
- 理解MySql事务隔离机制、锁以及各种锁协议
一直以来对数据库的事务隔离机制的理解总是停留在表面,其内容也是看一遍忘一边.这两天决定从原理上理解它,整理成自己的知识.查阅资料的过程中发现好多零碎的概念假设串起来足够写一本书,所以在这里给自己梳理一 ...
- zuul隔离机制
文章转载自:https://blog.csdn.net/farsight1/article/details/80078099 ZuulException REJECTED_SEMAPHORE_EXEC ...
- Java沙箱安全机制介绍【转载】
沙箱安全机制的应用层面:360沙箱.win10沙箱.包括VMware Workstation.Oracle VM VirtualBox都可以充当沙箱去使用,沙箱中的操作与本机无关,进而保证本机的安全性 ...
- [MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]
[MySQL数据库之事务.读现象.数据库锁机制.多版本控制MVCC.事务隔离机制] 事务 1.什么是事务: 事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻 ...
随机推荐
- git 乱操作
https://www.cnblogs.com/qybk/p/10880901.html 错误提示一样,只是我是在我自己的分支(xxx_dev)里.所以以下要改一下. git pull origin ...
- CSIG青年科学家会议圆满举行,合合信息打造智能文档处理融合研究新范式
近期,第十九届中国图象图形学学会青年科学家会议(简称"会议")在广州召开.会议由中国图象图形学学会(CSIG)主办,琶洲实验室.华南理工大学.中山大学.中国图象图形学学 ...
- EF Core – 8.0 new features
参考 Docs – What's New in EF Core 8 Support DateOnly and TimeOnly SQL Server 早在 2008 年就已经支持 date 和 tim ...
- CSS & JS Effect – FAQ Accordion & Slide Down
效果 参考: Youtube – Responsive FAQ accordion dropdown | HTML and CSS Tutorial 几个难点 1. 如何 align left for ...
- Figma 学习笔记 – Team Library Style and Component
Design System 我们做设计通常会 Follow 一个 Design System, 比如 Material Guide. 里头会定义 Font, Color, Effect (Elevat ...
- MyBatis——案例——环境准备
配置文件完成增删改查 准备环境 数据库表 tb_brand -- 创建tb_brand表 create table tb_brand( id int primary k ...
- Driud——数据库连接池的使用
Druid数据库连接池的使用 1. 导入 jar 包 jar包下载:Central Repository: com/alibaba/druid/1.1.12 (maven.org) 导入项目中:(复制 ...
- 全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字符串
全网最适合入门的面向对象编程教程:55 Python 字符串与序列化-字节序列类型和可变字节字符串 摘要: 在 Python 中,字符编码是将字符映射为字节的过程,而字节序列(bytes)则是存储这些 ...
- 进程D 状态的产生及原因解释
在 Linux 系统中,进程的 D 状态表示进程处于不可中断的睡眠状态 (Uninterruptible Sleep).这种状态通常由进程等待某些资源或事件引起,这些资源或事件无法立即可用.以下是一些 ...
- webapi action 参数
使用地址参数传递(queryString)数据:eg:http://localhost:5063/WeatherForecast?age=123 /// <summary> /// GET ...