サポート終了迫る!Sassの@importを@useと@forwardに移行しよう!

こんにちは!
サービスデザイン部のムライシです!

今回、私はサービスデザイン部の活動で、弊社プロダクトのSassの@importを@useと@forwardに移行する取り組みを行いました!
(私のチームでは、フロントエンドの整備及びスキルの底上げという役務を担っております。)

今回はその過程や移行方法をまとめます。

なぜ@useと@forwardに移行する必要があるのか?

そもそも、なぜ@importを@useと@forwardに移行する必要があるのかご存知ですか?
実は、Sassの@importは廃止になることが決定しているのです!
Sass公式によると、@importは2021年10月1日に非推奨、遅くとも2022年10月1日に@importのほとんどのグローバル関数のサポートが終了になります。
参照:Sass公式サイト

@importがサポート終了になる理由はSass公式サイトによると下記があげられていました。(ムライシによるざっくり和訳なので、原文を読みたい方はSass公式サイトをご覧ください。)

  • 1つのスタイルシートで定義されたものはすべて、その後に@importで読み込まれたすべてのスタイルシートで使用できるため、特定の変数、mixin、または関数(総称して「メンバー」と呼ばれる)が最初に定義された場所を特定することがほぼ不可能だったため
  • 使用するメンバーを定義するすべてのスタイルシートを明示的に読み込むことを選択した場合でも、スタイルシートは読み込まれるたびに最初から再ロードされるため、CSSが重複し、奇妙な副作用が発生するため
  • 同じ名前のmixinや変数を複数のスタイルシートで定義すると、上書きされて思い通りのスタイルにならない可能性が常にあったため、ユーザーは定義したすべてのものに長くて扱いにくい名前空間を手動で追加する必要があったため
  • ライブラリの作成者は、それを使用するユーザーがプライベートヘルパーにアクセスしないようにする方法がなく、混乱と下位互換性の問題を引き起こしていたため
  • @extendを使用する際、作成者が明示的に拡張することを選択したセレクタだけでなく、スタイルシートの任意の場所のセレクタに影響を与える可能性があるため

@importと@useと@forwardについて

@importに代わる新しいモジュールシステムとして、@useと@forwardがあります。

@importとは
@importではメンバー(変数、mixin、関数)が書かれているファイルを@importで読み込んだあとに、メンバーを使用したファイルを@importで読み込むとファイルを飛び越えて参照することができました。
これはSass公式の挙げていた問題点のうちの1つですね。

@useとは
@useは@importと同様に別のファイルを読み込みます。
@useで読み込んだメンバーは読み込んだスタイルシートのみで使えます。
オブジェクト指向プログラミングではカプセル化と呼びます。
@importと大きく違うのは、メンバーはファイル単位で名前空間を持つということです。
名前空間とは、メンバーがどのファイルから呼ばれたのかを定義するものです。
メンバーを呼び出す際は<namespace>.<variable><namespace>.<function><namespace>.<mixin>という形式で呼び出す必要があります。

デフォルトの名前空間はファイル名となります。
@use "../settings" as ●●;と書くことで名前空間を自由に変えることができます。
@use "../settings" as *;と書くと名前空間をなくすことができます。
名前空間をなくすと変数やmixinを使用するときに変数名やmixin名の前に名前空間を記述する必要がなくなります。

メンバーの名前を-(ハイフン)または_(アンダースコア)で始めるものにすれば、そのメンバーは他ファイルからは参照できず、宣言されたファイル内でしか使えなくなるプライベートメンバーとなります。

@forwardとは
@useでは名前空間を生成し、読み込まれたファイルでのみメンバーを使用することができました。
しかし、そうなるとCSSフレームワークなどでメンバーが参照できなくなる状況が生まれます。
メンバー定義元のファイルを、使いたいファイルから直接読み込みにいかないといけませんでしたが、@forwardを使えばそのファイルを読み込んだファイルにもメンバーが見えるようになります。
ファイルを中継するようなイメージです。

@forwardする際にプレフィックスをつけることもできます。
_test.scssで@forward './aaa' as heading-*;と書くと、_c-hoge.scssでinclude時に@include test.heading-hoge;という感じでプレフィックスをつけられます。

