tl;dr
- sass-loaderでfibersを使うのはコンパイル速度を向上させるため
- ただしfibersはnode16には対応していないし、おそらく今後も対応されることはなさそう
- node 16以上で使うならsass-loaderで
options.sassOptions.fiber = false;
を設定してfibersを無効化する必要がある
用語について
現在npmで sass
として公開されているパッケージはdart製のsassとなっています。旧来使われていたnode製のsassは node-sass
として公開されています。
ここでは区別を分かりやすくするため、dart製のsassを dart-sass
、 node製のsassを node-sass
と呼び分けています。
本文
そもそもsass-loaderで何を設定しているのでしょうか。sass-loaderでdart-sassを利用する際はだいたい下記のように設定しているかと思います。
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// ...
use: [
`// ...
{
loader: `sass-loader`,
options: {
implementation: require('sass'), // ここで `dart-sass` を読み込んでいる
sassOptions: {
fiber: require(`fibers`), // 大体の人がなんとなく一緒に読み込んでいる
},
},
},
],
},
],
},
// ...
};
まず implementation
プロパティについてはsass-loaderのドキュメント に書いてあるとおりですが簡単にまとめると
implementation
を設定しない場合、 node-sassとdart-sassの両方インストールされている場合、自動的に後者が読み込まれるimplementation
を設定することで明示することができる
の2点かとおもいます。この「両方がインストールされている場合」というのは、プロジェクト内の package.json
の devDependencies
などに追加されている場合以外にも他のパッケージの依存に入っている場合も含まれるので、基本的には明示したほうが良いでしょう。
では、なぜ dart-sass
を利用する場合に fiber
プロパティにfibersパッケージを指定しているのでしょうか。
node-sassにもdart-sassにも render()
と renderSync()
というAPIがあります。これはどちらもscssからcssへコンパイルするための関数ですが、前者が非同期に実行され、後者は同期的に実行されます。sass-loaderでは render()
が利用され非同期的にscssファイルがコンパイルされることになります。
しかし、 dart-sassでは非同期にに実行する render()
が非同期実行のオーバーヘッドのため renderSync()
より2倍近く遅くなってしまいます。これを避けるために、利用されるのがfibresです。
fibersはもともとnode.jsに非同期に関数を実行する仕組み(Promiseやasnyc/await)がなかった頃に開発されたものです。同期関数を非同期で実行することができる Promise
に相当する機能があります。(他にもGeneratorに相当する機能もありますがここでは割愛します。)
速度の早い renderSync()
関数を非同期でするために、このfibersを利用する設定が前述した webpack.config.js
です。ただしfibersは作者により使用を避けるべきとされており、node 16への対応もされていません。
まとめ
つまり記事の冒頭でまとめたように、 node 16 を利用するのであればfibersを利用することはできず、コンパイル速度の低下を受け入れざるを得ません。sass-loaderではdart-sassの場合に renderSync()
を利用するIssueが立っているのでもしかするとsass-loader側で対応がなされるかもしれません。