如何实现网络微前端沙箱

介绍

这篇文章主要讲解了“如何实现网络微前端沙箱”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习”如何实现网络微前端沙箱”吧!

<强>背景

应用沙箱可能是微前端技术体系里面最有意思的部分。一般来说沙箱是微前端技术体系中不是必须要做的事情,因为如果规范做的足够好,是能够避免掉一些变量冲突读写,CSS,样式冲突的情况。但是如果你在一个足够大的体系中,总不能仅仅通过规范来保证应用的可靠性,还是需要技术手段去治理运行时的一些冲突问题,这个也是沙箱方案成为微前端技术体系的一部分原因。

首先纵观各类技术方案,有一个大前提决定了这个沙箱如何做:最终微应用是单实例或多实例存在宿主应用中。这个直接决定了这个沙箱的复杂度和技术方案。

<李>

单实例:同一个时刻只有一个微应用实例存在,此刻浏览器所有浏览器资源都是这个应用独占的,方案要解决的很大程度是应用切换的时候的清理和现场恢复。比较轻量,实现起来也简单。

<李>

多实例:资源不是应用独占,就要解决资源共享的情况,比如路,由样式,全局变量读写,DOM。可能需要考虑的情况比较多,实现较为复杂。

最开始我们的想法是:

从业务场景:我们可能存在的情况是当用户操作一个产品一的同时和另一个产品B发生了关联操作,需要唤醒应用B,做操作,虽然从产品维度可以规避掉,比如先切到B,然后切回,但是从某种程度上因为技术的原因,我们限制了产品交互的发挥。

从技术角度:解决了多实例当然单实例的场景也不在话下,并且单实例的方案某种程度上给编码上带来了一定复杂度,比如业务代码需要自己做业务上下文的切换。

最近乾坤2也转变了思路,从单实例的支持到开始支持多实例,多多少少也侧面说明了,多实例是一个值得投入和技术攻克的场景。

基于上面的考量,我们就开始了我们浏览器VM沙箱的实现探索。总结起来可以用下图表示:

如何实现网络微前端沙箱

<强> JavaScript沙箱实现

<强>沙箱环境构造

要实现沙箱,我们需要隔离掉浏览器的原生对象,但是如何隔离,建立一个沙箱环境呢?Node 中 有 vm  模块,来实现类似的能力,但是浏览器就不行了,但是我们可以利用闭包的能力、利用变量作用域去模拟一个沙箱环境,比如下面的代码:

function foo(window) {   console.log(window.document); } foo({     document: {}; });

比如这段代码的输出一定是 {},而不是原生浏览器的 document。

所以 ConsoleOS(面向阿里云管体系的微前端方案) 实现了一个 Wepback 的插件,在应用代码构建的时候给子应用代码加上一层 wrap  代码,创建一个闭包,把需要隔离的浏览器原生对象变成从下面函数闭包中获取,从而我们可以在应用加载的时候,传入模拟的 window、document  之类的对象。

// 打包代码 __CONSOLE_OS_GLOBAL_HOOK__(id, function (require, module, exports, {window, document, location, history}) {   /* 打包代码 */ }) function __CONSOLE_OS_GLOBAL_HOOK__(id, entry) {   entry(require, module, exports, {window, document, location, history}) }

当然也可以不靠工程化的手段来实现,也可以通过请求脚本,然后在运行时拼接这段代码,然后eval 或者 new Function 来达到相同的目的。

原生对象模拟

沙箱隔离能力有了,剩下的问题就是如何实现这一堆浏览器的原生对象了。最开始的想法是我们根据 ECMA  的规范实现(现在仍然有类似的想法),但是发现成本太高。不过在我们各种实验之后,发现了一个很“取巧”的做法,我们可以 new iframe  对象,把里面的原生浏览器对象通过contentWindow 取出来,因为这些对象天然隔离,就省去了自己实现的成本。

const iframe = document.createElement( 'iframe' );

当然里面有很多的细节需要考量,比如:只有同域的 iframe 才能取出对应的的contentWindow。所以需要提供一个宿主应用空的同域 URL  来作为这个 iframe 初始加载的 URL。当然根据 HTML 的规范,这个 URL 用了 about:blank  一定保证同域,也不会发生资源加载,但是会发生和这个 iframe 中关联的 history 不能被操作,这个时候路由的变换只能变成 hash 模式。

如何实现网络微前端沙箱