Sass | Kayo's Melody https://kayosite.com Practice Makes Perfect Sun, 26 Sep 2021 06:34:38 +0000 zh-CN hourly 1 https://cdn.kayosite.com/wp-content/uploads/2021/08/image.jpg?imageMogr2/thumbnail/!64x64r|imageMogr2/gravity/Center/crop/64x64 Sass | Kayo's Melody https://kayosite.com 32 32 115630942 从 Sass Breaking Change: Slash as Division 说起 https://kayosite.com/analysis-of-sass-breaking-change-slash-as-division.html https://kayosite.com/analysis-of-sass-breaking-change-slash-as-division.html#respond Sun, 26 Sep 2021 06:32:50 +0000 https://kayosite.com/?p=3753 最近在修改一个项目的时候,发现了一系列的 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。

]]>
https://kayosite.com/analysis-of-sass-breaking-change-slash-as-division.html/feed 0 3753
SassDoc 详细介绍与最佳实践 https://kayosite.com/sassdoc-introduction-and-best-practices.html https://kayosite.com/sassdoc-introduction-and-best-practices.html#respond Mon, 22 Aug 2016 08:20:00 +0000 https://kayosite.com/?p=3543 SassDoc 是一款专门为 Sass 代码生成注释的工具,通过 SassDoc,开发者可以通过类似 JSDoc 的方式在 Sass 代码上添加注释,然后直接用命令生成文档。最近在处理团队框架 QMUI Web 时,遇到了需要为大量 Sass 方法写文档的问题,因此研究了这个工具,本文将会详细说明 SassDoc 的使用方法以及其中的最佳实践。

基本使用

在 Sass 中,可以使用多行注释 /* xxxx */ 和单行注释 // xxxx 两种注释方法。如文章开头所述,SassDoc 是使用类似 JSDoc 的方式,即在代码中通过注释编写文档内容的方式生成文档,因此 SassDoc 有特定的注释语法:

/// 跨浏览器的渐变背景,垂直渐变,自上而下
///
/// @group 外观
/// @name gradient_vertical
/// @param {Color} $start-color [#555] - 渐变的开始颜色
/// @param {Color} $end-color [#333] - 渐变的结束颜色
/// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位
/// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位
@mixin gradient_vertical($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%){
  background-image: -webkit-gradient(linear, left top, left bottom, color-stop($start-percent, $start-color), color-stop($end-percent, $end-color)); // Safari 4-5, Chrome 1-9
  background-image: -webkit-linear-gradient(top, $start-color $start-percent, $end-color $end-percent);  // Safari 5.1-6, Chrome 10+
  background-image: -moz-linear-gradient(top, $start-color $start-percent, $end-color $end-percent); // Firefox 3.6+
  background-image: -o-linear-gradient(top, $start-color $start-percent, $end-color $end-percent);  // Opera 12
  background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
  background-repeat: repeat-x;
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 and down
}

总结如下:

  • 使用 /// 作为 SassDoc 的注释标识(旧版的 SassDoc 中,使用的是 Sass 的注释方式,但这样这些注释也会被输出到 CSS 代码中,因此最新版的 SassDoc 选择重新定义一个 /// 作为专属的注释方式)
  • /// 中的第一行没有任何标记的文字会被当作 Sass 方法的描述
  • 带有 @name@param 这类标记的会当作对应的注释属性,完整的标记列表可以参考 http://sassdoc.com/annotations/

按照以上的方法,在 Sass 代码上写好了需要的注释,接下来就应该输出文档了。输出文档首先要安装 SassDoc 工具:

npm install sassdoc -g

然后对需要生成文档的 Sass 文件执行如下命令:

sassdoc sassFileName

例如:对 _compatible.scss 执行上面的操作,会直接生成如下的文档页面:

Sassdoc 示例
SassDoc 默认效果示例

如上图各个方法已经根据注释的内容输出对应的文档,并且文档的样式也很完善。至此,就是 SassDoc 的基本使用。

进阶使用

使用非默认主题以及其他选项

