😳レガシー案件のgulp-sassをDart Sassへ移行した

この記事は[「レガシー」を保守したり、刷新したりするにあたり得られた知見・ノウハウ・苦労話 by Works Human Intelligence Advent Calendar 2022（カレンダー2）](https://qiita.com/advent-calendar/2022/works-hi-3)19日目の記事です。

# ⚡ モチベーション

- Gulpの利用をやめる（npm-scriptsで賄える）
- gulp-sass（中身はLibSass）をDart Sassへ更新する


# 😳 移行の理由
弊社のプロジェクトにて古いバージョンのGulp（gulp-sass : 4.1.0）を使って構築されたSassコンパイル環境があり、Node.jsも古いバージョン（14.15.1）が必要で、Node.jsのバージョンをプロジェクト毎に固定する必要があり、別の作業者が同様の環境を構築するのに手間がかかるものであった。

加えて下記のサイトにある通り、**gulp-sassは非推奨となって久しい環境**と言える。

https://devlog.atlas.jp/2021/09/14/3974

> そんなSass環境ですが、現在LibSassは非推奨

> 2020年10月に唐突にLibSassが非推奨となりました。

> 公式としてはLibSassではなくDart Sassを使うことを推奨している、ということは数年前から言われていました。

> GulpでSassを使用するためにgulp-sassを使っていますが、これはもともと node-sassを使用してコンパイルするような仕組み

> node-sassはLibSassをベースに作られている

加えてSassのコンパイルくらいならタスクランナーであるGulpを使わずとも**npm-scriptsでビルド出来るだろう**と考え、Gulp自体も使うことを止めました。

# 🔧 移行の方法

## １）package.jsonの設置と必要なライブラリのインストール

まずはSassコンパイルテスト用のフォルダを作り、下記内容で作成したpackage.jsonを設置。

```json:package.json
{
  "name": "sass-compiler",
  "version": "1.0.0",
  "scripts": {
    "sass": "sass sass/:css/ --style compressed --no-source-map --watch",
    "build": "stylelint --fix css/**.css & postcss css/**.css --use postcss-combine-media-query autoprefixer --dir css --no-map"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.4",
    "postcss": "^8.4.20",
    "postcss-cli": "^10.1.0",
    "postcss-combine-media-query": "^1.0.1",
    "postcss-scss": "^4.0.3",
    "sass": "^1.49.9",
    "stylelint": "^14.6.1",
    "stylelint-config-recess-order": "^3.0.0",
    "stylelint-config-recommended-scss": "^8.0.0",
    "stylelint-scss": "^4.2.0"
  }
}
```

### 使うライブラリ
|ライブラリ|概要|
|:-----------|:------------|
|PostCSS|コンパイルされたstyle.cssに追加で処理を行うためのもの|
|Autoprefixer|ベンダープレフィックスを付与する|
|postcss-combine-media-query|メディアクエリでスタイルをまとめる|

### Autoprefixerのデフォルト設定
設定がない場合のデフォルトは下記（今回はデフォルトのまま使う）。
- &gt; 1% … 1%以上のシェアがあるブラウザ
- last 2 versions … 最後の2バージョンのブラウザ
- Firefox ESR … 最新のFirefox ESR版

### npm-scripts：sass（作業時に起動）
```terminal
$ sass sass/:css/ --style compressed --no-source-map --watch
```
- --style compressed（出力形式を圧縮）
- --no-source-map（ソースマップを作成しない）
- --watch（ファイルを保存した際に自動コンパイル）

### npm-scripts：build（納品時に起動）
```terminal
$ stylelint --fix css/**.css
```
- Stylelintについては後述
```terminal
$ postcss css/**.css --use postcss-combine-media-query autoprefixer --dir css --no-map
```
- --use XXX YYY（PostCSSのプラグイン「postcss-combine-media-query」と「autoprefixer」を使うの意）
- --dir css（出力先フォルダ名）
- --no-map（ソースマップを作成しない）
- PostCSSを使ってベンダープレフィックス付与しメディアクエリでスタイルをまとめる

```terminal
$ npm i
```
で必要なライブラリをインストール。
node_modulesフォルダに必要なライブラリが入り、package-lock.jsonファイルが作られる。

既存のプロジェクトからsassフォルダをコピーし配置、VSCodeを起動しNPM スクリプト【sass】を実行する。
**gulp-sassで使っていた.scssファイルをコンパイルした際、おそらくエラーが大量に出るのでそれを潰していく作業が必要**になる。

## ２）Stylelintについて
「Stylelint」はCSSの記述ミスを指摘し、可能であればその修正まで行ってくれるプラグイン。
また、ルールを決めておきそのルールに従った記述でないとエラーを出してくれる（ルールは自由にカスタマイズ可能）。
今回はSassコンパイル時にミスの修正とプロパティ順の変更を行うよう設定する。

VSCodeで拡張機能パネルを開き`@id:stylelint.vscode-stylelint`で検索、「Stylelint（vscode-stylelint）」をインストール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95865/af2491cd-035c-45f9-87de-cef61fbddc77.png)

プロジェクト配下のVSCode設定ファイル（.vscode/settings.json）に下記を追加。
```json:.vscode/settings.json
{
  "[scss]": {
    // 保存時に整形
    "editor.formatOnSave": true,
    // SCSSのフォーマッタをPrettierに
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  // stylelintを.scssでも効かせる
  "stylelint.validate": [
    "css",
    "scss"
  ],
  // VSCodeの組み込みリンターを無効化
  "scss.validate": false,
  "css.validate": false,
  // コードアクションを保存時に実行（stylelint --fix）
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": true
  }
}
```

