qiankun【隔离沙箱】
qiankun隔离沙箱有两种,一种是js变量隔离沙箱,LegacySandbox 单例沙箱ProxySandbox 多例沙箱SnapshotSandbox 低版本沙箱一种是样式隔离沙箱样式隔离沙箱通过配置。
文章目录
前言
qiankun隔离沙箱有两种,
- 一种是js变量隔离沙箱,
LegacySandbox
单例沙箱、ProxySandbox
多例沙箱、SnapshotSandbox
低版本沙箱 , - 一种是样式隔离沙箱,样式隔离沙箱通过配置
start(options)
中的参数决定
一、js环境隔离沙箱
假如说我们在子应用A上的window
定义了一个变量window.a =3
, 在子应用B上又定义了一个变量window.b = 4
, 当我们A ,B应用切换时那么window.a
的值怎么处理。正确的肯定是在A应用上window.a=3
,切到b 应用上window.b = 4
在学习其原理之前,不妨先思考几个问题:
在主应用修改window属性后,在子应用再次修改同一个属性。子应用和主应用中访问到window属性的值是一致的吗
主应用不修改window属性,又子应用修改window属性,此时主应用访问到的window属性与子应用访问到,被修改的window属性值是一致的吗
沙箱启用规则
官方上说,qiankun
默认使用的是legacySandbox沙箱
,可以通过下面的配置进行更改,使用LegacySandbox沙箱
或ProxySandbox沙箱
。至于SnapshotSandbox沙箱
,当发现浏览器不支持Proxy
时,会自动优雅降级使用 ,使用者不能进行控制。
start({
singular:true, //是否为单实例场景、设置为单实例场景则使用单实例沙箱
})
loadMicroApp(app,{
singular:true, // 效果与start相同
})
但实际上,qiankun默认使用的是ProxySandbox
沙箱、并且上面的配置,也不能配置qiankun使用何种沙箱模式。你可以通过对源码的debugger进行验证,这可能是官网上的一个误导。但是从性能上来看这是一个正确的选择
LegacySandbox沙箱
- 使用
proxy
对window对象
进行代理- 不支持多实例,在同一个页面,同时激活多个子应用可能会有异常。应为无法监听对
window
的更改,是哪一个子应用进行的,所以异常会难以排查
ProxySanbox沙箱
- 使用
proxy
对window对象
进行代理- 支持多实例,在同一个页面,可以同时激活多个子应用。每个子应用使用独立的
fakewindow
所以既然已经支持Proxy为何不直接使用ProxySanbox沙箱
呢
SnapshotSandbox低版本沙箱
把主应用的 window 对象做浅拷贝,将 window 的键值对存成一个 Hash Map。之后无论微应用对 window 做任何改动,当要在恢复环境时,把这个 Hash Map 又应用到 window 上就可以了。
主要流程如下:
在子应用mount时:
- 浅拷贝
window
上的所有属性(浅复制主应用的 window key-value 快照)
,用于下次恢复主应用环境
,在子应用unmount
时,还原主应用
的环境 - 检查是否存在上一次子应用
unmount
时记录的变更属性,没有则跳过,有则通过记录被更改的属性
,对子应用的环境
进行还原
在子应用unmount时:
- 将当前子应用的
window key-value属性
与主应用快照
进行diff
,存储不同的属性,用于下次子应用mount
时,还原子应用的window环境
- 通过子应用
mount
时挂载的window key-value快照
还原主应用的环境
缺陷:
- 每一次子应用unmount时,都要进行和
上一次的window 快照
进行diff,window属性庞大,比较消耗性能。
LegacySandbox单例沙箱
LegacySandbox 沙箱
与 SnapshotSanbox沙箱
实现思路差不多,都是通过记录新增或改变的属性,从而实现对环境的还原和隔离。但是LegacySandbox
与SnapshotSanbox
的对比方式略有不同,LegacySandbox
使用es6的Proxy
创建一个window的proxy
对象,所有调用window.xxx
的操作,都代理到proxy上,并且在这上面进行一系列记录属性变更的操作,这也是LegacySandbox
比SnapshotSanbox
性能高的原因
首先说一下LegacySandbox
中的三个关键变量
- addedPropsMapInSandbox:存储在子应用运行时期间
新增的全局变量
,用于卸载子应用时还原主应用
全局变量 - currentUpdatedPropsValueMap:存储在子应用运行期间
更新的全局变量
,用于卸载子应用时还原主应用
全局变量 - modifiedPropsOriginalValueMapInSandbox:存储子应用
全局变量的更新
,用于运行时切换后还原子应用
的状态
在子应用mount时
- 通过
currentUpdatedPropsValueMap
对象,对子应用环境进行还原。
{
key: "active",
value: function active() {
var _this2 = this;
if (!this.sandboxRunning) {
this.currentUpdatedPropsValueMap.forEach(function (v, p) {
return _this2.setWindowProp(p, v);
});
}
this.sandboxRunning = true;
}
}
沙箱激活期间(子应用mount后)
- 子应用更改
window
属性(window.a=xxx)
时,实际访问的是proxy对象
,并触发set方法。 - 在set方法中存在逻辑,判断a是否为window上的属性。
- 如果不是:
将key-newValue
记录在addedPropsMapInSandbox
对象中,表明为新增的属性。
然后在currentUpdatedPropsValueMap
对象中记录key-newValue
。
最后设置window[key]=newValue
,保证子应用下次get window.a
的时候能读取到最新的值。 - 如果是:
并且modifiedPropsOriginalValueMapInSandbox
对象没有记录,则在modifiedPropsOriginalValueMapInSandbox
记录window的初始key-value
。
然后在currentUpdatedPropsValueMap
对象中记录key-newValue
。
最后设置window[key]=newValue
,保证子应用下次get window.a
的时候能读取到最新的值。
/** 沙箱期间新增的全局变量 */
this.addedPropsMapInSandbox = new Map();
/** 沙箱期间更新的全局变量 */
this.modifiedPropsOriginalValueMapInSandbox = new Map();
/** 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot */
this.currentUpdatedPropsValueMap = new Map();
... 其他代码
if (!rawWindow.hasOwnProperty(p)) {
addedPropsMapInSandbox.set(p, value);
} else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
// 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值
modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
}
currentUpdatedPropsValueMap.set(p, value);
if (sync2Window) {
// 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据
rawWindow[p] = value;
}
在子应用unmount时
- 通过
modifiedPropsOriginalValueMapInSandbox
,还原window所有被子应用修改过的key-value
- 通过
addedPropsMapInSandbox
将在子应用中增加的window的key-value
设置成undefined
{
key: "inactive",
value: function inactive() {
var _this3 = this;
// renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot);
// restore global props to initial snapshot
this.modifiedPropsOriginalValueMapInSandbox.forEach(function (v, p) {
return _this3.setWindowProp(p, v);
});
this.addedPropsMapInSandbox.forEach(function (_, p) {
return _this3.setWindowProp(p, undefined, true);
});
this.sandboxRunning = false;
}
}
ProxySandbox多例子沙箱
此沙箱模式,会把当前 window 的一些原生属性(如document, location等)拷贝出来,单独放在一个对象上,这个对象也称为
fakeWindow,它会为每个子应用分配一个fakeWidnow
实现原理大致如下
- 将主应用中的
window
原生属性进行浅拷贝,放在一个对象(fakeWindow)
上- 为每一个子应用分配一个
fakeWindow
,作为子应用的window
子应用访问和改写window对象属性时,都会被拦截
- 当子应用中新增\改写
window
上的属性时:如果改变的是原生属性(location、document等)
,则修改window
,如果不是,则修改fakewindow
- 当子应用读取
window
上的属性时:如果读取的时原生属性(location,document等)
,则读取window
,如果不是,则访问fakewindow
上的属性
二、style隔离沙箱
style沙箱有两种,一种是使用shadowBox,通过Shadow DOM完成样式隔离,一种是严格模式,通过为子应用中所有的样式增加前缀实现样式隔离。
开启隔离
start({
sandbox:{
strictStyleIsolation:true,// 默认true 使用shadow box 样式隔离
experimentalStyleIsolation:true,// 使用严格模式
}
})
shadowBox
开启shadowBox之后,qiankun会在父容器dom下加入一个shadow Dom,并将子应用html的渲染结果放置在shadow Dom内,由于shadow dom的特性,shadow dom 内部和外部的样式互相隔离。天然形成了隔离沙箱。
qiankun官方建议使用experimentalStyleIsolation:true严格模式,因为shadow DOM可能会导致公用样式(如element-ui)无法进行
严格模式
// 假设应用名是 react16
.app-main {
font-size: 14px;
}
// 开启后 会自动为子应用的样式选择器增加【data-qiankun-子应用名】的前缀
div[data-qiankun-react16] .app-main {
font-size: 14px;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)