[ gulp]既存のプロジェクトの@import→@useをドキュメントを読みながら書き換える

# 概要

時間が空いたので重い腰をあげた。

@importを@useに書き換えてみました。


# 経緯

2019年10月2日に@importルールが思い通りにならないことを問題視していてこれらの問題を解決する全く新しいモジュールシステムを議論、設計、開発して、Dart Sass 1.23.0でそれが利用可能になったことを発表し、それに伴い、最終的には@importを廃止する方針を発表した。

- Dart SassとLibSassの両方がモジュールシステムのサポートを開始してから1年後、またはDart Sassがモジュールシステムのサポートを開始してから2年後、いずれか早い方（遅くとも2021年10月1日）に、@importと、モジュールを介して行われる可能性のあるグローバルなコアライブラリ関数の呼び出しを非推奨にします。

- この非推奨措置が実施されてから1年後（遅くとも2022年10月1日）には、@importとほとんどのグローバル関数のサポートを完全に終了します。これには、すべての実装のメジャーバージョンのリリースが必要になります。

[Sass公式ブログのFuture Plans](https://sass-lang.com/blog/the-module-system-is-launched#future-plans)

# 環境
Node 12以上
gulp 4

# 準備
1. node-sassのアンインストール
2. gulp-sassの5.0.0とsass（dart-sass）をインストール

```bash
  npm i node-sass sass --save-dev
```

# gulpfileの書き換え

gulp-sass公式を参考にgulpfileを書き換え

```javascript:gulpfile.js
const sass = require('gulp-sass'));
const compiler = require('sass');
sass.compiler = compiler;

↓

const sass = require('gulp-sass')(require('sass'));
```

# fiberについて
gulp-sass5は、Node 16でDart Sassを高速化するためにNode fibersを使用することができなくなりました。

[gulp-sass公式 #Migrating to version 5](https://github.com/dlmanning/gulp-sass#migrating-to-version-5)

## なんで使用してたの？

[dart-sass公式 #JavaScript API](https://github.com/sass/dart-sass#javascript-api)

dart-sassには同期処理の renderSync() と非同期処理の render() があって、デフォルトでは、非同期コールバックのオーバーヘッドのために、 renderSync()は render()の2倍以上の速さになる。このパフォーマンスへの影響を避けるために、render()はfiberパッケージを使用して、同期コードパスから非同期インポータを呼び出すことができるよ。ということでfiberを使用していた。


[node-fibers](https://github.com/laverdet/node-fibers)

しかし、Node fiberは公式にも

「可能な限り使用を避けることを推奨しています。」

「このモジュールのオリジナルバージョンは、サーバ上のJavaScriptが大きく変わっていた2011年初頭のnodejs v0.1.xを対象としていました。それ以降、async/await、Promises、Generatorsが標準化され、エコシステム全体がその方向に向かっています。」と書いてあり、

Update [April 13th, 2021] で、「nodejs v16.0.0以降との互換性がありません。」としている。

[gulp-sass公式 #What about fibers?](https://github.com/dlmanning/gulp-sass#what-about-fibers)

> We used to recommend Node fibers as a way to speed up asynchronous rendering with Dart Sass. Unfortunately, Node fibers are discontinued and will not work in Node 16. The Sass team is exploring its optons for future performance improvements, but for now you will get the best performance from sass.sync().

Dart Sassによる非同期レンダリングを高速化する方法として、Node fiberを推奨してたんだけど、Node fiberはNode 16では動作しないので、今のところsass.sync()が最善。でもSassチームは将来のパフォーマンス向上のための選択肢を模索してますよ。ということで、fiberが使えないかわりにsass.sync()を推奨している。




# sass.sync()について
[gulp-sass公式](https://github.com/dlmanning/gulp-sass#usage)

sassの同期レンダリング。syncを足すだけ。

※Gulp4を使用していることを前提として書き換え。

```javascript:gulpfile.js
const gulpsass = require('gulp-sass')(require('sass'));

function sass() {
  return gulp
      .src(SRC + 'sass/**/*.scss')
      .pipe(gulpsass({outputStyle: 'expanded'}).on('error', gulpsass.logError))
```

```javascript:gulpfile.js
const gulpsass = require('gulp-sass')(require('sass'));

function sass() {
  return gulp
      .src(SRC + 'sass/**/*.scss')
      .pipe(gulpsass.sync({outputStyle: 'expanded'}).on('error', gulpsass.logError))
```

# ソースの書き換え

## gulp-sass-globが使えない🥺
今までgulp-sass-globを使用して中身全部呼び出していたので、 ~~めんどい~~ ちゃんと管理しなきゃな。

## import → use
とりあえず、scssファイルの呼び出しの@importを全て@useに変更して、エラーを潰していく。

@importではグローバルに指定されていた変数、mixin、関数などは@useだとカプセル化することで、読み込んだシートのみに適用させるファイルスコープ。

## import url()について

Sassがコンパイル時にファイルをインポートする時の@importとプレーンなCSSのインポートは別物なので、以下はそのまま使用可能

```scss
  @import "http://fonts.googleapis.com/css?family=Droid+Sans";
```
[Plain CSS @imports](https://sass-lang.com/documentation/at-rules/import#plain-css-imports)
> Sass will compile any @imports with the following characteristics to plain CSS imports:
Imports where the URL ends with .css.
Imports where the URL begins http:// or https://.
Imports where the URL is written as a url().
Imports that have media queries.

Sassは以下の特徴を持つ@importをプレーンなCSSの@importにコンパイルします。
- URLが.cssで終わるインポート
- URLが http:// または https:// で始まっているインポート
- URLがurl()として書かれているインポート
- メディアクエリを持つインポート

また、@useや@forwardはimportよりも先に書かないといけない。

## 他のモジュールから変数、関数、mixinにアクセス
@useの場合は呼び出したファイル内でしか、変数とかmixinとかの参照ができない。
@importをuseに変えただけでは動かないので、モジュールごとに呼び出す必要がある。

```scss
// sass/base/_setting.scss
$fontSize: 12px;

// sass/layout/_common.scss
body {
  font-size: $fontSize;
}

-> エラー
Error: Undefined variable.
   ╷
32 │   font-size: $fontSize;
```
書き換える

```scss
// sass/base/_setting.scss
$fontSize: 12px;

// sass/layout/_common.scss
@use "base/setting";// 変数が定義してあるファイルを呼び出し

body {
  font-size: setting.$fontSize;
}
```
@useを使用する場合、読み込んだ変数はグローバルにはならずファイルスコープなので、名前空間を使ってどのファイルの変数か明示する必要がある。関数やmixinも同様。

名前空間はデフォルトだとファイル名だけど、as節を使用して名前空間を指定することも可能

```scss
// sass/base/_setting.scss
$fontSize: 12px;

// sass/layout/_common.scss
@use "base/setting as s";// 名前を定義

body {
  font-size: s.$fontSize;
}

```
*を使用して名前空間を持たないモジュールをロードすることもできる

```sass
// sass/base/_setting.scss
$fontSize: 12px;

// sass/layout/_common.scss
@use "base/setting as *";// 名前を定義

body {
  font-size: $fontSize;
}
```
[@use #Loading Members](https://sass-lang.com/documentation/at-rules/use#loading-members)

> We recommend you only do this for stylesheets written by you, though; otherwise, they may introduce new members that cause name conflicts!

これは自分で書いたスタイルシートに対してのみ行うことをお勧めします。
名前の衝突の原因となる新しいメンバー（変数とかmixinとか）を導入してしまうかもしれません。

## forward
@forwardは、@useでスタイルシートがロードされたときに、そのmixin、関数、変数を利用できるようにします。

これにより、多くのファイルにまたがるSassライブラリを整理することができる一方で、ユーザーは単一のエントリーポイントファイルを読み込むことができます。


変数やmixinのファイルがいっぱいある時は、ファイルの読み込みも増え、名前空間もバラバラになってしまうので、中継としてmixinフォルダの中に_index.scssを作成し、@forwardで各ファイルを呼び出すなどで管理すると便利です。

```scss:mixin/_index.scss
@forward "mixin/mediaquery";//mixin以下にあるファイルをここに記述していく
@forward "mixin/fontsize";
```

```scss:中継用main.scss
@use "mixin";//呼び出しはすっきり
```

```scss:表示用　common.scss
@use "mixin" as mixin;

@include mixin.sp {
  @include mixin.fs(24);
}
```
名前をつけてどのファイルを呼び出しているか明示的に扱う

```scss:表示用　common.scss
@use "mixin/mediaquery" as mixinMedia;
@use "mixin/fontsize" as mixinFontsize;

@include mixinMedia.sp {
  @include mixinFontsize.fs(24);
}
```

### プレフィックスの追加

メンバーは、それらが定義されているモジュールの外部で意味をなさない可能性があるので、@forwardで転送した全てのメンバーにプレフィックスを追加するオプションがあります。

```scss:mixin/error.scss
$noError: #DDD;

@mixin error {
  background-color: #fdd;
}
```

```scss:中継用main.scss
@forward "mixin/error" as test-*;

```

```scss:表示用　common.scss
@use "error";

.error {
  @include error.test-error;
}
```

### 一部だけを転送、一部除いて転送

hideは、リストされたメンバーは転送されず、その他のメンバーは転送されることを意味します。
showは、指定されたメンバーだけが転送されることを意味します。

```scss
@forward "mixin/error" hide error;// mixin error以外全て転送
@forward "mixin/error" show $noError;// $noErrorだけを転送
```



## ビルトインモジュール

エラーの出るコード例

```scss
font-size: ($size / $base) * 1rem;
```

エラー

```scss
Using / for division is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($size, $base)

More info and automated migrator: https://sass-lang.com/d/slash-div
```

[Breaking Change: Slash as Division](https://sass-lang.com/documentation/breaking-changes/slash-div)

冒頭の説明を読むと、

sassは/を分割として扱うべきか、セパレータとして扱うべきかを判断するけどややこしいからmath.div()を使ってってことらしい。

mathはビルトインモジュールなので、呼び出しが必要。

[Built-In Modules](https://sass-lang.com/documentation/modules)

書き換え

```scss
@use 'sass:math';//mathモジュールの使用を宣言

font-size: math.div($size, $base) * 1rem;
```

# 終わり
とりあえず、基本的な書き換えは完了。