JS 沙箱
JS 沙箱
隔离问题最开始我们就是要做 JS 的隔离。在这个问题上,有的时候你不隔离其实也可以运行的,只不过不隔离,直接裸用 Single-SPA 的话,一旦应用之间的发生了冲突,你的应用很可能就跑不下去了,所以 JS 沙箱大部分情况是必要的。再 qiankun 沙箱的实现里,我们会有两个环境:一个代表的是外部的部分,就是我们的全局环境 Global Env,指的是你框架应用所运行的全局环境。而子应用加载的时候,实际上是应该跑在一个内部的沙箱环境里面,就是这张图上所表示的 Render Env。
沙箱实现思路有两条:其中最经典的实践思路其实是快照沙箱。快照沙箱就是在应用沙箱挂载和卸载的时候记录快照,在应用切换的时候依据快照恢复环境,当我子应用加载、启动了之后,此时的环境其实就是 Render Environment,是内部沙箱环境里,这个时候我记录一下当时的快照状态。而在我最后子应用 unmount 的时候,我把当前的环境和记录的快照进行一个对比,把它恢复回原来的全局状态。这就是说当我应用挂载了又卸载了,这个过程走了一遍之后,我当前整个 Windows 运行环境恢复成原来的样子,应用内部所做的修改,在它卸载的时候就会被恢复,这是快照沙箱思路。
举个例子,假如我们在 A 应用运行时,追加了一个全局变量,我们说 window.a = 123 ,这个时候 window.a 就变成了 123 ,但是在应用 A 卸载之后,快照还原, window.a 会被重新删除,你在全局环境中并不会继续看到 a 变量:
快照怎么打?其实也有两种思路:
- 一种是直接用 windows diff。把当前的环境和原来的环境做一个比较,我们跑两个循环,把两个环境作一次比较,然后去全量地恢复回原来的环境。
- 另一种思路其实是借助 ES6 的 proxy 就是代理。通过劫持 window ,我们可以劫持到子应用对全局环境的一些修改。当子应用往 window 上挂东西、修改东西和删除东西的时候,我们可以把这个操作记录下来。当我们恢复回外面的全局环境的时候,我们只需要反向执行之前的操作即可。比如我们在沙箱内部设了一个新的变量 window.a = 123 。那在离开的时候,我们只需要把 a 变量删了即可。
快照沙箱这个思路也正是 qiankun1.0 所使用的思路,它相对完善,但是缺点在于无法支持多个实例,也就是说我两个沙箱不可能同时激活,我不能同时挂载两个应用,不然的话这两个沙箱之间会打架,导致错误。所以说我们把目光又投向了另一条思路,我们让子应用里面的环境和外面的环境完全隔离。就如图所示,我们的 A 应用就活在 A 应用的沙箱里面,B 应用就活在 B 应用的沙箱里面,两者之间要不发生干扰,这个沙箱的实现思路其实也是通过 ES6 的 proxy,也通过代理特性实现的。