微前端踩坑: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-loadercss-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 挂载到页面上。

这么一来就大功告成啦!

相关推荐
Devil枫24 分钟前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦1 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子2 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享2 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf4 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨4 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL4 小时前
npm入门教程1:npm简介
前端·npm·node.js
小白白一枚1115 小时前
css实现div被图片撑开
前端·css
薛一半5 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js