とある社内プロジェクトでnode-sassからsass(dart-sass)に移行した話

:::note info
viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方は[こちら](#一緒に二次元業界を盛り上げていきませんか)まで。
:::

## :writing_hand_tone1: 経緯
Mac(Apple M1)でsassのコンパイルを行おうとしたところエラーが・・・
どうやらnode-sassはC/C++言語で書かれたプログラム(ネイティブコード)であり、x64アーキテクチャ(Intel Mac または Windows)向けのバイナリしか提供しておらず、M1チップ(ARMアーキテクチャ)だと動かないとのこと。
そのため、ネイティブコードではなくJavaScriptのみで動作するsass(dart-sass)に移行することに。

## :triangular_flag_on_post: 移行手順
1. node-sass → sass(dart-sass) への差し替え
2. ビルド設定の修正
3. 移行用のコマンド実行
4. 手動でエラー修正
5. コンパイル実行
6. 表示確認

## :page_facing_up: 手順詳細
### 1. node-sass → sass(dart-sass) への差し替え
```bash
// node-sassのアンインストール
npm uninstall node-sass

// sass(dart-sass)のインストール
npm install --save-dev sass
```

### 2. ビルド設定の修正
このプロジェクトでは社内唯一(？)いgruntでコンパイルを行っているため、gruntfile.jsを以下のように修正。
(gruntも早めにViteまたはWebpackに移行せねば・・・)
```diff_javascript: gruntfile.js
- const sass = require('node-sass');
+ const sass = require('sass');
```

### 3. 移行ツール(sass-migrator)を使って非推奨のコードを更新
node-sassからsass(dart-sass)に移行するための[公式ツール(sass-migrator)](https://sass-lang.com/documentation/cli/migrator/)があるため使用。

1. sass-migratorをインストール
    ```bash
    npm install -g sass-migrator
    // または
    npm install -D sass-migrator
    ```
    <br>
2. 移行用のコマンド実行
    ```bash
    // コマンド
    npx sass-migrator <移行設定> <対象ファイルのパス>

    // 移行設定
    //   color: 従来のカラー関数を新しいカラースペース互換関数に変換 ※今回は該当エラーなしのため不使用
    //   module: @import → @use に変換(スコープ名も付与してくれる)
    //   division: / での除算を math.div() に変換
    // オプション
    //   --migrate-deps(省略形 -d): @use、@forward、@import で読み込まれている先のファイルも変換。
    //   --dry-run(省略形 -n): 変換対象ファイルをリスト表示。（※変換は実行されない）    
    //   --verbose(省略形 -v): 追加情報をコンソールに出力。--dry-run と併用した場合、変更箇所を表示。
        
    // 実行したコマンド例
    npx sass-migrator module division -d ./assets/scss/common.scss
    ```

    #### 【問題発生その1】 読み込み時にループが発生
    移行コマンドを実行すると以下のエラーが・・・
    ```
    Fatal error: Error: Module loop: this module is already being loaded.
    ```
    確認すると確かにループが発生する書き方になっていた・・・

    ▽修正前
    ```scss:_settings.scss
    @use "assets/scss/_functions";

    $color-blue: functions.hsb(200, 90, 90);
    ```
    ```scss:_functions.scss
    @use "assets/scss/_settings"; // ここでループが発生

    @function hsb($h-hsb, $s-hsb, $b-hsb) { ...省略 }
    ```

    そのため、以下のように `_settings.scss` は読み込み専用 & `_functions.scss`内の関数を使って変数定義したい箇所は別ファイル(`_settings-color.scss`)として書き出すことに。(`_functions.scss`は変更なし)

    ▽修正後
    ```diff_scss:_settings.scss
    - @use "assets/scss/_functions";
    -
    - $color-blue: functions.hsb(200, 90, 90);
    ```
    ```diff_scss:_settings-color.scss
    + @use "assets/scss/_functions";
    + 
    + $color-blue: functions.hsb(200, 90, 90);
    ```

    #### 【問題発生その2】 一部 `@import` → `@use` への移行に失敗
    以下のエラーが発生。
    ```
    Error: The migrator has found multiple possible migrations for file:///home/***/***/assets/scss/_mixins.scss, depending on the context in which it's loaded.
    Migration failed!
    ```
    確認すると、相対パスのためそれぞれ同じファイルを読み込んでそうに見えるが、ファイル名が同じだけで別ファイルを読み込んでいる箇所があった。

    ▽修正前
    ```scss:assets/scss/common.scss
    @import "_mixins"; // assets/scss/_mixins.scss を読み込んでいる
    ```
    ```scss:assets/scss/base/font-awesome/font-awesome.scss
    @import "_mixins"; // assets/scss/base/font-awesome/_mixins.scss を読み込んでいる
    ```
    そのため、ひとまずそれぞれルート相対パスに変更することでエラー解消

    ▽修正後
    ```diff_scss:assets/scss/common.scss
    - @import "_mixins";
    + @use "assets/scss/_mixins";
    ```
    ```diff_scss:assets/scss/base/font-awesome/font-awesome.scss
    - @import "_mixins";
    + @use "assets/scss/base/font-awesome/_mixins";
    ```

    これで再度移行用のコマンドを実行したところ、無事に完了！
    <br>
3. sassコンパイル実行 & エラーログをファイル出力
    sassコンパイルを実行したところ、移行ツールだけだとカバーできない箇所があるようでまだエラーが出ていた・・・

    まだまだエラーがあるようで、コンソールだと確認しづらいので一旦エラーログをファイルに出力してそちらで確認することに。
    ```bash
    npm run grunt > sass_log.txt 2>&1
    ```
    <br>
4. 手動でエラー修正
    移行ツールでカバーできない箇所は手動で修正。
    今回の場合、以下4種類のエラーが出ていた。
    1. `/` での除算を `math.div()` に変換
        ```diff_scss
        // 例)
        - .btn { width: 500 / 750 * 100vw; }
        + .btn { width: math.div(500, 750) * 100vw; }
        ```
        対象箇所を洗い出すために使ったVSCodeの正規表現
        ```
        \(([^()]+)\s*/\s*([^()]+)\)
        ↓
        math.div($1, $2)
        ```
        ※ 例) (10px / 2) → math.div(10px, 2) に置換可能
        ※ VSCodeの正規表現を使う場合、`aspect-ratio: 16 / 9;` のように意図してない記述にもヒットしないよう注意
        ※ 例) 10px / 2 にも対応しようとすると関係ないところにヒットしまくるのでこちらでおおまかにつぶした後は` / `で検索して都度目視確認
        <br>
    2. `@use`の追記が必要 または `@use` に変更されてるもののスコープ名が付与されてない箇所の修正
        ```diff_scss
        // 例)
        + @use "assets/scss/_settings";
        ```
        or
        ```diff_scss
        // 例)
        - .lead-accent { color: $color-orange; }
        + .lead-accent { color: settings-color.$color-orange; }
        ```
        <br>
    3. 相対パスをルート相対パスに変更 ※必須ではない
        ```diff_scss
        // 例)
        - @use "../../_settings";
        + @use "assets/scss/_settings";
        ```
        <br>

5. コンパイル実行
    ```bash
    npm run grunt

    > grunt

    Running "dart-sass:target" (dart-sass) task
    Successfully compiled 0 stylesheet with source map.

    Done.
    ```
 
    無事にコンパイル通った！！！ :tada:
    <br>
6. 表示確認
    コンパイル通ったものの、表示崩れなど発生してないかブラウザで確認。（大事）

## :speech_balloon: やってみた感想
一番しんどかったのは「`/` での除算を `math.div()` に変換」
置換パターンがいくつかあり、正規表現で拾いきれないパターンもあったため、目視確認が必須で大変だった・・・

とはいえ移行ツール(sass-migrator)のお陰もあってなんとか移行できて一安心！！

もし同じようにsass移行に迫られた人は、よければ参考にしてみてください！



# 一緒に二次元業界を盛り上げていきませんか？
株式会社viviONでは、フロントエンドエンジニアを募集しています。

https://vivion.jp/recruit/

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。