qiankun js沙箱机制原理剖析

879 阅读5分钟

一.前言

目前微前端在我们团队中的应用已经日渐成熟,其中应用最为广泛的解决方案是qiankun。我们已知qiankun相较于iframe具有一些优势:

  • 更好的路由前进后退
  • 更自然的弹窗遮罩层覆盖可视范围
  • 更方便的页面间消息传递

同时,qiankun的可用也是因为解决了三个关键的问题:

  • 子应用的加载
  • 应用间的环境隔离
  • 路由劫持

纵使qiankun有众多好处,但是相对于iframe天生的主与子、子与子之间js、css完全隔离,qiankun的环境隔离还是没那么让我们省心的🤧,有很多我们需要注意的细节,就比如我上一篇文章中遇到的问题:子应用使用jsonp时候会出现jsonp回调函数未定义的情况。如下图: image.png 上篇文章中定位到了原因:是fetchJsonp回调函数的定义调用环境不一致导致调用时候报错函数未定义。即回调函数定义在子应用环境,而调用位置在主应用环境。这里的两个环境就是qiankun的沙箱机制生成的。 已知问题出现的原因,本篇文章就来研究一下qiankun中的运行环境隔离(沙箱机制)是怎么做的。

二.qiankun中JS沙箱源码分析

一.沙箱生成的代码流程

源码中我们直接跳到子应用创建时。当子应用创建时,会调用函数loadApp,调用位置如下图: image.png 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上的定义: image.png 通俗解释就是对一个对象的复制及代理。应用于qiankun的语境中,就是对Window对象进行复制,并代理给一个新的对象A,这个A对象就是子应用的运行环境。所以经过Proxy的处理后,会有两个独立的对象window和A,主应用的运行环境为window,子应用的运行环境为A,所以主应用子应用之间可以把运行环境隔离开。下图是官方文档里的使用示例: image.png Proxy中通过get和set是两个比较关键的拦截器可以拦截对原始对象的操作。

现在开始介绍这三种沙箱。

  1. legacySandbox。只有明确声明使用单实例模式的时候才会应用这种模式,反过来说它也只能支持单实例模式。造成这个局限的原因就是legacySandbox的沙箱是一个“伪沙箱”,他虽然使用了Proxy对window做了代理,但是并没有使用代理出来的新对象(运行环境)。它是这样的一个运行模式:image.png 核心:设置值的时候设置的都是主应用的window,Proxy做的事情就是记录改变(也就是快照),在卸载的时候使用快照把主应用的window恢复成原样。%所以:由于legacySandbox会修改主应用window,所以多实例的场景下各个子应用都改变window会造成逻辑冲突。 image.png

  2. ProxySandbox。默认使用的沙箱模式。和legacySandbox的区别就是支持多实例。image.png 他的核心思路很简单。只是存储了一个状态池作为子应用的沙箱,这样的话如果有多个子应用,就可以产生多个沙箱,即多个状态池。这些状态池是独立对象,且互不干扰,所以ProxySandbox可以使用在多实例模式中。ProxySandbox和legacySandbox的明显区别:ProxySandbox真正给fakeWindow赋值而且将fakeWindow作为运行环境,而legacySandbox依然使用主应用window。 image.png image.png

  3. SnapshotSandbox。当浏览器不支持Proxy的时候,需要有一个兼容的模式。image.png 当浏览器不支持Proxy时会开启SnapshotSandbox。SnapshotSandbox运行时也会改变全局window。它的中心原理是记录子应用运行时对全局window的变更,在卸载时进行还原以达到隔离环境的目的,所以也是“伪沙箱”。 image.png

三.3种js沙箱对比

多实例兼容ie不污染主应用
legacySandbox
proxySandbox
snapshotSandbox

在qiankun中,首先会根据是否支持window.Proxy来决定是否使用snapshotSandbox,然后再通过是否为单实例模式判断使用legacySandbox还是proxySandbox。proxySandbox由于支持多实例而且不污染主应用,目前看是最优的方案。确实,现在proxySandbox也是目前默认的沙箱模式。

以上为这篇文章的所有内容了!欢迎一起交流~