DartSassがなかなか辛かったので
Gulpを修正してみた

DartSassがなかなか辛い

以前、「【Sass】@importを@useに置き換えてみる《FLOCSS対応》」というLibSassからDartSassへの移行について記事を書きました。

無事@import記法から@use@forward記法に移行できたのですが、この記事の内容で数ヶ月使ってみたところ、正直前より使いにくくて辛くなってきました。

ざっくり何が使いにくいかというと、

  • Glob使えない
  • 名前空間めんどい
  • コンパイル遅い

などなど。ファイル数が少ない時は耐えれますが、複数のCSSファイル生成したり細かくパーシャルで分けたりって時はけっこう辛いです・・・。

いつまでもGulpでがんばってるから使いにくいのかなーとか考えましたが、「Webpackなら問題なくDartSassれてる」みたいな情報も調べてみて特にない様子。調べ方が悪いのかもしれないし、WebpackがGulpほど苦労がないから誰も記事にしないのかもですけど。

とりあえず現状は引き続きGulpでやっていくつもりなので、自分なりに解決方法探って対応してみました。

Glob使えない問題

gulp-sass-glob@useに対応してないので使えなくなりました。
代わりにgulp-sass-glob-use-forwardというプラグインがあるみたいなのでそれを使います。

jakerambo/gulp-sass-glob-use-forward: gulp task to allow importing directories in your SCSS

https://github.com/jakerambo/gulp-sass-glob-use-forward

インストールします。

$ yarn add -D gulp-sass-glob-use-forward

Gulpファイルに下記のように追記します。

import gulp from 'gulp'
import sass from 'gulp-dart-sass'
import sassGlob from 'gulp-sass-glob-use-forward'

function buildCss() {
  return gulp.src('assets/sass/**/*.scss')
  .pipe(sassGlob()) //ここで使う
  .pipe(sass())
  .pipe(gulp.dest('assets/css'))
}

そうすると@use@forwardでGlobが使えるようになります。

@use "global/*";
@use "foundation/*";
@use "layout/*";
@use "object/component/*";
@use "object/project/*";
@use "object/utility/*";

こんな感じでフォルダ内のファイルをまとめてglobで読み込めるので、いちいちファイル名指定しなくてもいいし楽。全部まとめた_index.scssみたいなファイルも用意する必要ないので便利です。

ただし、1点問題がありました。

子階層からglobを使うとうまく機能しない・・・。

// OKな書き方
@use "../global/variable" as g; 
@use "../global/mixin" as m;
@use "../global/function" as f;

// NGな書き方
@use "../global/*" as *;

下記の記事で同じ問題点の解決方法について情報があったので試してみましたが解決しませんでした。

gulp-dart-sass で glob したい

https://qiita.com/taqumo/items/8f726268f4723202e451

なので、やはり子階層用に_index.scssで下記のようなファイルを用意しておいて、

@forward "variable";
@forward "mixin";
@forward "function";

各ファイルの1行目で読み込むようにするしかないみたいです。

@use "../global" as g;

名前空間めんどいだけでメリットない問題

@useに変えると変数やmixinなどをまとめてパーシャルファイルを読み込んで使う場合、下記のように名前空間が必要になります。

@use "../../global" as g;

.c-hoge{
  color: g.$hoge-color;
}

この書き方にしてしばらく使ってみたんですが、名前空間付けるメリットは今の所マジでみつからないです・・・。この例で言うとg.ってたったの2文字でほんのちょっとの手間ではありますが、たくさん書いてるとこれのせいで作業効率落ちてるんじゃないか?って気持ちになるくらい、変数の前に付けるのが邪魔で仕方ないです。

そんな時はアスタリスク(*)を使うことで省略できます。

@use "../../global" as *;

.c-hoge{
  color: $hoge-color;
}

これで書き味はLibSassの時とほぼ同じになりました。

共通ファイルを読み込ませる時に相対パス書くのめんどい問題