対応手順

1. LibSass(node-sass)からDartSass(sass)に移行
2. @useと@forwardでscssファイルを読み込む

LibSass(node-sass)を使っている場合はDartSass(sass)に移行

現在、@useと@forwardはDartSassでのみ使用できます。
DartSass??となったそこのあなた!
実はSassはRuby Sass、LibSass、DartSassの3種類あります。
現在、Sass公式はDartSassの使用を推奨しています。

gulp-sassを使用している場合、デフォルトのコンパイラはLibSass(node-sass)になっています。
Gulpタスクにsass.compilerをsassにする指定がない場合LibSassを使用していることになります。

まずnpmでsassをインストール

npm i sass --save-dev

高速化のためにfibersというパッケージをインストール
fibersはDartSass公式のREADMEで紹介されているパッケージです。

npm i fibers --save-dev

ScssをコンパイルするGulpタスクの中で、DartSass(sass)に切り替える

gulp$.sass.compiler = require('sass');

注意
node-sass-●●のライブラリがnode-sassに依存しており、DartSassだとうまく動かない場合があります。
DartSass切り替えのリリース前に動いているか確認するとよいです。
今回切り替えた際、node-sass-package-importerというパッケージが正常に機能しなくなりました。
その場合は動いていない箇所のパスをしっかり明記することで解決できます。

scss

@import "~normalize.css"; //これを
@import "~normalize.css/normalize"; //このように

@useと@forwardでscssファイルを読み込む

運用するプロダクトごとに管理のしやすさなどが違うので、プロダクトにあった読み込み方法を検討してください。
今回の取り組みでは2つの方法を考えてみました。

ちなみに私が今回@useと@forwardへの移行をしたサイトは、変数とmixin、コンポーネントなどはそれぞれ単体のファイルで定義されており、app.scssでまとめて読み込むという構成をしていました。
その構成を前提として説明していきます。

プロダクトの考え方に合わせたおすすめの対応方法

スピード感を重視するプロダクトにはこれ!

スピード感を重視するプロダクトには、
mixinファイルをすべて@forwardで読み込んだまとめファイルを新しく作成し、
そのファイル1つだけをディレクトリ内のコンポーネントごとに切り出しているscssファイルで読み込む

方法が良いです。
私の担当しているサービスではこの方法を起用しました。

メリット:共通のmixinを使用しているscssファイルで読み込ませるファイルが1つで良いので、新しくscssファイルを作成したときに記述がラクで効率が良い

デメリット:とりあえず全部を読み込めばそれをどこでも使用できるというのは、Sass公式の@importを廃止して@useと@forwardに移行する意図と反している

①事前準備

@useと @forwardを使うとstylelintに引っかかる場合があります。
引っかかった場合は、stylelintのルールを定義しているファイルに@useと@forwardを追記する必要があるので、stylelintまわりを管理している方に追記をお願いすると良いでしょう。

②mixinファイルをforwardで読み込んだまとめファイルを作成する

PCとSPで使用しているmixinが異なる場合はファイルを分けると良いです。

下記のようなファイルになります。

mixin-pc.scss

@charset "utf-8";

@forward "../scss/mixin/m-animation";
@forward "../scss/mixin/m-badge";
@forward "../scss/mixin/m-btn";
@forward "../scss/mixin/m-c-breadcrumbs";
@forward "../scss/mixin/m-label";
@forward "../scss/mixin-pc/m-icon";
@forward "../scss/mixin-pc/m-l-limitter;

③変数とmixinを利用している全てのscssファイルにmixinまとめファイルと変数ファイルを読み込む

このように追加します。

scss

@charset "utf-8";

@use "../settings" as *; //変数ファイル
@use "../../../../core/scss/mixin-pc" as *; //mixinまとめファイル

 .c-breadcrumbs {
   @include c-breadcrumbs($font-color-link, $font-color-default);
}

ひたすらおまじないのように書いていきます。

作業での注意点・困ったこと