プロジェクト配下にStylelint設定ファイル（.stylelintrc）を下記内容で配置。
```json:.stylelintrc
{
  "extends": [
    "stylelint-config-recess-order",
    "stylelint-config-recommended-scss"
  ],
  "plugins": [
    "stylelint-scss"
  ],
  "ignoreFiles": [
    "**/node_modules/**"
  ],
  "rules": {
    "at-rule-no-unknown": null,
    "scss/at-rule-no-unknown": true,
    "no-descending-specificity": null,
    "no-duplicate-selectors": null,
    "scss/operator-no-unspaced": null
  }
}
```

|extends／plugins|概要|
|:-----------|:-----------|
|stylelint-config-recess-order|プロパティ順のソート|
|stylelint-config-recommended-scss|標準的なstyleのルールを設定|
|stylelint-scss|scssのチェックを可能にするプラグイン[^1]|
[^1]:元々StylelintはCSS用なので、これを入れないと.scssファイルで実行してくれない
これで、VSCodeで.scssファイルを変更、コード保存した際にStylelintが走ってくれる。

## ３）npm-scriptsのおさらい
```package.json
{
...
  "scripts": {
    "sass": "sass sass/:css/ --style compressed --no-source-map --watch",
    "build": "stylelint --fix css/**.css & postcss css/**.css --use postcss-combine-media-query autoprefixer --dir css --no-map"
  },
...
}
```

### npm-scripts：sass
1. `style.scss`の変更 → 保存
2. Stylelint（VSCodeプラグイン）がプロパティ順ソート及びコード修正を行う
3. `sass sass/:css/ --style compressed --no-source-map --watch`（Dart Sassが`XXX.scss`を`XXX.css`としてCSSファイルにコンパイル）

### npm-scripts：build
1. `stylelint --fix css/**.css`（コマンドラインのStylelintが`XXX.css`のプロパティ順ソート及びコード修正を行う）[^2]
2. `postcss css/**.css --use postcss-combine-media-query autoprefixer --dir css --no-map`（XXX.cssに対してPostCSSを使ってベンダープレフィックス付与しメディアクエリでスタイルをまとめる）
[^2]:mixinの仕方に寄ってはコンパイル毎に毎度lintチェックのエラーが発生する可能性があるため、XXX.scssだけでなく成果物であるXXX.cssにもStylelintを適用している

## ４）エラーの解消をしていく
コンパイルするとエラーが出てくるので解消する（記述を新しいものに書き換える必要がある）。
例えばcalcで割り算（除算）を使う箇所。

> Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.
Dart Sass 2.0.0 以降では / による除算は廃止予定なので、代替として math.div を使うよう勧告されている。

使用するscssファイルの行頭に下記を記述し、
```scss
@use 'sass:math';
```

該当箇所を置き換え。
```diff_scss
@function calcvw($num, $width: $sp-design-width) {
-  @return (1vw * $num / $width * 100);
+  @return (1vw * math.div($num, $width) * 100);
}
```
`@import`の`@use`への置き換え（WebFontの`@import`と被るため、変更が必要）
```diff_scss
- @import 'reset';
+ @use 'reset';
```
名前空間の設定
```diff_scss
- @import "variable";
+ @use "variable" as var;
.hoge {
-  color: $color-main;
+  color: var.$color-main;
}
```
分割されたsassファイル内で利用する毎に@useの記述と名前空間の設定が必要になる（一番面倒な所）。
※ `as * `とすると名前空間無しで使えるらしい。LP用などの場合に読み込むscssファイルがmixin1つなどならそれでも良いかも

● 結果
どうしても仕組みの変更に伴う差分は出てくるが、ある程度は仕方がないかと思われ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95865/64fdaed8-8a13-f090-0c3a-b44f940aa2f0.png)
※ 一見変に思えるが、軽量化されているだけで、問題なくWebフォントの読み込みが行われる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95865/1d8d8d85-c959-7c63-6679-02a8c69f598b.png)
※ Dart Sassでは小数点以下の取り扱いが10桁に変わったらしい。特にブラウザのレンダリング結果に問題はないと思われる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95865/738227be-8a06-3c9a-b9ad-49f369d9a73c.png)
※ むしろコンパイル結果は計算不要になっており、正しい挙動に思える

## ５）組み込み
コンパイル結果のCSSが問題ないことを差分などで確認できたら、プロジェクトに組み込む。
下記を削除し、
```
your_project/.node-version
your_project/gulpfile.js
your_project/package-lock.json
your_project/node_modules
```

`your_project/package.json`の中身を今回のものへ入れ替え、
```terminal
$ npm i
```
でインストールし直す。

`npm-scripts:sass`を実行し、動作に問題無いことを確認したら、結果のCSSを本アップしてみて崩れ等無いか確認する。最終的にCSSが完成したら`npm-scripts:build`を実行して仕上げる。

## ６）文字エンコーディング定義について
.scssファイル行頭の
```scss
@charset "utf-8";
```
については、日本語が含まれる場合に勝手に付与されるので不要のようだ。

# 🔗 参考サイト
https://developers.gmo.jp/12920/

https://haniwaman.com/dart-sass/