Update Goals#

  • node-sass -> sass (dart-sass)
  • Minimize impact, avoid updating other dependency versions unless necessary
  • Based on the above two conditions, see if we can upgrade the node.js version

Reasons to Abandon node-sass#

Current Project Dependency Versions#

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

Update Strategy#

node.js#

Webpack officially doesn't provide the maximum node version supported by webpack 3, and even if webpack officially supports it, related webpack plugins may not. Therefore, whether the node version can be updated can only be determined through testing. Fortunately, although this project's CI/CD runs on node 12, I've been using node 14 for daily development, so I'll take this opportunity to upgrade the node version to 14.

webpack, sass-loader#

The webpack version is currently in a "ticking time bomb" state of not updating unless necessary. Based on the current webpack 3 limitation, the maximum supported sass-loader version is ^7 (sass-loader's 8.0.0 version changelog explicitly states that version 8.0.0 requires webpack 4.36.0).

If sass-loader@^7 in the project supports using dart-sass, then there's no need to update sass-loader, and consequently no need to update webpack; otherwise, webpack would need to be updated to 4, and then determine the sass-loader version accordingly.

So does it support it or not? I found this package.json snippet on the webpack official documentation page introducing sass-loader:

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

This proves that at least sass-loader@7.2.0 already supported dart-sass, so the webpack version can stay at ^3, and sass-loader can temporarily stay at version 7.0.3. If there are issues later, it can be updated to the latest version 7.3.1 in the ^7 range.

dart-sass#

I couldn't find the maximum sass version supported by sass-loader@^7. GitHub Copilot confidently told me:

Official documentation quote:

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

Suggestion:

  • If you need to use a higher version of sass, please upgrade to sass-loader 8 or higher.

However, I couldn't find any trace of this text on the internet. Moreover, the last version in sass's ~1.26 range is 1.26.11, not 1.26.5. According to common npm versioning principles, releases that only change the patch version while keeping the major and minor versions the same generally only contain bugfixes without breaking changes. It's unlikely that updating from 1.26.5 to 1.26.11 would suddenly become incompatible with sass-loader 7, so this is more likely an AI hallucination or limited training data.

Out of caution, I ultimately decided to use the last version of sass 1.22 mentioned in the webpack official documentation, which is 1.22.12.

Analysis Complete, Let's Update#

Step 1: Uninstall node-sass, Install sass@^1.22.12#

npm uninstall node-sass
npm install sass@^1.22.12

Step 2: Update webpack Configuration (Optional)#

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
+            options: {
+                // In fact, this line doesn't need to be added in most sass-loader versions, sass-loader can automatically detect whether it's sass or node-sass
+                implementation: require('sass')
+              },
            },
          },
        ],
      },
    ],
  },
};

Step 3: Batch Replace /deep/ Syntax with ::v-deep#

Because the /deep/ syntax was deprecated in 2017, /deep/ became an unsupported deep selector. node-sass, with its excellent error tolerance, could continue to provide compatibility, but dart-sass doesn't support this syntax. So we need to batch replace the /deep/ syntax with ::v-deep, which, although abandoned in Vue's subsequent RFC, is still effectively supported to this day.

# Something like this, using VSCode's batch replace also works
sed -i 's#\s*/deep/\s*# ::v-deep #g' $(grep -rl '/deep/' .)

Step 4: Fix Other Sass Syntax Errors#

During the migration, I found some non-standard syntax in the project. node-sass, with its excellent robustness, silently forced parsing, while dart-sass can't handle this rough work. Therefore, these syntax errors need to be manually fixed based on compilation errors. I encountered two types:

// Extra colon
.foo {
-  color:: #fff;
+  color: #fff;
}

// :nth-last-child without specified number
.bar {
-  &:nth-last-child() {
+  &:nth-last-child(1) {
      margin-bottom: 0;
  }
}

Pitfalls#

::v-deep Styles Not Taking Effect#

After updating dependencies, everything seemed fine at first glance, so I pushed to the test environment. Less than a day later, a colleague called me - the ::v-deep deep selector wasn't working?

Trying my luck, GPT gave the following answer:

In the Vue 2 + vue-loader + Sass combination, this syntax is correct, provided that your build toolchain supports the ::v-deep syntax (such as vue-loader@15 and above + sass-loader).

Although I still haven't verified why updating to vue-loader@15 is required to use ::v-deep syntax, after updating vue-loader, the ::v-deep syntax did work. While writing this article, I found some clues that might explain this issue:

  1. The vue-loader version 14 official documentation simply doesn't have examples of ::v-deep syntax. This example was only added after vue-loader 15.7.0 was released.
  2. Someone mentioned in a vue-cli GitHub Issue comment:

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


    And vue-loader only added @vue/component-compiler-utils to its dependencies in version 15.0.0-beta.1, and didn't update its @vue/component-compiler-utils version to the required ^3.0.0 until vue-loader 15.7.1.

Can we upgrade to vue-loader 16 or even 17? No. The vue-loader v16.1.2 changelog clearly states:

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

vue-loader 14 -> 15 Breaking Changes#

When migrating vue-loader from 14 upward, running directly without modifying the webpack configuration will encounter Vue syntax recognition issues. Specifically, even though .vue file naming uses correct and valid syntax, the compiler just won't recognize it during build/development, reporting syntax errors. vue-loader officially has a migration document that needs attention.

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()
    // ...
  ]

Additionally, in my project, I needed to remove the babel-loader for .vue files in the webpack configuration:

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

Final Update Status#

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

All other dependency versions remain unchanged

References#