@useでは同じファイル名のものは名前空間が同じになりエラーが出る
その場合、as hogeのように独自の名前空間をつける必要があります。
一回一回判断して名前空間をつけていると手間になるため、as *で名前空間をなくしておくとimportのときと同じように変数やmixinを使うことができます。

scss

@use "../../../../core/scss/mixin-pc" as *;

別ファイルのスタイルを@extendする場合は、@useでそのファイルを読み込む必要がある
別ファイルに書かれているクラスセレクタ、プレースホルダーセレクタのスタイルを@extendで使用する場合は、そのスタイルが書かれているファイルを使用するファイルで@useで読み込む必要があります。
(@useはカプセル化されるため、別ファイルのスタイルは共有されない)

extend-test.scss

@charset "utf-8";

.test { //プレースホルダーセレクタ(%)も使用可能
  display: block;
  padding: 20px 0;
  border-radius: 50px;
  background-color: #ff3232;
  color: #fff;
  text-align: center;
  font-weight: bold;
  font-size: 1.8rem;
}

guide.scss

@use "./extend-test";

.guide-action-btn {
  @extend .test;
}

@extendは名前空間を記述する必要がありません。
例えば、@use "./extend-test" as aaa;と名前空間をつけて、@includeのときと同じように@extend aaa.test;とするとエラーになります。
名前空間をつけてもas *で名前空間をなくしても@extendするときの記述は@extend .test;だけでOKなので、読み込むときはシンプルに@use "./extend-test";で良いでしょう。

同じファイル内であればクラスセレクタもプレースホルダーセレクタも今まで通り@extendで使用できます。

スピードよりもSass公式の意図を尊重するプロダクトにはこれ!

スピードよりもSass公式の意図を尊重するプロダクトには、
コンポーネントごとに切り出しているscssファイル内でincludeしている共通のmixinがある場合、そのmixinが使われているファイルのみを@useで読み込む
方法が良いです。

メリット:使用しているmixinのファイルだけを読み込むので、構造は綺麗。(しかしmixinは@includeされるまでcssには書き出されないため、読み込みスピードは関係ない)
使用しているmixinがどのファイルに書かれているのかがパッとわかる。

デメリット:新しいscssファイルを作成する際、使用するmixinが記述されているファイルを探して1つ1つ書く必要があり手間になる。
不要になったスタイルが出た際、不要になったmixinファイルを読み込んでいるファイルから読み込みの記述を1つ1つ削除する必要があり手間になる。

###①下準備
方法1と同じです。

②各scssファイルでincludeされているmixin名で検索して対象のmixinファイルを探し出す

③@useでmixinが使われている各scssファイルに読み込むようにする

このようにファイル内で使われているmixinの分のみ読み込みます。
使われているmixinの種類が多いほど@useで読み込むファイルも多くなります。

例:product_name/sp/scss/component/_c-media.scss

@use "../settings" as *;
@use "../../../../core/scss/mixin/m-c-root" as *;
@use "../../../../core/scss/mixin/m-c-media" as *;
@use "../../../../core/scss/mixin/m-icon" as *;
@use "../../../../core/scss/mixin/m-c-af-info" as *;

最後に

いかがだったでしょうか?
プロダクトごとに構成は違うので、今回の方法がベストではない場合もあるかもしれませんが、参考になれば幸いです。

参考文献

  • https://sass-lang.com/documentation/at-rules/use (最終閲覧日:2021年7月29日)
  • https://sass-lang.com/documentation/at-rules/forward (最終閲覧日:2021年7月29日)
  • https://sass-lang.com/documentation/at-rules/import (最終閲覧日:2021年7月29日)
  • https://blog.rhyztech.net/sass_use_and_forward/ (最終閲覧日:2021年7月29日)
  • https://labor.ewigleere.net/2020/11/09/scss_transfer_libsass_dartsass/ (最終閲覧日:2021年7月29日)
  • https://kojika17.com/2020/05/next-generation-sass-module-system.html (最終閲覧日:2021年7月29日)

GMOメディアでは仲間を募集しております

当社は、「For your Smile,with Internet.」という企業理念のもと、多くの笑顔と感動を生み出していくべく、インターネットメディア媒体を多ブランドでサービス展開しています。
募集内容について詳しくはコチラをご覧ください