为什么说现在的微前端沙箱不是真正意义的沙箱
前言
在上一篇文章为什么single-spa才是微前端的正确开发模式中我们讲过 qiankun 沙箱的诞生背景,这里我们再回顾一下:
- 项目技术栈杂乱无章,代码质量较低
- 项目开发时缺少规范,导致项目一融合就会相互影响(css冲突、js冲突等)
- 项目改造人力成本大,新功能需要持续迭代
- ...
面对这些问题,当前采用的最好的方案是开发出来一套能够大大减少接入成本同时又能够避免项目之间的相互影响的微前端框架,这也就是为什么国内微前端框架会花费大量的资源去实现沙箱的原因。
但是各种微前端方案对沙箱并没有一个标准概念,同时也没有对沙箱解决了什么问题进行详尽的讲解,大量开发者人云亦云。因此本篇文章会从 ShadowRealm 的角度去分析当前微前端沙箱中存在的问题以及未来的发展趋势。
什么是沙箱?
国内微前端反复提及沙箱概念, 但是其定义并不准确,不能代表计算机领域通用的沙箱概念, 计算机领域的沙箱概念如下:
沙箱(Sandbox)是一种程序的隔离运行机制,其目的是限制不可信进程的权限。沙箱技术经常被用于执行未经测试的或不可信的客户程序。为了避免不可信程序可能破坏其它程序的运行,沙箱技术通过为不可信客户程序提供虚拟化的磁盘、内存以及网络资源。由于沙箱里的资源被虚拟化,所以沙箱里的不可信程序的恶意行为往往会被限制在沙箱中。
可以看出计算机领域的沙箱是关注计算机安全领域的一项技术。
国内微前端沙箱到底是什么?
首先国内的微前端方案中提及的沙箱对比上述的标准沙箱概念,并不严谨,属于自创概念,其次浏览器中并没有沙箱的标准实现,相关特性 ShadowRealm 尚在 TC39 标准提案中(stage 3阶段), 暂无实现。
目前各种国内微前端的沙箱均为自创的、非标准官方的通过模拟手段实现的沙箱还是会存在很多局限性,实现方式多为:
- iframe + webcomponent 实现沙箱 —— wujie。
- window 代理 + shadowdom/scoped css 实现沙箱 —— qiankun。
微前端中的沙箱主要分为两部分,一个是 js 沙箱,另一个就是 css 沙箱。
js 沙箱
在进行项目融合时,通常会遇到下面几个场景:
- 项目中定义了全局变量,多个项目并存时,如果重名必将相互影响。
- UMD模块将依赖挂载至全局(如window), 多个项目并存时相互影响。
由此可见,全局污染的主要来源主要来源于定义和依赖,因此概括下来 js 沙箱的用途是屏蔽多个项目融合时的相互的污染,依赖冲突。相较于国外微前端多依靠治理加上运行时模块化(systemjs)或 MF 解决此类问题,国内的微前端多依靠 js 沙箱,其本质是放弃治理, 转而隔离。
模拟的JS沙箱的bad case
目前“模拟的 js 沙箱是“无瑕疵”的 JS 沙箱吗? 答案是否定的,上文提到的 ShadowRealm 是未来“官方”的 JS 沙箱, 更加严谨。
非iframe沙箱常见问题
- 原型链修改
Array.prototype.cf = 'xxx';
- DOM 0级事件
// A系统
window.onclick = () => {
// xxx
}
// B系统
window.onclick = () => {
// xxx
}
这里只列举了几个,现实场景中还会有很多这样的例子。
iframe沙箱常见问题
identity discontinuity(身份丢失问题)在基于 iframe 的沙箱中是一个凸现的问题。所有非简单类型(Non-primitive)在跨 iframe 传导中均存在此问题。
onst globalOne = window;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
console.assert(iframeArray !== Array);
const list = iframeArray('a', 'b', 'c');
list instanceof Array; // false
[] instanceof iframeArray; // false
Array.isArray(list); // true
identity discontinuity(身份丢失问题)是TC39对此问题的官方命名, 上述例子也来源于TC39,身份丢失问题最大影响是阻碍应用间通信。非简单类型在通信时会出现错误判断、无法判断的问题。
正确的处理方式-避免污染
从上述分析不难看出, 现代前端的全局污染问题的根源是不良的开发习惯。 面对此类问题的根本解决办法并非施加额外技术手段,而是规范开发模式,避免出现问题。 项目开发时
- 正确使用UMD
- 减少或避免定义全局变量
- 避免编写不在函数作用域内的js代码
- 避免污染原型链
- 减少使用DOM0级事件
- …
包开发时
- 减少副作用
- 模块支持多实例
- 避免污染全局
小结
现有的模拟实现的js沙箱方案或多或少存在缺陷, 且并非传统计算机领域中的沙箱概念。 js沙箱仍在系统融合中有意义与价值,但无法根治全部的融合开发时遇到的污染问题,仅能“缓解”。
-
整体系统仍然只有一个 document ,多个小于17版本的 React 共存于隔离的沙箱中仍然会有问题。
-
对于 vue-router、react-router 这些路由库,他们本身版本和版本之间以及不同路由库之间会存在冲突问题。
注意:对于 iframe 隔离的情况虽然是不同的上下文,但是个人认为多多少少还是会存在一些冲突问题,这一点后面需要待实践。
css 沙箱
在进行项目融合时,样式问题也是一个比较头痛的事情,当我们项目融合完了后突然发现我的样式怎么不起作用了,亦或是我的样式怎么被另一个样式所覆盖了,原因可能有很多:
- 不规范的命名导致重复
- 为了简单,直接在项目中使用全局样式
- 多实例下的第三方 Ui 库的引入(antd,element-ui)
- ...
由此可见,样式污染的主要来源主要来源于定义和依赖,因此概括下来 css 沙箱的用途是屏蔽多个项目融合时样式的相互污染,和 js 沙箱一样,国内的微前端多依靠 css 沙箱,其本质是放弃治理, 转而隔离。
下面是当前微前端实现 css 沙箱的几种方式:
shadowdom(wujie、qiankun、Garfish)
Shadow dom 用简单概括为:将 Dom 文档树中的某个节点变为隔离节点,隔离节点内的子节点样式、行为将与外界隔离(隔离节点内的样式不会受到外部影响,也不会影响外部节点,在隔离节点内的事件最终都只会冒泡到隔离节点中)
优点
- 浏览器基本的样式隔离
- 支持主子应用样式隔离
- 支持多实例
缺点
-
导致某些依赖事件冒泡的库无法正常运行。
React16 的合成事件依赖于事件的冒泡,将所有的事件收集到 document 中,但是在 shadowdom 就会失效,所以在 React17 中改变了事件系统处理机制。
字节的微前端框架 Garfish 通过手动将事件向上传播,从而避免 React 依赖事件委托的库失效
-
无法处理像 dialog、modal 等挂载在父应用 body 上的组件样式失效问题。
为了避免被父元素的样式影响,像 modal、dialog、message 这些组件都会被挂载到父容器的 body 下,继而就脱离了原来的 css 沙箱,最终就导致样式失效。
腾讯微前端框架 wujie 通过 document 的代理将 modal、dialog、message 等组件挂载到沙箱中,从而解决了样式问题。
-
其他问题可参考该 issue。
scoped css(qiankun)
scoped css 是 vue loader 实现的组件级样式隔离方案,用起来只要给组件的 style 加一个 scoped 属性:
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
转换结果:
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
这样就限制了组件样式的作用范围,而 qiankun 就很好的借鉴了 scoped css 的思路推出了一个试验性的样式隔离特性 experimentalStyleIsolation
,当启用时 qiankun 会改写子应用所添加的样式,为所有样式规则增加一个特殊的选择器规则来限定其影响范围:
// 假设应用名是 react16
.app-main {
font-size: 14px;
}
转换结果:
div[data-qiankun-react16] .app-main {
font-size: 14px;
}
我们看到 qiankun 对所有样式加了一层 data-qiankun=“应用名” 的选择器来隔离,qiankun 官方支持后面将会重点投资这个方案而不再推荐 shadowdom。
优点
- 不需要用户手动增加配置。
缺点
- 不支持 @keyframes, @font-face, @import, @page。
- 子应用的节点会受到主应用的影响。
- 无法处理像 dialog、modal 等挂载在父应用 body 上的组件样式失效问题。
- ...
正确的处理方式-避免污染
从上述分析不难看出, 现代前端的样式污染问题的根源和 js 全局污染一样,都是由于不良的开发习惯。在上篇文章中我提到过 single-spa 他们是怎么看待样式污染问题的——先治理再隔离。 业务开发
- 优先使用 css module、stylesd-component、Emotion 等库去避免样式污染。
第三方库
- 使用 PostCSS Prefix 添加前缀(antd)
模拟的沙箱没有用吗?
不是的,肯定是有用的,比如相面几个场景:
- 系统老,存在变量污染, 无法短期内改善, 但需要融合
- 假设某个甲方客户的系统技术差、老旧、存在诸多全局污染, 这时我们需要有个功能通过SDK形式嵌入该系统。 此时因为我们无法要求客户对系统进行改造。 所以使用js沙箱进行隔离, 保证SDK有一个独立且纯净的运行环境, 是非常有帮助的
总结
随着互联网发展, 前端系统越来越复杂, 微前端问题日渐凸现。 “官方”(w3c, tc39, 浏览器厂商)随未明确定义和解决“微前端”问题, 但实际上也有所行动,提出了一些相关方案
- web Component — 技术栈无关的组件
- shadow DOM — 样式隔离
- 浏览器es module — 依赖运行时管控
- shadowRealm — js沙箱
现阶段我们能做的就是要把控好项目,杜绝污染,尽可能做到技术栈统一,团队协商好依赖版本。
转载自:https://juejin.cn/post/7189433139444318264