変数やmixinをまとめて使いたいので、global/_index.scssに共通ファイルをまとめています。

global/_index.scss

@forward "variable";
@forward "mixin";
@forward "function";

これを下記のように各ファイルで読み込ませて使っています。

@use "../../global" as *;

この相対パスですが面倒ですね。node_modulesのファイルを使う時も指定が面倒です。

@use "../../../node_modules/swiper/swiper.scss";

これらの相対パス問題についてはgulp-dart-sassincludePathsパラメータで解決します。

mattdsteele/gulp-dart-sass: SASS plugin for gulp

https://github.com/mattdsteele/gulp-dart-sass

gulp-dart-sassをインストールします。

$ yarn add -D gulp-dart-sass

Gulpファイルは下記のように書きます。

import gulp from 'gulp'
import sass from 'gulp-dart-sass'
import sassGlob from 'gulp-sass-glob-use-forward'

function buildCss() {
  return gulp.src('assets/sass/**/*.scss')
  .pipe(sassGlob())
  .pipe(
    sass.sync({
      includePaths: ['node_modules', 'assets/sass'], //パスを指定
      outputStyle: 'expanded'
    }).on('error', sass.logError)
  )
  .pipe(gulp.dest('assets/css'))
}

こんな感じでnode_modulesへのパスと、sassのルートディレクトリをincludePathsで指定しておくと、@useの後の相対パスを省略できます。

// "../../global" みたいになるのを省略可能に
@use "global" as *;

// "../../../node_modules/swiper/swiper.scss" みたいになるのを省略可能に
@use 'swiper/swiper.scss';

これで各所で共通ファイルを使いたい時にいちいち相対パスを気にしなくてよくなるので少しマシになります。
そもそも共通ファイルを読み込むために1行目に書くこと自体がめんどいですが、それはどうしようもなさそうなので我慢します。

複数ファイルを生成する時にコンパイル遅い問題

gulp-dart-sasssass.sync()という書き方ができるようになっています。

import gulp from 'gulp'
import sass from 'gulp-dart-sass'
import sassGlob from 'gulp-sass-glob-use-forward'

function buildCss() {
  return gulp.src('assets/sass/**/*.scss')
  .pipe(sassGlob())
  .pipe(
    sass.sync({  // ← これ
      includePaths: ['node_modules', 'assets/sass'],
      outputStyle: 'expanded'
    }).on('error', sass.logError)
  )
  .pipe(gulp.dest('assets/css'))
}

これは「同期レンダリング」というものらしいですが、DartSassでは同期レンダリングの方が非同期レンダリングよりも高速になるらしいです。同期レンダリングが何かは正直調べてもよくわからないです。以前の記事「【Sass】@importを@useに置き換えてみる《FLOCSS対応》」でfibersというパッケージを使うとパフォーマンスが上がるっていう公式の情報があった件と同じようなことで、とりあえず深く考えずに使ってみることにしました。ちなみにfibersはnode v16以上に対応していないようなので、もう指定する必要はないっぽいです。

とりあえずsass.sync()で書いてみて、若干コンパイルが早くなった気がします。

あとはLibSassの時と同じく、gulp-cachedなどを使って変更があったファイルとその依存関係のあるファイルのみをコンパイルする様にできれば、マシになりそうです。

修正してみます。

修正に必要なパッケージをインストールします。

$ yarn add -D gulp-cached gulp-dependents

Gulpファイルを修正します。

import gulp from 'gulp'
import sass from 'gulp-dart-sass'
import sassGlob from 'gulp-sass-glob-use-forward'
import cached from 'gulp-cached'
import dependents from 'gulp-dependents'

