micro-app的css样式隔离

devtools/2024/9/23 14:35:56/

手写微前端micro-app-CSS隔离

子应用的CSS可能会对基座应用或者其他子应用产生的影响

首先现在我们把react页面放入到vue2的页面大家也能看到一些问题了,在react中的index.css中对body的一些css样式,已经影响了基座应用的css

为了看的更明显,我自己写一下,在基座应用(vue2)中声明一个css样式,比如:

.text-color{color:red
}

做实验的时候,如果是vue2项目。别把这个样式写到了带scoped的style样式标签中了,这种本身就是隔离的,我们这里所谓的隔离,主要是针对全局样式

由于我们前面的处理只是将link标签转换为了style标签,因此在react项目中做处理的话,最好将样式写在静态css文件中,比如之前讲index.css文件放到了index.html中

如果在子应用中,也有同名的全局样式

css">.text-color{color:yellow
}

那么,你会发现,子应用的这个样式,对基座应用中,同样使用这个样式的标签起了作用,我们就是要隔绝这种情况

在这里插入图片描述

比如上面的.text-color这个样式,在子应用中,我们就可能会加上

micro-app[name=app] .text-color

2、代码实现

首先创建新的文件scopedcss.js,创建scopedCSS函数,来进行css过滤替换处理。从上面的演示可以分析出,这个函数我们至少需要两个参数

1、style节点对象,通过这个对象获取textContent与sheet的值

2、子应用app的名字,因为我们需要这个名字来组装前缀

/*** 进行样式隔离* @param {HTMLStyleElement} styleElement style元素* @param {string} appName 应用名称*/
export default function scopedCSS (styleElement, appName) {// 前缀const prefix = `micro-app[name=${appName}]`console.log(styleElement.sheet);console.log(styleElement.textContent);
}

你会发现打印了textContent的内容,但是sheet内容却为空,原因是css没有挂载到页面之前,样式表还没生成。是获取不了sheet的。而且有时候style元素(比如动态创建的style)在执行样式隔离时还没插入到文档中,此时样式表还没生成。也会出现这种情况

不能获取sheet内容的话,我们仅仅凭借textContent字符串的内容,去做处理工作量太大,也不好区分css中的内容

所以我们做一个取巧的办法,声明一个临时的style模板,用来填充css,用完之后删除

let templateStyle // 模版sytle
/*** 进行样式隔离* @param {HTMLStyleElement} styleElement style元素* @param {string} appName 应用名称*/
export default function scopedCSS (styleElement, appName) {// 前缀const prefix = `micro-app[name=${appName}]`// console.log(styleElement.sheet);// console.log(styleElement.textContent);// 初始化时创建模版标签if (!templateStyle) {templateStyle = document.createElement('style')document.body.appendChild(templateStyle)// 设置样式表无效,防止对应用造成影响templateStyle.sheet.disabled = true}if (styleElement.textContent) {// 将元素的内容赋值给模版元素templateStyle.textContent = styleElement.textContent// 获取临时模板中的sheetconsole.log(templateStyle.sheet)} 
}

我们需要的是将**@media内部的.text加上前缀,而这些,sheet中的cssRules**已经帮我们划分了类型了,类型有数十种,我们只处理STYLE_RULEMEDIA_RULESUPPORTS_RULE三种类型

  • type为1的,是普通的样式STYLE_RULE

  • type为4的,是media类型,MEDIA_RULE

  • type为12的,为supports类型SUPPORTS_RULE

也就是说,我们需要根据类型不一样,分开进行处理。其实分开处理无非也就是mediasupports类型,再递归执行一下

let templateStyle // 模版sytle
/*** 进行样式隔离* @param {HTMLStyleElement} styleElement style元素* @param {string} appName 应用名称*/
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// console.log(templateStyle.sheet)// 格式化规则,并将格式化后的规则赋值给style元素styleElement.textContent = scopedRule(Array.from(templateStyle.sheet.cssRules || []), prefix)// 清空模版style内容templateStyle.textContent = ''} 
}/*** 依次处理每个cssRule* @param rules cssRule* @param prefix 前缀*/
function scopedRule (rules, prefix) {let result = ''// 遍历rules,处理每一条规则for (const rule of rules) {switch (rule.type) {case 1: // STYLE_RULEresult += scopedStyleRule(rule, prefix)breakcase 4: // MEDIA_RULEresult += scopedPackRule(rule, prefix, 'media')breakcase 12: // SUPPORTS_RULEresult += scopedPackRule(rule, prefix, 'supports')breakdefault:result += rule.cssTextbreak}}return result
}// 处理media 和 supports
function scopedPackRule (rule, prefix, packName) {// 递归执行scopedRule,处理media 和 supports内部规则const result = scopedRule(Array.from(rule.cssRules), prefix)return `@${packName} ${rule.conditionText} {${result}}`
}

