微前端踩坑:qiankun 子应用中使用 element-plus 样式失效问题

项目背景

主应用 vue2 + element;

子应用 vue3 + element-plus。

问题描述

子应用目前的路由路口放在了主应用内,但是之后的业务中会作为一个项目独立出来;需要保证主应用和子应用的样式不能互相影响;

而主应用的老代码由于历史原因,有很多不规范的全局样式,因此对子应用中使用了 strictStyleIsolation: true 开启严格样式隔离;

同时,在子应用的 main.ts 入口文件中,导入了 element-plus

然而项目运行之后发现 element-plus 组件的部分样式居然失效了?!

原因分析

首先,qiankun 严格样式隔离的原理是通过创建一个 shadow dom,并把子应用包裹在 shadow dom 里面:

同时,子应用的样式也都会被挂在 shadow dom 的根节点下:

而 element-plus 的全局变量是通过 :root 选择器来作用到根节点的,但是在 shadow dom 中,是无法通过 :root 来选中根节点的,也就导致了这部分的样式失效。

其实 shadow dom 设计的初衷就是为了代码隔离,避免元素之间互相影响;从这个角度理解 shadow dom 中的样式表通过 :root 无法选中根节点,似乎也合情合理。

but,合理归合理,问题还是得解决的~

解决思路

实际上,在 shadow dom 中也是存在根节点的,这个根节点名为 shadow host ,我们可以用 :host 来代替 :root 选中 shadow dom 对应的根节点:

shadow dom

那我们只要将样式文件中的 :root 替换成 :host,不就可以使样式重新生效了吗!

这里给大家提供几个解决方案:

  1. 在项目本地维护一份 element-plus 相关样式文件,手动将样式文件中的 :root 替换成 :host。

    但这样做的问题就是:如果更新了 element-plus,要记得去手动同步一下这个样式文件。

  2. 介入应用打包构建流程,通过修改编译时信息实现对样式文件的替换。

    乍一听十分高大上,其实说白了就是写一个 loader,在 webpack 打包文件的时候把 :root 替换成 :host;

我在项目中最终采用的是第 2 个方案,下面我们来看看如何实现:

编写 loader

大家千万别被编写 loader 吓到,这个 loader 的逻辑十分简单: 我们直接在项目中新建一个 js 文件,名为 my-style-loader.js,代码如下:

js 复制代码
// my-style-loader.js
const myStyleLoader = function (source) {
  return source.replace(/:root/g, ':host')
}
module.exports = myStyleLoader

这段代码只做了两件事情:

  1. 定义了一个函数,拿到上一个 loader 解析的资源文件,把 :root 替换成 :host,然后再返回替换后的资源文件。
  2. 然后导出这个函数。

so easy~

找准 loader 作用时机

代码完成了,接下来就是考虑怎么使用这个 loader 了。

我们知道最终页面上被包裹在 <style> 标签中的样式内容,实际上是各个 loader 链式作用后的结果,那么我们编写的这个 loader 应该放在哪个步骤呢?我们来一起分析一下:

首先 element-plus 样式的入口是个.scss 文件,内容如下:

element-plus/theme-chalk/src/index.scss

而一个 .scss 文件在打包的时候,一般会经历以下步骤:

  1. 首先会通过 sass-loader,解析文件中的语法,将 .scss 文件转换成 .css 文件;
  2. 接下来,css-loader 会接收到 sass-loader 转换后的资源文件,并进一步解析文件中的 url 路径、@import 语法等等;
  3. 最后通过 style-loader,将解析好的样式文件包裹在 <style> 标签内,并挂载到 dom 上;

严格来讲,style-loader 的作用时机是早于 sass-loader 和 css-loader,这和 loader 的机制有关系,大家可以先按照这个顺序来理解,关键是了解每个 loader 做了什么,从这一点来考虑我们编写的 loader 的插入顺序

这么一看,我们编写的 loader 只要作用在 sass-loader 之后,style-loader 之前就可以了。

修改 webpack 配置

最后一步,就是修改 webpack 配置了。

由于 webpack 默认是从 node_modules 中寻找 loader 的,所以我们要把我们编写的 loader 路径,添加到 webpack loader 解析路径的规则中:

js 复制代码
module.exports = {
  //...
  resolveLoader: {
    modules: [
    'node_modules',
    './src/loaders' // 在这里添加我们编写的 loader 所在路径
   ],
  },
};

然后我们需要在 webpack 配置中新增一条规则:

js 复制代码
module: {
  rules: [
    {
      test: /\.scss$/,
      include: [
        path.resolve(__dirname, 'node_modules/element-plus/theme-chalk') // 这条规则只匹配element-plus 下的样式文件
      ],
      use: [
        'style-loader',
        'css-loader',
        'my-style-loader',
        'sass-loader'
      ]
    }
  ]
}

这里还有一种更为便捷的方式------通过内联 loader 来导入这个文件:

js 复制代码
import '!!style-loader!css-loader!my-style-loader!sass-loader!element-plus/theme-chalk/src/index.scss'

其中 !! 表示禁用其它所有的 loader 配置,只启用内联 loader;每个 loader 之间又通过 ! 来分隔。

因此上面代码翻译过来就是:

针对这个 .scss 文件,先用 sass-loader 处理 sass 语法,再用我们自己编写的 my-style-loader 替换指定选择器,然后交给 css-loader 解析,最后再通过 style-loader 挂载到页面上。

这么一来就大功告成啦!

相关推荐
原则猫7 小时前
UDP 到底怎么实现可靠的
前端
Java后端的Ai之路7 小时前
14、ReactJS - 路由(Router)
前端·react.js·状态模式
裕晟资质规划8 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
ShineWinsu9 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
月夏9 小时前
Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)
前端
广州华水科技9 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
梦曦i9 小时前
uni-storage 0.1.0 发布:TTL过期管理+多实例隔离
前端·storage·uni-app x
猫头_10 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
console.log('npc')11 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型
定栓11 小时前
Webpack迁到Vite第七节(二):全家桶移除 — Babel 全家桶深度分析
前端·webpack·node.js