前端微应用-乾坤(qiankun)原理分析-沙箱隔离(css)
为什么需要沙箱隔离?沙箱sandbox又是什么概念呢?
沙箱(sandbox)是前端开发中的一个概念,它指的是在某个特定的环境中,对代码进行隔离,防止代码相互影响。在 Web 开发中,沙箱通常指的是在某个特定的 DOM 节点中,对代码进行隔离,防止代码相互影响。
比如网络安全中的沙箱是指,一些资源文件在沙箱环境中进行分析,避免是恶意脚本被执行了影响整个系统瘫痪。
Q: 为什么乾坤需要沙箱隔离呢?我之前开发多页应用的时候也没用到该技术啊?只是配置了下nginx转发到对应的HTML就行了啊?
A: 因为乾坤是容器(主应用),以及子应用的模式,载体主应用是一个完整的HTML,子应用仅作为HTML
中的一部分。如下图所示:
如果这种模式下,子应用里面有样式对body、html、:root
这种全局样式在会照成影响的,以及如果子应用中重写了window
的某个方法,那么岂不是对全局都污染了。
如果你说你们比较规范不会有以上这些问题,但是你不能保证你引入的第三方插件库有没有这种操作,所以才需要沙箱隔离。
Q: 沙箱隔离是运行时,还是编译时,会不会影响我的性能?
A: 沙箱隔离是运行时的,多少会影响点性能,如果你是后台管理那么可以忽略不计,如果你是做3d
或者2d
图谱流程图等里面需要频繁用到检测节点使用Math
计算的话(因为节点碰撞可能是要千万级的使用window
上的Math
方法),那么可能需要你关闭沙箱隔离
Q: 那我应该什么场景下关闭沙箱,需要我自己测试下性能吗?
A: 如果你频繁需要用到window
上的API
,比如1s
调用10万
次,那么你必须要去关闭沙箱。经测试1s
内调用1万
次基本无感的。正常是不会影响你开发的。
Q: 我怎么关闭沙箱?或者我能用什么方式解决这个问题呢?
A: start({sandbox: false })
就可以关闭了。比如你知道频繁调用了Math
方法,那么你把Math
方法cloneDeep
一份再去使用就不会有这个问题了。
Q: 我能只关闭单独某个微应用的沙箱吗?
A: 暂时看应该是不行的!
在乾坤中针对
css
,js的window
分别做了沙箱隔离,咱们接下来会一个个的拆分,看看它是怎么做到的。
css 沙箱隔离
乾坤(qiankun) 默认是没有对
css
沙箱隔离的。但是它提供了一个API
可以帮咱们开启css
隔离,其实就是把全局的body、html、:root
进行了替换,如果你自己设置一个scope
避免这些全局样式同样相当于隔离了。start({ sandbox: {experimentalStyleIsolation: true} });
在上一篇中咱们可以看到子应用的html
的link
标签外部引入的css
被插件import-html-entry
替换成了style
标签。
比如是<link rel="stylesheet" href="./test.css" />
/* ./test.css */
a {
color: red;
}
会被转成<style>a{color: red;}</style>
转换后的HTML
是怎么操作css的呢?通过以下代码
function createElement(
appContent: string, // 1. 替换后的HTML
scopedCSS: boolean,
appName: string,
): HTMLElement {
const containerElement = document.createElement('div');
containerElement.innerHTML = appContent;
// appContent always wrapped with a singular div
const appElement = containerElement.firstChild as HTMLElement;
if (scopedCSS) {
const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
if (!attr) {
appElement.setAttribute(css.QiankunCSSRewriteAttr, appName); // 2. 这里对父元素增加了隔离属性data-qiankun="app-vue-history" appName
}
const styleNodes = appElement.querySelectorAll('style') || []; // 3. 获取所有style标签
forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
css.process(appElement!, stylesheetElement, appName); // 4. 这里是替换的
});
}
return appElement;
}
具体执行流程如下图所示:
乾坤(qiankun)
的process
替换部分是怎么做的呢?如下代码:
// eslint-disable-next-line class-methods-use-this
const ruleStyle = (rule: CSSStyleRule, prefix: string) => {
const rootSelectorRE = /((?:[^\w\-.#]|^)(body|html|:root))/gm;
const rootCombinationRE = /(html[^\w{[]+)/gm;
const selector = rule.selectorText.trim();
let { cssText } = rule;
// handle html { ... }
// handle body { ... }
// handle :root { ... }
if (selector === 'html' || selector === 'body' || selector === ':root') {
return cssText.replace(rootSelectorRE, prefix);
}
// handle html body { ... }
// handle html > body { ... }
if (rootCombinationRE.test(rule.selectorText)) {
const siblingSelectorRE = /(html[^\w{]+)(\+|~)/gm;
// since html + body is a non-standard rule for html
// transformer will ignore it
if (!siblingSelectorRE.test(rule.selectorText)) {
cssText = cssText.replace(rootCombinationRE, '');
}
}
// handle grouping selector, a,span,p,div { ... }
cssText = cssText.replace(/^[\s\S]+{/, (selectors) =>
selectors.replace(/(^|,\n?)([^,]+)/g, (item, p, s) => {
// handle div,body,span { ... }
if (rootSelectorRE.test(item)) {
return item.replace(rootSelectorRE, (m) => {
// do not discard valid previous character, such as body,html or *:not(:root)
const whitePrevChars = [',', '('];
if (m && whitePrevChars.includes(m[0])) {
return `${m[0]}${prefix}`;
}
// replace root selector with prefix
return prefix;
});
}
return `${p}${prefix} ${s.replace(/^ */, '')}`;
}),
);
return cssText;
}
-
- 处理
html { ... } 、 body { ... } 、 :root { ... }
- 处理
-
- 处理
html body { ... } 、html > body { ... }
- 处理
-
- 处理
body下 div,body,span { ... }
- 处理
然后拼接成需要用的css
,在通过style.textContent
设置style
中的样式`。
css 沙箱总结
Q: 乾坤(qiankun)
css 沙箱有没有必要?,看起来好像也没啥特殊的不就加个前缀,并且它默认也没有开启啊?
A: 从本次源码中分析,如果咱们开启start({ sandbox: {experimentalStyleIsolation: true} });
后才会增加前缀,并且咱们了解到了它的作用替换规则,其实就是对html、body、:root
进行了替换。并且css经过子应用的卸载并不会留下痕迹(影响别的应用)。不开启其实也行的
Q: 乾坤(qiankun)
css 沙箱那岂不是个笑话?
A: 如果你的子应用没有全局样式,确实用不到这个API
,但是也算是给我们敲了个警钟,避免使用这些全局样式。
设置
start({ sandbox: {strictStyleIsolation: true} });
的话会启用shadowDOM
,无界
的核心就是这个,等咱们分析无界的时候再细说。