递归之后,最终其实还是使用**scopedStyleRule()**函数进行处理。这个函数难度最大,因为要写难度的很大的正则表达式,太复杂了,我也不会,找了一下micro-app的源码

/*** 修改CSS规则,添加前缀* @param {CSSRule} rule css规则* @param {string} prefix 前缀*/
function scopedStyleRule (rule, prefix) {// 获取CSS规则对象的选择和内容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 === '*') {// 选择器 * 替换为 micro-app[name=xxx] *return cssText.replace('*', `${prefix} *`)}// 匹配顶层选择器,如 body,htmlconst builtInRootSelectorRE = /(^|\s+)((html[\s>~]+body)|(html|body|:root))(?=[\s>~]+|$)/// 匹配查询选择器return cssText.replace(/^[\s\S]+{/, (selectors) => {return selectors.replace(/(^|,)([^,]+)/g, (all, $1, $2) => {// 如果含有顶层选择器,需要单独处理if (builtInRootSelectorRE.test($2)) {// body[name=xx]|body.xx|body#xx 等都不需要转换return all.replace(builtInRootSelectorRE, prefix)}// 在选择器前加上前缀return `${$1} ${prefix} ${$2.replace(/^\s*/, '')}`})})
}

效果
在这里插入图片描述


http://www.ppmy.cn/devtools/8307.html

相关文章

ETL 与 ELT:哪一个适合您的业务?

每家公司都面临着诸多相互竞争的优先事项,数量之多,动辄数十,甚至可能达到数千。为了从海量的数据中筛选出有价值的见解,许多企业开始转向ETL或ELT流程,以组织并优化其数据资产。然而,随着SaaS产品的广泛应…

判断完数(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int n 0;int i 1;int j 0;int result 1;//提示用户&#xff1b;printf("请输入一个…

Java复习第二十天学习笔记(过滤器Filter),附有道云笔记链接

【有道云笔记】二十 4.8 过滤器Filter https://note.youdao.com/s/dSofip3f 一、为什么要使用过滤器 项目开发中&#xff0c;经常会用到重复代码的实现。 1、请求每个servlet都要设置编码 2、判断用户是否登录&#xff0c;只有登录了才有操作权限。 二、过滤器相关Api int…

阿里二面凉了,难蹦。。。

分享一位同学阿里巴巴的后端面经&#xff0c;共有 2 面&#xff0c;第一面很顺利过了&#xff0c;可惜挂在第二面。 这两面的知识点范围&#xff0c;我帮大家罗列一下&#xff1a; 网络&#xff1a;TCP、HTTP mysql&#xff1a;索引应用、索引结构、隔离级别、最左匹配 redis…

JMeter组件--配置元件--JSON断言

JSON断言&#xff08;JSON Assertion&#xff09; 可以对 Jmeter 取样器的响应消息以 JSON 方式进行检查&#xff1b;检测响应报文返回的特征值&#xff0c;进而判断取样器在业务上是否正确&#xff1b; 右键 >>> 添加 >>> 断言 >>> JSON断言&…

LeetCode刷题总结 | 图论1—深度优先搜索广度优先搜索一些简单套模板问题

1.深度优先搜索 1.1 深搜三部曲 1.确认递归函数&#xff0c;参数 void dfs(参数)通常我们递归的时候&#xff0c;我们递归搜索需要了解哪些参数&#xff0c;其实也可以在写递归函数的时候&#xff0c;发现需要什么参数&#xff0c;再去补充就可以。 一般情况&#xff0c;深…

快速掌握Spring监控(Spring Boot admin)

监控 监控可视化监控平台Admin底层逻辑info 自定义端点 监控 监控的作用&#xff1a; 监控服务状态是否宕机监控服务运行指标&#xff08;内存&#xff0c;虚拟机&#xff0c;线程&#xff0c;请求等&#xff09;监控日志管理服务&#xff08;服务下线&#xff09; 监控的实…

230基于matlab的布谷鸟(COA)多目标优化算法

基于matlab的布谷鸟&#xff08;COA&#xff09;多目标优化算法&#xff0c;以 满意度、成本、时间、质量为目标的多目标优化求解代码。程序已调通&#xff0c;可直接运行。 230 matlab 布谷鸟&#xff08;COA&#xff09;多目标优化 - 小红书 (xiaohongshu.com)