如果对默认的样式不满意,也可以使用官网提供的其他主题,在介绍如何使用其他主题时,先要介绍一下 SassDoc 的选项:

  • dest SassDoc 的输出目录,默认为 ./sassdoc
  • exclude 排除某些 Sass 文件,可以使用 * 通配符,类型为数组
  • package 类型为 String 或 Object,该选项可以告知 SassDoc 项目的标题,版本号等信息,默认值为 ./package.json
  • theme 文档的主题,默认为 default
  • autofill 规定那些属性需要尽量自动补全,默认为 ["requires", "throws", "content"]
  • groups 该方法的分组,文档中会根据把同一个分组的方法归类到一起展示,默认为 { undefined: "general" }
  • no-update-notifier 在使用 SassDoc 时(例如执行输出文档的命令),如果当前的 SassDoc 不是最新版本,会有输出提示,这个选项可以控制取消这个提示,默认为 false
  • verbose SassDoc 默认不会输出各个文档的生成进度,如果需要可以把这个选项设置为 true
  • strict 严格模式,默认为 false,开启后则使用一些废弃语法会报错(例如上面提到的旧版中可以使用的多行注释)

可以看出,如果希望使用其他主题,只需要下载对应的主题,并且在 theme 这个选项中进行配置即可,官方的其他主题列表

文件级注解

SassDoc 提供了一个文件级注解的功能,文件级注解与上面的普通注解相似,但是并不是书写在每个方法之上,而是写在文件的开头,它作用是当方法的注解缺少某些属性时,会自动把文件级注解当作缺省值使用。

例如在 _calculate.scss 中,方法的注解中都没有写 group 这个属性,但在文件级注解中有 group 属性,后续生成的文档都会以文件级注解中的 group 值当作自身的值。

代码:

////
/// 辅助数值计算的工具方法
/// @author Kayo 
/// @group 数值计算 
/// @date 2015-08-23
////

/// 获取 CSS 长度值属性(例如:margin,padding,border-width 等)在某个方向的值
///
/// @name getLengthDirectionValue
/// @param {String} $property - 记录着长度值的 SASS 变量 
/// @param {String} $direction - 需要获取的方向,可选值为 top,right,bottom,left,horizontal,vertical,其中 horizontal 和 vertical 分别需要长度值的左右或上下方向值相等,否则会报 Warning。
/// @example
///   // UI 界面的一致性往往要求相似外观的组件保持距离、颜色等元素统一,例如:
///   // 搜索框和普通输入框分开两种结构处理,但希望搜索框的搜索 icon 距离左边的空白与
///   // 普通输入框光标距离左边的空白保持一致,就需要获取普通输入框的 padding-left
///   $textField_padding: 4px 5px;
///   .dm_textField {
///     padding: $textField_padding;
///   }
///   .dm_searchInput {
///     position: relative;
///     ...
///   }
///   .dm_searchInput_icon {
///     position: absolute;
///     left: getLengthDirectionValue($textField_padding, left);
///     ...
///   }
@function getLengthDirectionValue($property, $direction) {
  ...
}

效果:

数值计算方法的效果
文件级注释示例

最佳实践

完全自定义外观

如果你不喜欢 SassDoc 提供的主题,或者本身的文档有一整套样式(例如上面提到的框架有自己的完整官网,因此 Sass 方法的文档也需要配合官网的风格),那么你就需要完全自定义样式。对开发者来说,常用的思路应该是把 Sass 代码中的注释输出为特定格式,例如 JSON,然后页面中通过读取这些数据输出 HTML。

SassDoc 中也提供了一些相关的接口,第一步是把指定的 Sass 文件读取出里面的注解,并输出数组,在此之前,你需要建立一个脚手架,方便你调用这个任务,持续地更新你的文档,例如使用 Gulp,首先在本地目录安装 SassDoc:

npm install sassdoc --save-dev

然后建立一个 Gulp 的 Task:

gulp.task('readToolMethod', false, function(){
  var sassdoc = require('sassdoc');

  sassdoc.parse([
    './qmui/helper/mixin/'
  ], {verbose: true})
  .then(function (_data) {
    // 文档的数据
    console.log(_data);
  });
});

