LibSass から Dart Sass への乗り換え

Ususama で LibSass から Dart Sass への乗り換えを試験したのでメモしておきます。

経緯

こちらの記事を拝見して自分も乗り換えないと、と思った次第。

特に Ususama では Scss のコンパイラとして gulp-sass を利用していますが、現時点(2020/11/9)では gulp-sass はデフォルトだと node-sass を使用しています。

Node-sass is a library that provides binding for Node.js to LibSass, the C version of the popular stylesheet preprocessor, Sass.

sass/node-sass: Node.js bindings to libsass

node-sass は内部的には LibSass を使用しているため、 Dart Sass を使用するならば切り替えが必要になります。

その他にも色々手を加えたいところはあるのですが、一気に変更するとデバッグが大変なことになるのでできることから少しずつ……という気持ちで、 Dart Sass への切り替えをやってみたいと思います。

背景

全てはこの記事ですね。2020/10/26にタイトルの通り、 LibSass が非推奨として宣言されました。

LibSass, Dart Sass

そもそも LibSass や Dart Sass とは、というところも軽く触れておきます。

(比較項目)LibSassDart Sass
言語C++製Dart製
1.0.0リリース日2012/10/162018/3/27
備考普及率高公式推奨

「Dart Sass の方が後発」「公式は Dart Sass 推奨」というところを押さえておけば良いかと思います。

この他、 Dart Sass の機能についてはSass: Feature Watch: CSS Imports and CSS Compatibilityに詳しく書かれています。

  • プレーンな css のインポートをサポート
  • css の min(), max() をサポート
  • メディアクエリでの範囲指定

Dart Sass には以上のような機能が盛り込まれています。

しかし、一番大きいのは以下の点ではないでしょうか。

  • @import から @use, @forward

分割したscssファイル、パーシャルを読み込む際に使用していた @import が将来的には廃止予定(2022/10予定)で、代わりに @use を使うように、とのことです。

この他、 lighten(), saturate(), adjust-hue(), map-get() 辺りも変わります。

Dart Sass へ

冒頭に記載した通り、 gulp-sass はデフォルトでは node-sass(LibSass) を使用しているため Dart Sass への切り替えが必要です。

Dart Sass に切り替えるということは、上述の通り @import から @use, @forward への書き換えておくべきでしょう。

ということで、冒頭の乗り換え試験に至るわけです。

Dart Sass への切り替え

まずは gulp-sass のコンパイラを切り替えるところから。

You can choose whether to use Dart Sass or Node Sass by setting the sass.compiler property. Node Sass will be used by default, but it’s strongly recommended that you set it explicitly for forwards-compatibility in case the default ever changes.

gulp-sass – npm

gulp-sass はコンパイラの切り替えが可能とのことなので公式に則って Gulpタスク を書き換えます。

const gulp = require('gulp');
const plumber = require('gulp-plumber');
const notify = require('gulp-notify');
const sass = require('gulp-sass');
sass.compiler = require('sass'); // 追記
const autoprefixer = require('gulp-autoprefixer');
const Fiber = require('fibers'); // 追記

const scss = () => {
    return gulp.src(
            `./src/scss/**/*.scss`,
            {
                ignore: [
                    `./src/scss/assets/bootstrap/bootstrap.scss`,
                    `./src/scss/assets/bootstrap/honoka/bootstrap/**`,
                    `./src/scss/assets/bootstrap/honoka/honoka/**`
                ]
            })
            .pipe(plumber({
                errorHandler: notify.onError({
                    message: 'Error: <%= error.message %>',
                    title: 'sass'
                })
            }))
            .pipe(sass({
                fiber: Fiber, // 追記
                outputStyle: 'compressed'
            }).on('error', sass.logError))
            .pipe(autoprefixer({
                cascade: false
            }))
            pipe(gulp.dest('./dist/'));
};

Scss をコンパイルするタスクの中で、 sass.compiler = require('sass'); を追記することでコンパイラを切り替えています。

