从 Sass Breaking Change: Slash as Division 说起

最近在修改一个项目的时候,发现了一系列的 Sass 的告警——由除号引起的告警:

Sass Division 告警

目录

什么告警?

告警的内容很简单,用 / 作为除法已经在 Dart Sass 2.0.0 中被弃用了,作为一个 Sass 的基础语法,这次弃用属于 breaking change 了,因此目前编译时只是会抛出 warning 而不是 error,否则大量项目都无法正常运行。

研究了一下可以看到,Sass 官方特定用了一整个篇幅的文章,来阐述为何要作出这个修改,主要的原因在于,/ 在 Sass 中同时承担除号以及 CSS 分隔符的作用,例如:

Sass 代码

// 作为除号使用
.test_division {
    border-radius: ceil(28px / 2);
}

// 作为 CSS 分隔符使用
.test_operator {
    font: 12px/1.5 -apple-system, "SF UI Text", "PingFang SC", "Lucida Grande", "Microsoft YaHei", sans-serif;
}

实际编译出的 CSS 代码

.test_division {
    border-radius: 14px;
}

.test_operator {
    font: 8px -apple-system, "SF UI Text", "PingFang SC", "Lucida Grande", "Microsoft YaHei", sans-serif;
}

示例中有两个 /,一个是用作除号,另一个是作为 font 属性中 font-sizeline-height 的分隔符,可以看到作为除号使用的时候,/ 一般不会出现什么问题,但是作为分隔符使用的时候,/ 很容易被重载为除号,实际上要实现分隔符的效果,通常需要这样编写:

.test_operator {
    font: #{12px/1.5} -apple-system, "SF UI Text", "PingFang SC", "Lucida Grande", "Microsoft YaHei", sans-serif;
}

使用了插值(Interpolation)语法,包裹了 12px/1.5,插值的作用是仅解析 Sassscript,把 Sass 变量输出为实际的值,但不会进行运算,如果属性值比较复杂,则会导致编写的时候不大直观。

Sass 本身使用了 complex heuristics 的技术去判断 / 应该作为除号还是分隔符,complex heuristics 是需要回顾当前上下文的内容,来作出判定的,因此对于 Sass 来说存在一定消耗。

综合来说,原有的 / 语法对于开发者会带来一些困扰,尤其是随着 CSS 有更多的属性使用到了分隔符(例如 gridhsl() 等语法),同时对于 Sass 的维护以及编译也会带来一些额外的消耗,所以 Sass 最终决定重新定义除法,新的语法也相当清晰:

@use "sass:math";

.test_division {
    border-radius: math.div(28px, 2);
}

新的语法基于 Sass 的 module 语法,引入了相关的运算模块后,就可以调用 math.div 代替原来的 /,而 / 则只作为分隔符使用。

如何解决告警?

要解决告警,首先要知道为何项目中会出现这个告警,用到了这个语法的项目比较多,但目前只有这个项目出现了告警。

首先这个 breaking change 仅在 Dart Sass 的最新版本中才引入,Node Sass 的版本目前还没有跟上。另外该项目中是使用 "sass": "^1.30.0" 来声明 sass 模块的版本(即 Dart Sass 的 npm 包),重新执行 npm i 会导致安装上新版的 Dart Sass 从而出现告警,因此解决方案也围绕 Dart Sass 版本去处理。

降级 sass 模块

删除 package-lock.jsonnode_modules,把 package.json 中 sass 模块的版本改为 "sass": "~1.32.12",即版本号会少于 1.33.0,这个版本的 Dart Sass 并未引入 slash as division 的 breaking change。

更新业务语法

即按上面提到的方式,把相关的告警内容改为用新的 math.div 语法代替,如果涉及的业务量比较大,更新起来会比较费时。

使用 sass-migrator

sass-migrator 是 Sass 官方推出的迁移工具,方便开发者对原有的业务代码进行最新版本的 Sass 适配。sass-migrator 并不是把相关的旧语法直接替换为最新的语法,而且采用更稳固的方式,把代码安全地更新为符合最新要求的语法,例如上面的例子:

Sass 代码

.test_division {
    border-radius: ceil(28px / 2);
}

sass-migrator 的安装与调用

npm i -g sass-migrator
sass-migrator division test.scss

处理后的 Sass 代码

.test_division {
    border-radius: ceil(28px * 0.5);
}

可以看到,sass-migrator 并没有把原有的 / 修改为最新的 math.div 语法,而是修改为用乘法代替。实际上跟 sass 基于 complex heuristics 进行分析会把分隔符重载为除号一样,迁移工具也无法准确地判断每个 / 的作用,因此 sass-migrator 采用了稳固的方式去适配最新的 Sass 规则。

Dart Sass Vs Node Sass

Node Sass 由于没有引入最新的 Sass 特性,因此并不会出现这个告警,但并不建议使用 Node Sass 代替已经用 Dart Sass 编写的代码,主要是:

  • Dart Sass 已经是官方的首选,无论是新特性还是问题修复,Dart Sass 会有更强的时效性,担心新特性会为业务带来问题可以通过锁包进行控制。
  • Node Sass 实际上已经被官方定义为 deprecated,目前项目会维护一个主要版本,但是维护进度并不确定,并且明确没有计划再为 Node Sass 添加新特性,也不会适配 CSS 的新特性。
  • Node Sass 基于 LibSass 开发,而 LibSass 依赖了的模块安装比较麻烦,尤其是对于 Windows 的用户,它要求用户在 Windows 中必须安装 Python2 和 Visual Studio。

因此出于长期维护的考虑,选用 Dart Sass 也是一个趋势。

Dart Sass on Dart-VM 与 Dart Sass on NPM

目前 Dart Sass 有两种实现,分别是:

  • 基于 Dart-VM 的 Dart Sass
  • 基于纯 JavaScript 的 Dart Sass

根据官方的介绍,单独运行的命令行版本,是基于 Dart-VM 运行的,得益于 Dart-VM 的高性能,这个版本的 Dart Sass 性能非常好,适合用于编写脚本单独编译 Sass 文件。

而 NPM 中的 Dart Sass 则是纯 JavaScript 实现,因此可以很方便地用于前端项目构建。虽然 JavaScript 版本的 Dart Sass 性能比 LibSass 要差一些,但是对于样式代码的编译量来说,区别并不大。

三个版本的 Sass 编译速度对比

上图是 Dart Sass on Dart-VM、Dart Sass on NPM、Node Sass 分别去编译 BootStrap 4 的耗时(来源于 Stack Overflow),可以看出 Dart Sass on NPM(即 Dart Sass JS)比 Node Sass 还要慢很多(大概3倍的耗时),但实际上即使是 Bootstrap 这个体量,也只是2秒的耗时,考虑到官方和社区都逐步迁移到 Dart Sass 了,因此新项目也建议使用 Dart Sass。

本文由 Kayo Lee 发表,本文链接:https://kayosite.com/analysis-of-sass-breaking-change-slash-as-division.html

评论列表

回复

你正在以游客身份访问网站,请输入你的昵称和 E-mail