qiankun微前端原理

1,678 阅读2分钟

加载

qiankun以url为入口进行加载,请求下来url对应的文档后,先识别文档内容中的外链资源,然后自己去加载对应的资源内容,并删除文档中的资源,然后对加载内容做处理后再加回dom上

js隔离

js隔离在qiankun中有如下三种方式:

快照沙箱

只支持单应用的代理沙箱

支持多应用的代理沙箱

这里就不细说了,请看文章:juejin.cn/post/707003…

怎么把原生window对象替换为proxy的

这才是本文想讲的内容

一个应用的中的js分为两种:一种是html模板中写好的,一种是后续动态加载的

对于html模板中写好的资源,qiankun做法如下:

const rawDOMAppendOrInsertBefore = HTMLHeadElement.prototype.appendChild
function mngCode(scriptText, sourceUrl) {
    return `;(function(window, self, globalThis){
                    with(window){
                        ;`.concat(scriptText, "\n")
                            .concat(sourceUrl,
                                `
                    }
             }).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`
}

const { scripts } = parse(html)

scripts.forEach(elm => {
    if (elm.src) {
        const source = fetch(elm.src)
        const code = mngCode(source, elm.src)
        eval(code)
    }
    eval(mngCode(elm.text, ''))

    var scriptCommentElement = document.createComment(
        "dynamic script ".concat(src, " replaced by qiankun")
    );
    rawDOMAppendOrInsertBefore.call(_mountDOM, scriptCommentElement)
})

简单来说,qiankun会把模板中的js提取出来,对于外链的,自己去请求,然后把请求结果包装一下使用proxy代理window对象,再去执行;对于内嵌的,也是包装一下使用proxy代理window对象,再去执行。并把原资源替换为注释元素,防止原本的代码执行并提示下用户这里原本有这个资源

对于动态加载的js,处理如下:

function mngCode(scriptText, sourceUrl) {
    return `;(function(window, self, globalThis){
                    with(window){
                        ;`.concat(scriptText, "\n")
                            .concat(sourceUrl,
                                `
                    }
             }).bind(window.proxy)(window.proxy, window.proxy, window.proxy);`
}

const rawDOMAppendOrInsertBefore = HTMLHeadElement.prototype.appendChild
HTMLHeadElement.prototype.appendChild = function overwriteAppendChild(elm) {
    switch(elm.tagName) {
        case 'script':
            if (elm.src) {
                const source = fetch(elm.src)
                const code = mngCode(source, elm.src)
                eval(code)
            }
            eval(mngCode(elm.text, ''))
            
            var scriptCommentElement = document.createComment(
                "dynamic script ".concat(src, " replaced by qiankun")
            );
	    return rawDOMAppendOrInsertBefore.call(_mountDOM, scriptCommentElement);
            
    }
}

如上代码所示,qiankun重写了原生appendChild方法,当调用此方法,最终只会插入一个替代的注释元素,并由乾坤请求原本的链接后自己去执行,在执行时使用proxy替代了全局window对象

css隔离

有三种方式:直接把样式节点挂载在挂载子应用的根元素里、shadowDomcss-modules

第一种无法隔离子应用和主应用的样式,如果同时存在多个子应用,也无法隔离 shadowDom就是把整个子应用直接放到一个shadowDom元素里

css-modules

有了js的处理经验,这种应该很容易懂了,因为大体逻辑跟js保持一致就可以了,只是最后的处理方式略有不同

const rules = styleNode.sheet.cssRules
const css = [...rules].reduce((pre, cur) => pre + appName + cur.selectorText + ':'
    + cur.cssText.replace(cur.selectorText, '') + ';', '')
styleNode.textContent = 

相对路径问题可以通过重写getTemplate方法解决: www.cnblogs.com/goloving/p/…