const dependentsConfig = {
  ".scss": {
    parserSteps: [
    /(?:^|;|{|}|\*\/)\s*@(import|use|forward|include)\s+((?:"[^"]+"|'[^']+'|url\((?:"[^"]+"|'[^']+'|[^)]+)\)|meta\.load\-css\((?:"[^"]+"|'[^']+'|[^)]+)\))(?:\s*,\s*(?:"[^"]+"|'[^']+'|url\((?:"[^"]+"|'[^']+'|[^)]+)\)|meta\.load\-css\((?:"[^"]+"|'[^']+'|[^)]+)\)))*)(?=[^;]*;)/gm,
    /"([^"]+)"|'([^']+)'|url\((?:"([^"]+)"|'([^']+)'|([^)]+))\)|meta\.load\-css\((?:"([^"]+)"|'([^']+)'|([^)]+))\)/gm
    ],
    prefixes: ["_"],
    postfixes: [".scss", ".sass"],
    basePaths: []
  }
};

function buildCss() {
  return gulp.src('assets/sass/**/*.scss')
  .pipe(sassGlob())
  .pipe(cached('scss'))
  .pipe(dependents(dependentsConfig))
  .pipe(sassGlob())
  .pipe(
    sass.sync({
      includePaths: ['node_modules', 'assets/sass'],
      outputStyle: 'expanded'
    }).on('error', sass.logError)
  )
  .pipe(gulp.dest('assets/css'))
}

gulp-cachedを使うと、キャッシュが作られて、変更のあったファイルのみ処理を通すようになります。

ただし、これだけだと@use@forward@include meta.load-cssで読み込んだパーシャルファイルを変更して保存したときにはコンパイルされません。依存関係を解決してパーシャルファイルを読み込んでいる元ファイルを辿ってコンパイルするように処理を通す必要があります。

これは@importの時であればすでに解決されていた問題で、gulp-progenyを使えばOKでした。しかし@useではgulp-progenyは対応していません。別の解決方法として下記記事を見つけたのでそのまま使わせてもらいました。

@useまたは@include meta.load-cssで読み込んでるscss(sass)ファイルを更新してもコンパイルされない問題解決

https://qiita.com/yamabero/items/ea4f4c3c1a23a6ed778c

gulp-dependentsというパッケージを使います。

ただし、なぜかこれを使うと途中でgulp-sass-glob-use-forwardが無効化されGlobがエラーになってしまいます。そのためgulp-dependentsの前後にgulp-sass-glob-use-forwardの処理を繋いでいます。

.pipe(sassGlob()) //Globを有効化
.pipe(cached('scss')) //キャッシュ
.pipe(dependents(dependentsConfig)) //@use・@forwardの依存関係を解決(依存元のファイルを処理に通す)
.pipe(sassGlob()) //依存元のファイルのGlobを有効化

あまり良くはない書き方にはなるんでしょうが、こうしないとうまくコンパイルされなかったので仕方なく。
こういうのをちゃんと解決しようと思ったら自分でプラグイン作ったりしないといけないんでしょうね。。。
そんな時間ないんでとりあえずの対応です。

これで変更があったファイルと依存関係のあるファイルだけコンパイルできるようになりました。

1つだけ注意点。

この手法の場合、依存関係を辿った先の親ファイルに必ず共通ファイルも読み込ませることが必要です。

@use "global/*"; // ← 必要ないけど読み込ませる
@use "foundation/*";
@use "layout/*";
@use "object/component/*";
@use "object/project/*";
@use "object/utility/*";

こうしておかないと、例えばglobal/_variable.scssという変数をまとめたファイルで変更して保存した時に、依存関係を辿った先に@useが書かれてるところがない場合、コンパイルされません。

下記でも生成するCSSの結果は同じですが、

@use "foundation/*";
@use "layout/*";
@use "object/component/*";
@use "object/project/*";
@use "object/utility/*";

globalディレクトリ内のファイルの更新は無視されるのでglobal/_variable.scssを更新してもコンパイルが走りません。そのため必要なくてもファイルの変更時に更新させたい場合は@useで読み込ませておくことが必要になります。

