上文学习了如何渲染微应用,那么这篇文章就记录一下,如果避免渲染之后存在的两个问题
通过模拟沙箱,将微应用的代码环境与基座应用分割开。
首先通过proxy模拟一个全局变量
export default class Sandbox { // ... microWindow = {} constructor() { this.proxyWindow = new Proxy(microWindow,{ get(target,key){ //... } }) } }
思路就是通过proxyWindow,充当微应用代码中的全局变量。
bindScope(code) { window.proxyWindow = this.proxyWindow; return `(function(window, self){with(window){;${code}\n}}).call(window.proxyWindow, window.proxyWindow, window.proxyWindow)` }
这里通过with将微应用的window
,也就是全局变量通过call指定为我们的proxyWindow
,非常优秀。
然后proxyWindow里面的get拦截器需要注意的是,如果获取的是函数,则需要将函数绑定到原始window上
get: (target, key) => { if (Reflect.has(target, key)) { return Reflect.get(target, key) } const rawValue = Reflect.get(window, key); // 如果原生window的值是函数,则需要将this绑定到window上 // 如:console alert if (typeof rawValue === 'function') { const valueStr = rawValue.toString(); // 排除构造函数 if (!/^function\s+[A-Z]/.test(valueStr) && !/^class\s+/.test(valueStr) ) { return rawValue.bind(window) } } // 其他情况直接返回 return rawValue; },
全局事件监听行为,通过重写addEventListener和removeEventListener来实现,微应用卸载时候自动清除事件
// 记录原生方法 const rawWindowAddEventListener = window.addEventListener; const rawWindowRemoveEventListener = window.removeEventListener; function effect(microWindow) { const eventListenerMap = new Map(); microWindow.addEventListener = function (type, listener, options) { const listenerList = eventListenerMap.get(type); if (listenerList) { listenerList.add(listener) } else { eventListenerMap.set(type, new Set([listener])) } return rawWindowAddEventListener.call(window, type, listener, options) } microWindow.removeEventListener = function (type, listener, options) { const listenerList = eventListenerMap.get(type); if (listenerList?.size && listenerList.get(type)) { listenerList.delete(listener); } return rawWindowRemoveEventListener.call(window,type,listener,options) } return ()=> { // console.log("需要卸载的全局事件",eventListenerMap) if(eventListenerMap.size) { eventListenerMap.forEach((listenerList,type)=> { console.log('listenerList:',listenerList,type) if(listenerList.size) { for (const listener of listenerList) { rawWindowRemoveEventListener.call(window,type,listener) } } }) eventListenerMap.clear() } } }
采用闭包的形式存储事件列表,然后,由于沙箱支持关闭,所以,需要一个变量记录状态,两个函数操作状态
export default class Sandbox { active = false; microWindow = {}; injectedKeys = new Set(); constructor() { this.releaseEffect = effect(this.microWindow) // 保存一下清空函数 // ... } // ... start() { if (!this.active) this.active = true; } stop() { if (this.active) { this.active = false; this.injectedKeys.forEach(k => { Reflect.deleteProperty(this.microWindow, k) }) this.injectedKeys.clear() // 卸载全局事件 this.releaseEffect() } } }
使用
// src/app.js // 资源加载完成后进行渲染 mount() { this.sandbox.start() // .... this.source.scripts.forEach(info => { // (0, eval)(info.code) (0,eval)(this.sandbox.bindScope(info.code)) }) } // 卸载应用 unmount(destory) { this.sandbox.stop() //... }
这样一个简易的沙箱环境就制作完成