一.前言
目前微前端在我们团队中的应用已经日渐成熟,其中应用最为广泛的解决方案是qiankun。我们已知qiankun相较于iframe具有一些优势:
- 更好的路由前进后退
- 更自然的弹窗遮罩层覆盖可视范围
- 更方便的页面间消息传递
同时,qiankun的可用也是因为解决了三个关键的问题:
- 子应用的加载
- 应用间的环境隔离
- 路由劫持
纵使qiankun有众多好处,但是相对于iframe天生的主与子、子与子之间js、css完全隔离,qiankun的环境隔离还是没那么让我们省心的🤧,有很多我们需要注意的细节,就比如我上一篇文章中遇到的问题:子应用使用jsonp时候会出现jsonp回调函数未定义的情况。如下图:
上篇文章中定位到了原因:是fetchJsonp回调函数的定义和调用环境不一致导致调用时候报错函数未定义。即回调函数定义在子应用环境,而调用位置在主应用环境。这里的两个环境就是qiankun的沙箱机制生成的。
已知问题出现的原因,本篇文章就来研究一下qiankun中的运行环境隔离(沙箱机制)是怎么做的。
二.qiankun中JS沙箱源码分析
一.沙箱生成的代码流程
源码中我们直接跳到子应用创建时。当子应用创建时,会调用函数loadApp,调用位置如下图:
loadApp中有一段代码是像下面这样写的:目的是创建沙箱环境:
// 如果要开启沙箱模式
if (sandbox) {
//使用createSandboxContainer创建一个沙箱环境
sandboxContainer = createSandboxContainer(
appName,
initialAppWrapperGetter,
scopedCSS,
useLooseSandbox,
excludeAssetFilter,
);
// 用沙箱的代理对象作为接下来操作的运行环境
global = sandboxContainer.instance.proxy as typeof window;
mountSandbox = sandboxContainer.mount;
unmountSandbox = sandboxContainer.unmount;
}
所以我们来看看上面代码中的createSandboxContainer中是怎么创建沙箱的。
函数最开始是这样的(关键代码已经标注说明):
let sandbox: SandBox;
// 如果浏览器支持Proxy
if (window.Proxy) {
// 根据是否单实例模式判断使用哪种沙箱
sandbox = useLooseSandbox ? new LegacySandbox(appName) : new ProxySandbox(appName);
} else {
// 如果不支持Proxy,则使用快照沙箱
sandbox = new SnapshotSandbox(appName);
}
二.沙箱是如何创建的
从上图可以看出qiankun的沙箱隔离主要分为三种:legacySandBox,proxySandBox,snapshotSandBox。其中前两种是基于Proxy实现。qiankun做沙箱隔离的核心就是js的标准内置对象Proxy。如果我们理解Proxy,也就可以理解沙箱隔离是怎么做的了。下图是MDN上的定义:
通俗解释就是对一个对象的复制及代理。应用于qiankun的语境中,就是对Window对象进行复制,并代理给一个新的对象A,这个A对象就是子应用的运行环境。所以经过Proxy的处理后,会有两个
独立的对象window和A,主应用的运行环境为window,子应用的运行环境为A,所以主应用子应用之间可以把运行环境隔离开。下图是官方文档里的使用示例:
Proxy中通过get和set是两个比较关键的拦截器可以拦截对原始对象的操作。
现在开始介绍这三种沙箱。
-
legacySandbox。只有明确声明使用单实例模式的时候才会应用这种模式,反过来说它也只能支持单实例模式。造成这个局限的原因就是legacySandbox的沙箱是一个“伪沙箱”,他虽然使用了Proxy对window做了代理,但是并没有使用代理出来的新对象(运行环境)。它是这样的一个运行模式:
核心:设置值的时候设置的都是主应用的window,Proxy做的事情就是记录改变(也就是快照),在卸载的时候使用快照把主应用的window恢复成原样。%所以:由于legacySandbox会修改主应用window,所以多实例的场景下各个子应用都改变window会造成逻辑冲突。
-
ProxySandbox。默认使用的沙箱模式。和legacySandbox的区别就是支持多实例。
他的核心思路很简单。只是存储了一个状态池作为子应用的沙箱,这样的话如果有多个子应用,就可以产生多个沙箱,即多个状态池。这些状态池是独立对象,且互不干扰,所以ProxySandbox可以使用在多实例模式中。ProxySandbox和legacySandbox的明显区别:ProxySandbox真正给fakeWindow赋值而且将fakeWindow作为运行环境,而legacySandbox依然使用主应用window。
-
SnapshotSandbox。当浏览器不支持Proxy的时候,需要有一个兼容的模式。
当浏览器不支持Proxy时会开启SnapshotSandbox。SnapshotSandbox运行时也会改变全局window。它的中心原理是记录子应用运行时对全局window的变更,在卸载时进行还原以达到隔离环境的目的,所以也是“伪沙箱”。
三.3种js沙箱对比
| 多实例 | 兼容ie | 不污染主应用 | |
|---|---|---|---|
| legacySandbox | ❌ | ❌ | ❌ |
| proxySandbox | ✅ | ❌ | ✅ |
| snapshotSandbox | ❌ | ✅ | ❌ |
在qiankun中,首先会根据是否支持window.Proxy来决定是否使用snapshotSandbox,然后再通过是否为单实例模式判断使用legacySandbox还是proxySandbox。proxySandbox由于支持多实例而且不污染主应用,目前看是最优的方案。确实,现在proxySandbox也是目前默认的沙箱模式。
以上为这篇文章的所有内容了!欢迎一起交流~