可以看到,会输出数组格式的数据,并把每个方法作为一个 Object,Object 中包含了各个注解属性及其属性值:

SassDoc 解析出的数据格式
SassDoc 解析出的数据格式

接下来,你就可以根据这个数据拼接 HTML 了。

数据分组

SassDoc 中输出的数组数据并没有按不同 Group 把方法归类到不同的数组中,但在拼接 HTML 时,我们一般需要把方法按 group 归类到不同的数组中,方便遍历。这里给出一个方法,把刚刚的数组按 group 拆分成二维数组:

gulp.task('readToolMethod', false, function(){
    var fs = require('fs'),
      sassdoc = require('sassdoc'),
      _ = require('lodash');

  sassdoc.parse([
    './qmui/helper/mixin/'
  ], {verbose: true})
  .then(function (_data) {
    if (_data.length > 0) {
      // 按 group 把数组重新整理成二维数组
      var _result = [],
          _currentGroup = null,
          _currentGroupArray = null;
      for (var _i = 0; _i < _data.length; _i++) {
        var _item = _data[_i];
        // 由于 IE8- 下 default 为属性的保留关键字,会引起错误,因此这里要把参数中这个 default 的 key 从数据里改名
        if (_item.parameter) {
          for (var _j = 0; _j < _item.parameter.length; _j++) {
            var _paraItem = _item.parameter[_j];
            if (_paraItem.hasOwnProperty('default')) {
              _paraItem['defaultValue'] = _paraItem['default'];
              delete _paraItem['default'];
            }
          }
        }

        if (!_.isEqual(_item.group, _currentGroup)) {
          _currentGroup = _item.group;
          _currentGroupArray = []; 
          _result.push(_currentGroupArray); 
        } else {
          _currentGroupArray = _result[_result.length - 1];
        }
        _currentGroupArray.push(_item);
      }
      _result.reverse();

      // 准备把数组写入到指定文件中
      var _outputPath = './qmui_tools.json';

      // 写入文件
      fs.writeFileSync(_outputPath, 'var comments = ' + JSON.stringify(_result), 'utf8');
    }
  });
});

上面演示了如何把数组按 group 拆分为二维数组,并以 JSON 格式写入到文件中,方便拼接 HTML。需要注意的是,@param 这个注解中有一个 default 属性,代表参数的默认值,因此生成 JSON 后会产生一个 default 属性,在 IE8- 下,default 为属性的保留关键字,直接使用会引起错误,因此这里要把参数中这个 default 的 key 从数据里重命名,避免发生这种错误。

重新拼接方法体

在书写文档时,一般需要列出方法体(即完整的方法声明),例如:

onePixelBorder 方法
方法主题展示示例

SassDoc 输出的数据中本身不包含方法体,但是提供了组成方法体需要的数据,下面的方法可以利用一个 SassDoc 的 item 拼接处完整的方法体:

var makeCompleteMethodWithItem = function(item) {
  var result = '',
      itemType = null;

  if (item.context.type === 'placeholder') {
    itemType = '%';
  } else {
    itemType = item.context.type + ' ';
    result = '@';
  }

  result = result + itemType + item.context.name;
  if (item.parameter) {
    result += '(';

    var paraList = item.parameter;
    for (var paraIndex = 0; paraIndex < paraList.length; paraIndex++) {
      var paraItem = paraList[paraIndex];
      if (paraIndex !== 0) {
        result += ', $';
      } else {
        result += '$';
      }
      result += paraItem.name;
      if (paraItem.defaultValue) {
        result = result + ': ' + paraItem.defaultValue;
      }
    }
    result += ')';
  }
  result += ' { ... }';

  return result;
};

SassSDocMeister

最后推荐一款官方的工具—— SassSDocMeister,这个工具可以在线预览 SassDoc 注解的效果,对于刚接触 SassDoc 的用户来说会比较方便。

完整的 Demo 请参考 QMUI WebQMUI Web Demo,如果你觉得这篇文章对你有帮助,欢迎 Star。

]]>
https://kayosite.com/sassdoc-introduction-and-best-practices.html/feed 0 3543