node-sass 迁移至 dart-sass 踩坑实录

本文最后更新于:2025年7月4日 下午

更新目标

  • node-sass -> sass ( dart-sass )
  • 减少影响面,非必要不更新其他依赖的版本
  • 在前两条基础上,看看能否提升 node.js 的版本

抛弃 node-sass 的理由

项目依赖版本现状

  • node@^12
  • vue@^2
  • webpack@^3
  • vue-loader@^14
  • sass-loader@^7.0.3
  • node-sass@^4

更新思路

node.js

webpack 官方并没有提供 webpack 3 支持的最高 node 版本,且即使 webpack 官方支持,webpack 的相关插件也未必支持。因此 node 版本能否更新就只能自己试。好在尽管这个项目的 CI/CD 跑在 node 12,但我日常都在用 node 14 开发,因此顺势将 node 版本提升至 14。

webpack、sass-loader

webpack 的版本目前处于非必要不更新的定时炸弹状态,基于现有的 webpack 3 限制,所支持的最高 sass-loader 版本就是 ^7 ( sass-loader 在 8.0.0 版本的更新日志中明确指出 8.0.0 版本需要 webpack 4.36.0)。

如果项目中 sass-loader@^7 支持使用 dart-sass 就可以不更新 sass-loader,也就不必更新 webpack 版本;反之,就需要同步更新 webpack 至 4,再视情况定下 sass-loader 的版本。

那么到底支不支持呢?我在 webpack 官方文档介绍 sass-loader 的页面找到了这样一段 package.json 片段

{
  "devDependencies": {
    "sass-loader": "^7.2.0",
    "sass": "^1.22.10"
  }
}

这证明起码在 sass-loader@7.2.0 这一版本就已经支持 dart-sass 了,因此 webpack 版本可以停留在 ^3,而 sass-loader 暂时停留在 7.0.3 版本,如果后续有问题可以更新到 ^7 版本中最新的 7.3.1 版本。

dart-sass

sass-loader@^7 所支持的最高 sass-loader 我并没有查到,Github Copilot 信誓旦旦地告诉我

官方文档引用:

sass-loader@^7.0.0 requires node-sass >=4.0.0 or sass >=1.3.0, <=1.26.5.

建议:

  • 如果需要使用更高版本的 sass,请升级到 sass-loader 8 或更高版本。

但事实上,我并没有在互联网上找到这段文本的蛛丝马迹。并且在 sass 的 ~1.26 版本中最后一个版本是 1.26.11 而非 1.26.5,根据常见的 npm 版本号原则,major version 和 minor version 不变,只改变了 patch version 的发版一般只有 bugfix 而没有 breaking change,不至于从 1.26.5 更新到 1.26.11 就突然不支持 sass-loader 7 了,因此更可能是 AI 幻觉或者是训练数据受限。

出于谨慎考虑,最终决定采用 webpack 官方文档中提到的 sass 1.22 的最后一个版本,也就是 1.22.12。

分析完成,动手更新

第一步,卸载 node-sass,安装 sass@^1.22.12

npm uninstall node-sass
npm install sass@^1.22.12

第二步,更新 webpack 配置(非必须)

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
+            options: {
+                // 事实上,这一行在大部分 sass-loader 版本中不用加,sass-loader 能自动检测本地是 sass 还是 node-sass
+                implementation: require('sass')
+              },
            },
          },
        ],
      },
    ],
  },
};

第三步,批量替换 /deep/ 语法为 ::v-deep

因为 /deep/ 写法在 2017 年被弃用 ,/deep/ 变成了不受支持的深度作用选择器,node-sass 凭借其出色的容错性能够继续提供兼容,但 dart-sass 则不支持这种写法。于是需要将 /deep/ 语法批量替换成 ::v-deep 写法,这种写法虽然在 vue 的后续 rfc 被放弃了,但直至今日依然在事实上被支持。

# 大概就是这么个意思,用 vscode 的批量替换其实也行
sed -i 's#\s*/deep/\s*# ::v-deep #g' $(grep -rl '/deep/' .)

第四步,修复其他 sass 语法错误

在迁移的过程中,我发现项目中有一些不规范的写法,node-sass 凭借出色的鲁棒性不吭一声强行解析,而 dart-sass 则干不了这粗活。因此需要根据编译时的报错手动修复一下这些语法错误,我这里一共遇到两种。

// 多打了一个冒号
.foo {
-  color:: #fff;
+  color: #fff;
}

// :nth-last-child 没指定数字
.bar {
-  &:nth-last-child() {
+  &:nth-last-child(1) {
      margin-bottom: 0;
  }
}

踩坑

::v-deep 样式不生效

依赖更新完后看了两眼好像是没问题,就推测试环境了。结果一天没到就被同事 call 了,::v-deep 这种深度作用选择器居然没有生效?

抱着试一试的态度,GPT 给了如下回答

Vue 2 + vue-loader + Sass 的组合下,这种写法是正确的前提是你的构建工具链支持 ::v-deep 语法(如 vue-loader@15 及以上版本 + sass-loader)。

虽说我依然没有查证到为什么更新 vue-loader@15 才能使用 ::v-deep 语法,但对 vue-loader 进行更新后,::v-deep 语法确实生效了。在撰写本文时,我找到了些许蛛丝马迹,可能能解释这一问题。

  1. vue-loader 在 14 版本的官方文档就是没有 ::v-deep 写法的示例,这一示例一直在 vue-loader 15.7.0 版本发布后才被加入

  2. vue-cli 的 Github Issue 评论区中有人提到

    ::v-deep implemented in @vue/component-compiler-utils v2.6.0, should work after you reinstall the deps.

    而 vue-loader 在 15.0.0-beta.1 版本才将 @vue/component-compiler-utils 加入到自己的 dependencies 中,并直到 vue-loader 15.7.1 中才将其 @vue/component-compiler-utils 的版本号更新到满足要求的 ^3.0.0

那能否升级到 vue-loader 16 甚至 17 版本呢?不行,在 vue-loader v16.1.2 的更新日志中明确写道

Note: vue-loader v16 is for Vue 3 only.

vue-loader 14 -> 15 breaking change

vue-loader 从 14 往上迁移时,不修改 webpack 配置直接跑会遇到 vue 语法不识别的问题。具体表现为 .vue 文件命名都是正确有效的语法,但构建开发时编译器就是不认,报语法错误。vue-loader 官方有一份迁移文档,需要注意一下。

ERROR in ./src/......
Module parse failed: Unexpected token(1:0)
You may need an appropriate loader to handle this file type.
// ...
import path from 'path'
+const VueLoaderPlugin = require('vue-loader/lib/plugin')

// ...

  plugins: [
+    new VueLoaderPlugin()
    // ...
  ]

除此之外,在我这个项目中需要额外移除 webpack 配置中针对 .vue 文件的 babel-loader

{
  test: /\.vue$/,
  use: [
-    {
-      loader: 'babel-loader'
-    },
    {
      loader: 'vue-loader',
    }
  ]
}

最终更新情况

  • node@^12 -> node@^14
  • vue-loader@^14 -> vue-loader@^15
  • node-sass@^4 -> sass@^1.22.12

其余依赖版本维持不变

参见


node-sass 迁移至 dart-sass 踩坑实录
https://zhul.in/2025/07/05/node-sass-migration-to-dart-sass/
作者
竹林里有冰
发布于
2025年7月5日
更新于
2025年7月4日
许可协议