Rsbuild迁移之node-sass引发的血案

手上的工程非常古老,详细可看我的文章Vue2.6+Webpack4项目迁移至Rsbuild。迁移的过程中,老板明确用户分批迁移且迁移时间按月维度甚至更长。还提出必须要提供出问题后快速切回稳定环境的能力。因此,大刀阔斧修改现有代码的方案需要排除,需要采用同一套代码兼容新(Rsbuild)、旧(Webpack 4)两个构建工具的方案。

主要改造点

首先列举迁移到Rsbuild主要的改造点:

  • Node版本从14升级到18+
  • Node Sass改为Dart Sass

Sass不当语法修改

升级Dart Sass 后存在部分不兼容语法,按照提示进行改造即可(详细可见上面的迁移文章)。此部分改造经测试Node SassDart Sass 均兼容。

/deep/迁移至::v-deep

最要命的部分来了。Node SassDart Sass 有一个水火不容不可调和的语法,那就是穿透符号:Node Sass 使用/deep/Dart Sass使用::v-deep

如果同一套代码要适配两个不同的sass,那么只能从sass-loader 中入手:使用Rsbuild的Sass plugin在解析Sass内容时全局将代码中的/deep/ 替换为::v-deep

可以按照以下语句进行替换:

js 复制代码
// rsbuild.config.js
import { defineConfig } from '@rsbuild/core';
import { pluginSass } from '@rsbuild/plugin-sass';
import { pluginVue2 } from '@rsbuild/plugin-vue2';

export default defineConfig({
  plugins: [
    pluginVue2(),
    pluginSass({
      sassLoaderOptions: {
        additionalData: (content) => content.replace(/\/deep\//g, '::v-deep '),
      },
    }),
  ],
});

但是经测试,这个配置只能将.vue 文件中写在<style lang="scss"></style> 中的代码进行替换,通过下面这种形式引入的独立scss 文件中的代码替换不了,因而引发构建报错:

scss 复制代码
// style/test.scss

/deep/ .deep-component {
  color: red;
}
vue 复制代码
<style scoped lang="scss">
@import "~@style/test.scss";
</style>

在上面的additionalData 方法中,此处语句打印出的content结果是:

js 复制代码
// log
@import "~@style/test.scss";

所以独立文件内的Sass内容使用additionalData 方法替换是不生效的。

然后网上找了各种方法,包括但不限于使用string-replace-loader自定义插件替换Sass内容,都没有成功。

仔细想一想,sass-loader的解析流程:先将.vue 文件中的style 标签解析,然后再读取style 内部的@import 语句导入的内容进行合并。

那么,有没有方法,是在sass-loader读取@import 语句导入内容时运行的呢?答案是有,Sass Importer就介绍了自定义加载逻辑的方法。

由于Rsbuild 使用sass-embedded 且默认开启modern-compilerAPI,因此需要使用新的方法自定义:

js 复制代码
// scss-replace-importer.js
import fs from "fs";

export const SCSSDeepReplaceFileImporter = (options) => {
    return {
        findFileUrl(url) {
            const target = options.find(i => i.url === url)
            if (target) {
                const content = fs.readFileSync(target.path, "utf8");
                const newContent = content.replace(/\/deep\//g, "::v-deep");
                const cachePath = `.cache/${target.cacheName}`;

                fs.writeFileSync(cachePath, newContent);
                return new URL(`./${cachePath}`, import.meta.url);
            }
            return null;
        },
    }
};
js 复制代码
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url";
import { defineConfig } from '@rsbuild/core';
import { pluginSass } from '@rsbuild/plugin-sass';
import { pluginVue2 } from '@rsbuild/plugin-vue2';
import { SCSSDeepReplaceFileImporter } from "./importer"

const __dirname = dirname(fileURLToPath(import.meta.url));

export default defineConfig({
  resolve: {
    alias: {
      "@style": resolve(__dirname, "style")
    }
  },
  
  plugins: [
    pluginVue2(),
    pluginSass({
      sassLoaderOptions: {
        additionalData: (content) => content.replace(/\/deep\//g, '::v-deep '),
        sassOptions: {
          importers: [
            SCSSDeepReplaceFileImporter([
              {
                url: "~@style/test.scss",
                path: resolve(__dirname, "style/test.scss"),
                cacheName: "test.scss"
              }
            ])
          ],
        },
      },
    }),
  ],
});

此方法成功将预设的独立scss 文件中的/deep/ 替换成::V-deep ,项目成功运行。但是HMR模式下发现会一直运行hot reload。因为.cache 文件写入时会触发HMR的内容更新,导致一直循环运行hot reload。要解决这个问题,需要在Rsbuild 配置中将.cache 文件夹从HMR的文件监听列表中排除:

js 复制代码
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
  tools: {
    rspack: {
      watchOptions: {
        ignored: /\.cache/
      }
    }
  },
});

这样会出现一个小问题:更改目标scss文件时不会触发HMR,只有引入此scss 文件的文件内容保存时才会触发。

不过对于工程迁移来说算是小事。新、旧两个构建器共存期一过,此importer就可以取消,直接将目标文件的/deep/ 语法修改即可。

插曲

远离Node Sass,要不然你的生活会变得不幸!!!

在发现自定义importer的方案之前,一直找不到好的方式解决此问题。曾经一度想采用升级node-sass@8 然后修改 Sass implementation的方式进行升级。

但是!但是!但是!node-sass@8在我的电脑上一直安装失败。我打定主意,不能让这坨垃圾继续祸害世人,一定要改,所以才出现本文。

相关推荐
Achieve前端实验室2 小时前
【每日一面】如何解决内存泄漏
前端·javascript·面试
小肚肚肚肚肚哦2 小时前
🎮 从 NES 到现代 Web —— 像素风组件库 Pixel UI React 版本,欢迎大家一起参与这个项目
前端·vue.js·react.js
y***03172 小时前
Node.js npm 安装过程中 EBUSY 错误的分析与解决方案
前端·npm·node.js
听风说图2 小时前
Figma Vector Networks: 重新定义矢量图形编辑
前端
用户4099322502122 小时前
Vue3计算属性与侦听器的核心差异是什么?如何快速选对使用场景?
前端·ai编程·trae
九年义务漏网鲨鱼2 小时前
【Agentic RL 专题】五、深入浅出Reasoning and Acting (ReAct)
前端·react.js·大模型·智能体
爱泡脚的鸡腿2 小时前
uni-app D3实战(小兔仙)
前端
嬉皮客3 小时前
Gird布局详解
前端·css
烛阴3 小时前
C#常量(const)与枚举(enum)使用指南
前端·c#