上文学习了如何渲染微应用,那么这篇文章就记录一下,如果避免渲染之后存在的两个问题
- 如何解决全局变量污染的问题
- 微应用卸载后,如何自动卸载掉微应用挂载的全局事件
解决方案
通过模拟沙箱,将微应用的代码环境与基座应用分割开。
首先通过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()
//...
}
这样一个简易的沙箱环境就制作完成
原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/274075.html