微前端-02


上文学习了如何渲染微应用,那么这篇文章就记录一下,如果避免渲染之后存在的两个问题

  1. 如何解决全局变量污染的问题
  2. 微应用卸载后,如何自动卸载掉微应用挂载的全局事件

解决方案

通过模拟沙箱,将微应用的代码环境与基座应用分割开。
首先通过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

(0)
上一篇 2022年7月13日
下一篇 2022年7月13日

相关推荐

发表回复

登录后才能评论