最後少しややこしくなりましたが、とりあえずこれで複数ファイルを生成する時でも変更があったファイルのみコンパイルが走るので処理が早くなりました。

@importの時にできていたネストさせてパーシャルをインポートする方法が@useでできない問題

例えば下記のような使いまわしたいCSSがあるとして、

_scope.scss

h1 {
  font-size: 30px;
  font-weight: bold;
  // ...
}

h2{
  // ...
}

h3 {
  // ...
}

p{
  // ...
}

@importの時は下記のように書いていました。

.p-hogehoge{
   @import 'scope'
}

この書き方でコンパイルされたCSSは下記です。

.p-hogehoge h1 {
  font-size: 30px;
  font-weight: bold;
  // ...
}

.p-hogehoge h2{
  // ...
}

.p-hogehoge h3 {
  // ...
}

.p-hogehoge p{
  // ...
}

これは下記のように@useに変えてもうまくいきません。

.p-hogehoge{
   @use 'scope' // エラーになる
}

こういう場合は@include meta.load-cssを使います。

@use "sass:meta";

.p-hogehoge{
   @include meta.load-css('scope');
}

これで@importの時と同じ結果が得られます。

ちなみに一つ上の項目で@include meta.load-cssで読み込んだパーシャルファイルもgulp-dependentsで依存関係を解決されるようになっているので、読み込んだパーシャルファイルを更新した時もコンパイルが走るので問題なく使えます。

さいごに

DartSassで開発するにあたって、不都合に感じた部分を自分なりに修正してみました。
少しマシになった気がします。でもまだまだ以前ほど快適とは言えません。

DartSassは複数人がCSSを触るような規模のチーム開発ではもしかしたら何らかのメリットがあるのかもしれませんが、弊社のようなコーディング〜WordPress化・システム開発までほとんどフロントエンド1人で担当するような環境ではメリットが見つからないです。CSS設計にFLOCSSを使っていることもありますが、@importの時の方がルールもシンプルで他人に説明しやすく、作業に集中できたと感じます。

よくこういう新しい手法が出たときは慣れの問題とか言いますが、今回は慣れとかの問題じゃなく本当に手間が増えた感覚しかないです。

まだ過渡期ではあると思うので、有用なプラグインなど確立した手法がいくつか出てきたら開発効率が良くなっていくと思いますが、今のところ細かい不都合に対して調べて対応してもどこかに懸念点が残りモヤモヤするばかりです。

うー〜ん・・・もう少しいろいろと探ってみて自分なりの正解を見つけます。

記事一覧

RELATED

inoue
TRY

【WordPress】GA4連携の人気記事ランキング機能を自作プラグイン化してみた

inoue
TRY

【netlify】ビルドイメージを更新 [ Ubuntu Xenial 16.04 → Ubuntu Focal 20.04 ]

inoue
TRY

スクリーンショットのAPI「screendot」を使ってみた

inoue
TRY

【howler.js】ストリーミング音源からオーディオビジュアライザーを作る

inoue
TRY

Simple GA Ranking[ver2.1.6]が表示されないので調べてみた

inoue
TRY

【Sass】@importを@useに置き換えてみる《FLOCSS対応》

NEW POSTS

inoue
TIPS

【CSS + JS】テキストの無限ループアニメーション

inoue
TIPS

【Shopify】カートへの遷移を飛ばしてチェックアウトに進むボタンを設置する

inoue
TIPS

【JS】PhotoSwipe v5を使って画像をポップアップ表示する

inoue
TIPS

gulpでjpg・pngからAVIF画像を生成する

inoue
TIPS

【JS】MutationObserverでDOMを監視[PhotoSwipe(v5)+Swiper(v8)連携]

inoue
TIPS

【Google Sheets API】 スプレッドシートのデータをJSONで取得する

ブログ記事一覧
  • HOME
  • TRY
  • DartSassがなかなか辛かったのでGulpを修正してみた