JavaScript沙箱

1,525 阅读3分钟

参考链接: mp.weixin.qq.com/s/qOw0-u8w2…

在微前端架构中,JavaScript沙箱需要解决的问题

  1. 挂在window上的全局方法/变量(如setTimeout、滚动等全局事件监听等)在子应用切换时的清理和还原
  2. Cookie、LocalStorage等的读写安全策略限制
  3. 各子应用独立路由的实现
  4. 多个微应用共存时相互独立的实现

qiankun架构设计中,JavaScript沙箱的实现

核心文件

  • 关注两个入口文件proxySandbox.ts和snapshotSandbox.ts

技术方案

  • 他们分别基于proxy实现代理了window上的常用的常量和方法
  • 不支持proxy时降级通过快照实现备份还原

实现思路

起初版本使用了快照沙箱的概念,模拟ES6的proxy API,通过代理劫持window,当子应用修改或使用window上的属性和方法时,把对应的操作记录下来,每次子应用挂载/卸载时生成快照,当再次从外部切回当前子应用时,再从记录的快照中恢复,而后来为了兼容多个子应用共存的情况,又基于Proxy实现了处理所有全局性的常量和方法接口,为每个子应用构造了独立的运行环境。

阿里云开发平台的Browser VM

核心文件

Context.js

实现思路

  1. 借鉴with的实现效果,在webpack编译打包阶段为每个子应用代码包裹一层代码(见其插件包breezr-plugin-os下相关文件),创建一个闭包,传入自己模拟的window、document、location、history等全局对象(见根目录下相关文件)
  2. 在模拟的Context中,new一个iframe对象,提供一个和宿主应用空的(about:blank)同域URL来作为这个iframe初始加载的URL(空的URL不会发生资源加载,但是会产生和这个iframe关联的history不能被操作的问题,这事路由的变换只支持hash模式),然后将其下的原生浏览器对象通过contentWindow取出来(因为iframe对象天然隔离,这里省去了自己Mock实现所有API的成本)
  3. 取出对应的iframe中原生的对象之后,继续对特定需要隔离的对象生成对应的Proxy,然后对一些特定的实现(比如window.document需要返回特定的沙箱document而不是当前浏览器的document等)
  4. 为了文档内容能够被加载在同一个DOM树,对于document,大部分的DOM操作的属性和方法仍然直接使用宿主浏览器的document的属性和方法处理等

Figma

历史实现方案

  • 起初 Figma 同样是将插件代码放入 iframe 中执行并通过 postMessage 与主线程通信,但由于易用性以及 postMessage 序列化带来的性能等问题,Figma 选择还是将插件放入主线程去执行。

实现方案

  • Figma 采用的方案是基于目前还在草案阶段 Realm API,并将 JavaScript 解释器的一种 C++ 实现 Duktape 编译到了 WebAssembly,然后将其嵌入到 Realm 上下文中,实现了其产品下的三方插件的独立运行。

构思web worker实现js隔离