从前有匹马叫代码
心若没有栖息的地方,到哪里都是流浪

接着上面两篇文章,继续学习微前端,本章主要内容如下:

  •   微应用的样式隔离

首先,为什么需要做样式隔离?

因为微应用的话,是使用html entry加web components来实现的,所以说,这种方式实现的微应用并不具有iframe那种天然的样式隔离特性,

基于前两篇的分析,我们可以看出,我们是将html entry中的 资源(style,link,script)抽离出来,然后再挂载到micro-app-head中的,由于微应用

的样式挂载时机是出于基座应用后面的,所以如果基座应用与微应用具有相同的选择器,则微应用会覆盖掉基座应用的选择器。

借用一张图片来说明:

 

 

 如何解决这个问题?

既然我们可以拿到资源的代码,那么就在获取代码之后进行处理,思路是,将对应的样式代码,进行正则匹配替换,统一加上前缀,

 

const prefix = `micro-app[name=${appName}]`;

 

这样的话,每个微应用由于appName不同,也就不会相互影响了。

具体实现是采用cssRules+递归来处理不同的cssRule来实现,代码如下:

let templateStyle;

/**
 * 进行样式隔离
 * @param {HTMLStyleElement}
 * @param {string}
 */

export default function scopedCSS(styleElement, appName) {
    // 前缀
    const prefix = `micro-app[name=${appName}]`;
    if (!templateStyle) {
        templateStyle = document.createElement("style");
        document.body.appendChild(templateStyle);
        // 设置样式表无效
        templateStyle.sheet.disabled = true;
    }

    if (styleElement.textContent) {
        templateStyle.textContent = styleElement.textContent;
        styleElement.textContent = scopedRule(
            Array.from(templateStyle.sheet?.cssRules ?? []),
            prefix
        );
        // 清空模板style内容;
        templateStyle.textContent = "";
    } else {
        const observe = new MutationObserver(function () {
            // 断开监听
            observe.disconnect();
            styleElement.textContent = scopedRule(
                Array.from(templateStyle.sheet?.cssRule ?? []),
                prefix
            );
        });
        observe.observe(styleElement, { childList: true });
    }
}

function scopedRule(rules, prefix) {
    let result = "";
    for (const rule of rules) {
        switch (rule.type) {
            case 1:
                result += scopedStyleRule(rule, prefix);
                break;
            case 4:
                result += scopedPackRule(rule, prefix, "media");
                break;
            case 12:
                result += scopedPackRule(rule, prefix, "supports");
            default:
                result += rule.cssText;
                break;
        }
    }
    return result;
}

function scopedPackRule(rule, prefix, packName) {
    const result = scopedRule(Array.from(rule.cssRules), prefix);
    return `@${packName} ${rule.conditionText}{ ${result} }`;
}

function scopedStyleRule(rule, prefix) {
    const { selectorText, cssText } = rule;
    // 处理顶层选择器,如 body,html 都转换为 micro-app[name=xxx]
    if (/^((html[\s>~,]+body)|(html|body|:root))$/.test(selectorText)) {
        return cssText.replace(
            /^((html[\s>~,]+body)|(html|body|:root))$/,
            prefix
        );
    } else if (selectorText === "*") {
        return cssText.replace("*", `${prefix} *`);
    }

    const builtInRootSelectorRE =
        /(^|\s+)((html[\s>~]+body)|(html|body|:root))(?=[\s>~]+|$)/;

    return cssText.replace(/^[\s\S]+{/, (selector) => {
        return selector.replace(/(^|,)([^,]+)/g, (all, $1, $2) => {
            if (builtInRootSelectorRE.test($2)) {
                return all.replace(builtInRootSelectorRE, prefix);
            }
            return `${$1} ${prefix} ${$2.replace(/^\s*/, "")}`;
        });
    });
}

 

然后在获取到资源的时候进行调用就可以了,举个例子:

 

function fetchLinksFromHtml(app, microAppHead, htmlDom) {
    const linkEntries = Array.from(app.source.links.entries());
    // 通过fetch请求所有css资源
    const fetchLinkPromise = [];
    for (const [url] of linkEntries) {
        fetchLinkPromise.push(fetchSource(url));
    }

    Promise.all(fetchLinkPromise)
        .then((res) => {
            for (let i = 0; i < res.length; i++) {
                const code = res[i];
                // 拿到css资源后放入style元素并插入到micro-app-head中
                const link2Style = document.createElement("style");
                link2Style.textContent = code;
                console.log("link2style", link2Style.textContent);
                scopedCSS(link2Style, app.name);
                microAppHead.appendChild(link2Style);

                // 将代码放入缓存,再次渲染时可以从缓存中获取
                linkEntries[i][1].code = code;
            }

            // 处理完成后执行onLoad方法
            app.onLoad(htmlDom);
        })
        .catch((e) => {
            console.error("加载css出错", e);
        });
}

 

 

 

 

 

 

posted on 2022-07-18 15:19  从前有匹马叫代码  阅读(43)  评论(0编辑  收藏  举报