qiankun【隔离沙箱】


前言

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沙箱

  1. 使用proxywindow对象进行代理
  2. 不支持多实例,在同一个页面,同时激活多个子应用可能会有异常。应为无法监听对window的更改,是哪一个子应用进行的,所以异常会难以排查

ProxySanbox沙箱

  1. 使用proxywindow对象进行代理
  2. 支持多实例,在同一个页面,可以同时激活多个子应用。每个子应用使用独立的fakewindow

所以既然已经支持Proxy为何不直接使用ProxySanbox沙箱


SnapshotSandbox低版本沙箱

把主应用的 window 对象做浅拷贝,将 window 的键值对存成一个 Hash Map。之后无论微应用对 window 做任何改动,当要在恢复环境时,把这个 Hash Map 又应用到 window 上就可以了。
主要流程如下:

在子应用mount时:
  1. 浅拷贝window上的所有属性(浅复制主应用的 window key-value 快照),用于下次恢复主应用环境,在子应用unmount时,还原主应用的环境
  2. 检查是否存在上一次子应用unmount时记录的变更属性,没有则跳过,有则通过记录被更改的属性,对子应用的环境进行还原
在子应用unmount时:
  1. 将当前子应用的window key-value属性主应用快照进行diff,存储不同的属性,用于下次子应用mount时,还原子应用的window环境
  2. 通过子应用mount时挂载的window key-value快照还原主应用的环境
缺陷:
  • 每一次子应用unmount时,都要进行和上一次的window 快照进行diff,window属性庞大,比较消耗性能。

在这里插入图片描述


LegacySandbox单例沙箱

LegacySandbox 沙箱SnapshotSanbox沙箱实现思路差不多,都是通过记录新增或改变的属性,从而实现对环境的还原和隔离。但是LegacySandboxSnapshotSanbox的对比方式略有不同,LegacySandbox使用es6的Proxy创建一个window的proxy对象,所有调用window.xxx的操作,都代理到proxy上,并且在这上面进行一系列记录属性变更的操作,这也是LegacySandboxSnapshotSanbox性能高的原因

首先说一下LegacySandbox中的三个关键变量

  • addedPropsMapInSandbox:存储在子应用运行时期间新增的全局变量,用于卸载子应用时还原主应用全局变量
  • currentUpdatedPropsValueMap:存储在子应用运行期间更新的全局变量,用于卸载子应用时还原主应用全局变量
  • modifiedPropsOriginalValueMapInSandbox:存储子应用全局变量的更新,用于运行时切换后还原子应用的状态
在子应用mount时
  1. 通过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后)
  1. 子应用更改window属性(window.a=xxx)时,实际访问的是proxy对象,并触发set方法。
  2. 在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时
  1. 通过modifiedPropsOriginalValueMapInSandbox,还原window所有被子应用修改过的key-value
  2. 通过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;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯一样的MEI男子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值