注意する点としては、 Dart Sass のコンパイラのパッケージ名が sass であるということ。若干紛らわしいです。あと、高速化のために fibers も追加しています。

    "devDependencies": {
// 略
        "gulp-sass": "^4.1.0",
        "sass": "^1.28.0", // 追加
        "fibers": "^5.0.0", // 追加
        "gulp-autoprefixer": "^7.0.1",
// 略
    },

以上を踏まえて package.json にも sassfibers を追加します。

コンパイラの切替はこれでOKです。

コードの書き換え

続いてはコードの書き換え。

@import から @use, @forward へ

基本的には @import@use に書き換えていくのですが、変数や mixin をさらに他のscssファイルで使用したい場合は @forward にします。

例えば以下のように src/scss/layout/_l-main.scsssrc/scss/global/_index.scsssrc/scss/global/_scss_variables.scss という場合。

src/scss/global/_scss_variables.scss

@charset "utf-8";

@use "sass:color";
@use "var" as g;

$us-main-color_d: color.scale(g.$us-main-color, $lightness: -8%);
$us-main-color_l: color.scale(g.$us-main-color, $lightness: 8%);
$us-accent-color: color.adjust(g.$us-main-color, $hue: 150deg);
$us-link-color: color.adjust(g.$us-main-color, $hue: 210deg);
$us-link-v-color: color.scale($us-link-color, $lightness: -15%);
$us-link-ha-color: color.scale($us-link-color, $lightness: -8%);
$us-background-position: center center;
$us-eyecatch-height: 60vh;
$us-line-height: 1.6;
$us-letter-spacing: 0.15rem;

変数の宣言のパーシャル。

src/scss/global/_index.scss

@charset "utf-8";

@forward "var"; //変数(ejs等と共通)
@forward "scss_variables"; //変数
@forward "mixin"; //mixin
@forward "../assets/bootstrap/bootstrap"; //bootstrap
@forward "../../../node_modules/brand-colors/dist/latest/scss/brand-colors.latest"; //ソーシャルメディアなどのブランドカラー
@use "../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome"; //以下4つFontAwesome
@use "../../../node_modules/@fortawesome/fontawesome-free/scss/solid";
@use "../../../node_modules/@fortawesome/fontawesome-free/scss/regular";
@use "../../../node_modules/@fortawesome/fontawesome-free/scss/brands";

global/_scss_variables.scss 等を読み込んでいるパーシャル。

src/scss/layout/_l-main.scss

@charset "utf-8";

@use "../global" as g;

.l-main {
    background-color: g.$us-bg-color;
    color: g.$us-color;
}

最後に、 global/_index.scss を読み込んで実際に global/_scss_variables.scss の変数を使用しているパーシャル。

この例のように、実際に global/_scss_variables.scss変数を使用するscssファイルにインポートされる中間のscssファイル(ここでは global/_index.scss )では @use ではなく @forward を使います

global/_index.scssglobal/_scss_variables.scss@use で読み込んでしまうとファイルスコープがそこで閉じてしまうので、 layout/_l-main.scss からは見えなくなってしまいます。

その他の留意点としては以下。

  • @use ... as hoge; で名前空間の指定が可能
    • layout/_l-main.scss では @use "../global" as g;g という別名を付けているので g.$us-bg-color というように変数名も名前空間付きで指定
  • _index.scss はディレクトリ名のみで読み込むことが可能
    • layout/_l-main.scss では @use "../global" as g;global/_index_.scss を読み込んでいます

この辺りの感覚は個人的には ECMAScript の import, export に近いのではないかと思います(export は吐き出し元の方に記述しますが)。(循環参照になっていなければ)複数個所で同じものを読み込んでも良いとか。

ファイルスコープや名前空間の考え方はよりメンテナンス性を向上させることになると思う(@import だとどこで変数等が宣言されているのか追いづらかった)ので良いと思います。

