使用 grunt-sass 实现 dart-sass 与 node-sass 一键切换

解读

面试官真正想考察的是“在不改任务名、不改文件结构、只改配置”的前提下,让团队能在 dart-sass(官方推荐、无 C++ 编译依赖)node-sass(老项目遗留,速度更快但 Node 版本锁死) 之间自由切换。
国内真实场景里,node-sass 常因 node-gyp、Python、VS Build Tools 下载失败 导致 CI 挂掉;而 dart-sass 又可能因 @use / @forward 新语法 把老代码打爆。因此“一键切换”必须满足:

  1. 零任务脚本改动;
  2. 依赖按需安装,不硬编码;
  3. 切换后 sourceMap、输出压缩、includePaths 完全一致;
  4. 能在 package.json scripts 里用 grunt sass:dart / grunt sass:node 显式指定,也能通过 环境变量 默认回退。

知识点

  • grunt-sass 只是壳,真正干活的是 implementation 参数;
  • node-sass 与 sass(dart-sass) 的 API 差异renderSync 选项、importer 签名、sourceMap 格式;
  • 动态 require 与 try/catch:防止未安装对应包时进程崩溃;
  • grunt.config.merge模板字符串 实现多 target;
  • cross-env 或 npm config 在 Windows / macOS / Linux 下统一注入环境变量;
  • ci 镜像源:国内淘宝源、华为源对 node-sass 二进制文件加速;
  • lockfile 策略:yarn resolutions / npm overrides 锁定 sass 版本,避免同事误升级。

答案

  1. 安装依赖(按需)
# 方案 A:dart-sass
npm i -D grunt-sass sass
# 方案 B:node-sass
npm i -D grunt-sass node-sass
  1. Gruntfile.js 关键片段
module.exports = function(grunt) {
  const engine = process.env.SASS_ENGINE || 'dart'; // 默认 dart
  let impl;
  try {
    impl = engine === 'node'
      ? require('node-sass')
      : require('sass');
  } catch (e) {
    grunt.fail.fatal(`请先安装 ${engine === 'node' ? 'node-sass' : 'sass'} 包`);
  }

  grunt.initConfig({
    sass: {
      options: {
        implementation: impl,
        sourceMap: true,
        outputStyle: 'compressed',
        includePaths: ['node_modules']
      },
      dist: {
        files: [{
          expand: true,
          cwd: 'src/scss',
          src: '**/*.scss',
          dest: 'dist/css',
          ext: '.css'
        }]
      }
    }
  });

  grunt.loadNpmTasks('grunt-sass');
  grunt.registerTask('default', ['sass']);
};
  1. package.json 脚本
"scripts": {
  "sass:dart": "cross-env SASS_ENGINE=dart grunt sass",
  "sass:node": "cross-env SASS_ENGINE=node grunt sass"
}
  1. 一键验证
npm run sass:node   # 使用 node-sass
npm run sass:dart   # 使用 dart-sass

拓展思考

  • 性能对比:dart-sass 同步编译 1 万行代码比 node-sass 慢 30%,可通过 grunt-concurrent 把 sass 任务拆到子进程,或启用 sass 的 async render(需改写 grunt-sass 源码);
  • 语法兼容:老项目用 /deep/>>> 时,dart-sass 会报错,可写 自定义 importer 把旧语法实时替换为 ::ng-deep
  • 二进制缓存:CI 环境把 ~/.npm/node-sass 目录缓存到 阿里云 OSS / 腾讯云 COS,减少 90% 下载时间;
  • 未来迁移:官方已停止维护 node-sass,团队可定 里程碑版本号,在 package.json 中声明 "node-sass": "npm:sass@^1.77.0" 实现 无感重定向,最终彻底移除 node-sass。