当前位置: 首页 > Web前端 > vue.js

阿里云开放平台微前端解决方案沙盒实现

时间:2023-04-01 01:27:53 vue.js

介绍微前端已经成为前端领域的热门话题。关于微前端价值的讨论,可以参考科君的《拥抱云时代的前端开发框架——微前端》。说到微前端技术,总有一个绕不开的话题,就是前端沙箱。本文详细讨论如何在微前端领域实现前端沙箱。背景应用沙箱可能是微前端技术体系中最有趣的部分。一般来说,沙盒在微前端技术体系中并不是必须的,因为如果规范做得足够好,可以避免一些读写上的变量冲突,CSS样式冲突。但是如果你在一个足够大的系统中,仅仅通过规范是无法保证应用的可靠性的,还需要技术手段来管理运行时的一些冲突。这也是沙盒解决方案成为微前端技术体系的部分原因。首先,纵观各种技术方案,有一个大前提决定了这个沙箱是如何工作的:最终的微应用是存在于宿主应用中的单个实例或者多个实例。这直接决定了沙盒的复杂程度和技术方案。?单实例:同一时刻只有一个微应用实例存在。此时,浏览器的所有浏览器资源都是这个应用程序独享的。解决方案很大程度上是切换应用程序时的清理和现场恢复。它相对轻量级且易于实现。?多实例:资源不独占应用,需要解决资源共享的情况,如路由、样式、全局变量读写、DOM。可能需要考虑的情况很多,实现起来也比较复杂。一开始我们的想法是:从业务场景来看:我们可能会出现这样一种情况,当用户操作一个产品A,另一个产品B有关联操作,需要唤醒应用B来做操作。虽然从产品维度上可以避免,比如先切换到B再切换回A,但某种程度上,由于技术原因,我们限制了产品交互的使用。从技术角度:解决了多实例。当然,单例场景不是问题,单例方案给编码带来了一定的复杂度。比如业务代码需要自己切换业务上下文。最近,乾坤2也从支持单实例转变为支持多实例,这或多或少说明多实例是一个值得投入和技术突破的场景。基于以上考虑,我们开始探索我们的BrowserVM沙箱的实现。总结一下,可以用下图来表示:JavaScript沙箱实现沙箱环境搭建实现沙箱需要对浏览器原生对象进行隔离,但是如何隔离和搭建沙箱环境呢?Node中有一个vm模块可以实现类似的功能,但是浏览器做不到,但是我们可以利用闭包的能力,通过使用变量作用域来模拟沙箱环境,比如下面的代码:functionfoo(window){console.log(window.document);}foo({document:{};});例如,这段代码的输出必须是{}。而不是本机浏览器的文档。所以ConsoleOS实现了一个wepback插件,在构建应用代码的时候,给子应用代码加一层wrap代码,创建一个闭包,把需要隔离的浏览器原生对象改成从下面获取函数闭包,这样我们就可以通过Enter模拟窗口、文档等对象。//封装代码__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函数,达到同样的目的。有了原生对象模拟沙箱隔离能力,剩下的问题就是如何实现这一堆浏览器的原生对象了。本来的想法是按照ECMA的规范来实现(现在还有类似的想法),但是发现成本太高了。但是,经过我们的各种实验,我们发现了一个非常“刁钻”的做法。我们可以新建一个iframe对象,通过contentWindow取出里面的原生浏览器对象。这些对象应该是自然隔离的,这样就省去了自己实现的需要。成本。constiframe=document.createElement('iframe');当然还有很多细节需要考虑,比如:只有同域的iframe才能检索到对应的contentWindow。因此,有必要提供一个空的宿主应用程序的同域URL作为iframe初始加载的URL。当然,按照HTML规范,在这个URL中使用about:blank必须保证同域,不会发生资源加载,但是无法操作与这个iframe关联的关联历史。这时候路由Transformation只能变成hash方式。如下图所示,我们在对应的iframe中取出原始对象后,会为具体需要隔离的对象生成对应的Proxy,然后对一些属性的获取和属性设置进行一些具体的设置,比如因为window.document需要返回一个具体的沙盒文档,而不是当前浏览器的document.classWindow{constructor(options,context,frame){returnnewProxy(frame.contentWindow,{set(target,name,value){target[name]=value;returntrue;},get(target,name){switch(name){case'document':returncontext.document;default:}if(typeoftarget[name]==='函数'&&/^[a-z]/.test(name)){returntarget[name].bind&&target[name].bind(target);}else{returntarget[name];}}});下面说说细节,有兴趣的可以看看我们开源的代码,点击进入>>>但是为了让文档加载到同一个DOM树上,对于文档来说,大部分的DOM操作的属性和方法仍然直接使用宿主浏览器中文档的属性和方法。由于子应用有自己的沙盒环境,所有以前独占的资源现在都是应用独有的(尤其是location,history),所以子应用也可以同时加载。而对于一些变量,我们也可以通过在代理中设置一些访问权限来限制子应用的能力,比如Cookie、LocalStoage的读写。当iframe被移除时,window中写入的变量和一些超时时间设置也会被移除。(当然DOM事件需要被沙盒化,然后在主机中移除)。综上所述,我们的sandbox可以实现以下特性:CSS隔离CSS隔离方案比较常规,常见的有:?CSSModule?添加css命名空间?动态样式表?ShadowDOMCSSModuleorCSSNamespace:通过修改Basic组件样式前缀来实现依赖基础组件样式(依赖工程CSS预处理器编译和运行时基础组件库配置)的框架和微应用的隔离,同时避免全局样式的编写(依赖约定或项目lint手段)。DynamicStyleSheet:隔离方式是在js运行时通过动态加载和卸载微应用样式表来避免样式冲突。第一个限制是站点框架本身或其组件(页眉/菜单/页脚)与当前运行的微应用程序之间仍然存在样式冲突的可能性,第二个是无法支持显示同时运行的多个微应用程序。ShadowDOM:优点是浏览器级别提供的样式隔离能力,可以完全隔离。缺点是目前兼容性还不够好,改造会涉及到旧应用业务代码的改造,对子应用的侵入性比较大。最后经过实践,我们选择的方式是CSSModule+namespace来添加CSS。CSS模块保证应用业务风格不冲突,Namespace保证公库不冲突。我们实施了一个postcss插件,它在构建应用程序时为所有样式添加前缀,包括应用程序公共存储库中的CSS。(这样很容易实现同一个组件库的新旧版本的兼容)。如下图所示://hosthostapp.next-btn{color:#eee;}//subapplicationsubappaliyun-slb.next-btn{color:#eee;}//host中生成的节点这种实现的优点是:?每个应用都有一个命名空间,多个实例可以共存。?不依赖于特定的CSS预处理器。?对于同一个库中不同版本的CSS(比如fusion1和fusion2),可以做到完全隔离。?鉴于上面JS沙箱的存在,对于一些弹窗组件,本微应用获取的body实际上是宿主生成的节点,所以弹窗会添加到微应用(即上面的aliyun-slb)为这个节点,样式不会失效。但是,也存在一些问题,例如:?嵌套应用程序组件样式优先级问题。(由于css模块的存在,一般只出现在publiccss样式中,这是为了尽量避免嵌套)。?不同版本融合中常见字体的问题。(目前的方案比较hacky,下一个字体的名字用工程手段替换)。如何与其他系统结合一般来说,如果你看了上面的文章,你觉得这个沙箱方案不错,但是你有自己的微前端系统,如果要应用的话应该怎么办呢?目前,ConsoleOS的代码已经在Github上开源。在这里你可以试试看。点击进入>>>JS沙箱部分:如果你了解了上面对原理的介绍,就可以看出沙箱的实现其实包括两个层次。本机浏览器对象模拟的一部分(浏览器-VM)?如何构建闭包环境的一部分。Browser-VM可以直接使用,这部分是完全通用的。但是在闭包构建上,各个微前端系统并不一致,可能需要修改。例如:import{createContext,removeContext}from'@alicloud/console-os-browser-vm';constcontext=awaitcreateContext();construn=window.eval(`(()=>function({window,history,locaiton,document}){window.test=1;})()`)run(上下文);console.log(context.window.test);console.log(window.test);//操作虚拟化浏览器对象context.history.pushState(null,null,'/test');context.locaiton.hash='foo'//销毁上下文awaitremoveContext(context);当然你也可以直接选择沙盒提供的好的evalScripts方法:import{evalScripts}from'@alicloud/console-os-browser-vm';constcontext=evalScripts('window.test=1;')console.log(window.test===undefined)//如果trueCSS沙箱是用webpack搭建的,可以直接配置如下:constpostcssWrap=require('@alicloud/console-toolkit-plugin-os/lib/postcssWrap')//下面是webpackconfig{test:/\.css$/,use:['style-loader',{loader:'postcss-loader',options:{plugins:[//添加插件postcssWrap({stackableRoot:'.prefix',repeat:1})],},},'css-loader',]],exclude:/^node_modules$/,}TryLiveDemo不要干巴巴地看文章,直接尝试在线演示可能比较物理,或者试试生产环境的例子,A??libabaCloudEnterpriseWorkbench-ToolApplicationCenter,集成三方应用,提供云管理能力。这里的每一个应用都是一个微应用,涵盖了React、Vue、Angular的三大技术栈。上云,看云栖号:更多云资讯、云案例、最佳实践、产品介绍,访问:https://yqh.aliyun.com/本文为阿里云原创内容,未必未经许可不得转载