前言
在前公司工作的时候,我曾经手写过qiankun的核心代码用于技术分享。
这里先简单说一下qiankun是如何完成子应用挂载的。
1.qiankun会在基座应用中请求子应用的模板
2.由于浏览器安全限制,模版中的script代码是无效的,所以qiankun会过滤出所有的script,单独请求下来并通过eval等方式执行(比如单页应用,其挂载实际上就是执行了主js代码,通过调用对应的mount函数完成挂载)
qiankun在执行脚本时可能会出现全局变量污染,因此需要使用沙箱去隔离
下面就来聊一下我是如何实现qiankun的沙箱,隔离子应用和基座应用间的全局变量污染。
代码实现
闲话少说,直接上代码。
js
export class sandbox { // 沙箱完整版
running = false // 是否启动沙箱
fakeWindow = {}
realWindow = window
box = null // 沙箱proxy实例
constructor() {
this.box = new Proxy(this.fakeWindow, {
get: (target, key) => {
// 沙箱取值优先从 fakeWindow 上获取
if (Reflect.has(target, key)) return Reflect.get(target, key) // 优先代理对象
// 否则从window上找
const result = Reflect.get(this.realWindow, key)
if (typeof result == 'function') {
// 如果当前项在window中为函数则返回待执行函数
return result.bind(this.realWindow)
}
return result
},
set: (target, key, value) => {
// 沙箱启动的前提下
if (this.running) {
Reflect.set(target, key, value)
}
return true
}
})
}
// 启动沙箱
active() {
this.running = true
}
// 关闭沙箱
inactive() {
this.running = false
this.fakeWindow = {}
}
}
代码解读
这段代码是一个沙箱类的完整版本。
用于隔离代码执行环境的机制,可以限制代码的访问权限,提高代码的安全性。
代码中的 sandbox
类有以下几个属性和方法:
running
:一个布尔值,表示沙箱是否启动。fakeWindow
:一个空对象,用于模拟窗口对象。realWindow
:指向全局的window
对象。box
:沙箱的代理实例。
在构造函数中,通过 Proxy
对象创建了一个代理实例 box
,用于拦截对沙箱的访问。
代理对象的 get
方法用于拦截属性的读取操作,如果属性存在于 fakeWindow
对象中,则返回对应的值;否则,从全局的 window
对象中获取对应的值。如果获取的值是一个函数,则将其绑定到真正的 window
对象上返回。
代理对象的 set
方法用于拦截属性的赋值操作,只有在沙箱启动的前提下,才会允许属性的赋值; 赋值时会存储到 fakeWindow
上防止污染 realWindow
active()
方法用于启动沙箱,将 running
属性设置为 true
。
inactive()
方法用于关闭沙箱,将 running
属性设置为 false
,并重置 fakeWindow
对象为空对象。
通过使用这个沙箱类,可以在代码执行时限制对全局环境的访问,增加代码的安全性。
由于qiankun会使用eval等方式去执行脚本,通过eval读取上下文等特性,将沙箱劫持的代理对象作为window传入实现js隔离。
代码如下:
js
((window, module, exports) => {
try {
eval(code);
} catch (error) {
console.log(error);
}
})(app.sandbox.box, module, exports);