その他の個人的に引っかかった留意点は以下。

  • ファイルの読み込みルールが変わったので、今まで FLOCSS の foundation ディレクトリにあった変数や mixin の塊は新たに global ディレクトリを作成してそちらに移動
  • global/_index_.scss が Bootstrap の読み込みと自身の変数の読み込みを行っていて、最終的には同じ名前空間で展開されるので $link-color が名前的にバッティングしました
    • 回避するため、自身の変数群にはプレフィックスとして us- を最初に付けるようにしました
  • 今まで fs でファイルの内容を読み込み→scss拡張子で書き出ししている部分があったのですがその部分を削除
    • css を直で読み込みできるようになったため
  • .stylelintrc.json"ignoreAtRules" に追加

.stylelintrc.json

// 略
    "rules": {
        "at-rule-no-unknown": [
            true, {
                "ignoreAtRules": [
                    "function","if","else if","else","for","each","include","mixin","content","use","forward" // "use","forward" を追加
                ]
            }
        ],
// 略
    },
// 略

ビルトインモジュール

続いては lighten(), saturate(), adjust-hue(), map-get() 辺りのSass関数をビルトインモジュールに書き換えます。

上述 global/_scss_variables.scss で既に書き換えを実施していますが、再掲。

src/scss/global/_scss_variables.scss

@charset "utf-8";

@use "sass:color";
@use "var" as g;

$us-main-color_d: color.scale(g.$us-main-color, $lightness: -8%);
$us-main-color_l: color.scale(g.$us-main-color, $lightness: 8%);
$us-accent-color: color.adjust(g.$us-main-color, $hue: 150deg);
$us-link-color: color.adjust(g.$us-main-color, $hue: 210deg);
$us-link-v-color: color.scale($us-link-color, $lightness: -15%);
$us-link-ha-color: color.scale($us-link-color, $lightness: -8%);
$us-background-position: center center;
$us-eyecatch-height: 60vh;
$us-line-height: 1.6;
$us-letter-spacing: 0.15rem;
  • lighten(), darken(): color.scale() に置換。第一引数が色、第二引数のキーは明度($lightness)・彩度($saturation)の指定、値は指定の値(マイナス値だと darken(), desaturate())
    • color.lightness(), color.saturation() でも良さそう
  • adjust-hue(): color.adjust() に置換。第二引数のキーを色相($hue)指定

以上のようになっています。なお、ビルトインモジュールの使用にはモジュールの読み込みが必要で、上述の色系の場合は @use "sass:color"; を冒頭で指定する必要があります。

src/scss/_plugins/articlesns/_articlesns.scss

@charset "utf-8";

$sns-array: (
    Twitter: (
        color: $bc-twitter,
        darkness: 8%
    ),
    Facebook: (
        color: $bc-facebook,
        darkness: 12%
    ),
    Hatena: (
        color: $bc-hatena-bookmark,
        darkness: 8%
    ),
    Line: (
        color: $bc-line,
        darkness: 5%
    ),
    Pocket: (
        color: $bc-pocket,
        darkness: 12%
    ),
    Feedly: (
        color: $bc-feedly,
        darkness: 8%
    )
);

//SNSシェアボタンを付与
.c-btn_news {
    &,
    &:link,
    &:visited {
        color: darken($bg-color, 4%);
        .fa.fa-fw {
            margin-right: 0.1rem;
        }
    }
    &:hover,
    &:active,
    &:focus {
        text-decoration: none;
        color: $bg-color;
    }
    @each $key, $val in $sns-array {
        &#{$key} {
            background-color: map-get($val, color);
            &:hover,
            &:active,
            &:focus {
                background-color: darken(map-get($val, color), map-get($val, darkness));
            }
        }
    }
}

map-get()map.get() に変更。こちらも使用のためには @use "sass:map"; を冒頭で宣言する必要があります。

これで一通り書き直していって、元通り動くことを確認。

参考

契機

gulp-sass

fibers

@import から @use, @forwward へ

ビルトインモジュール

その他

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。