Reactとともに使ってきたCSS環境の変遷を振り返る

f:id:masino83:20220323135526p:plain

これはアソビュー Advent Calendar 2020の5日目です。

こんにちは、アソビュー!のフロントエンドエンジニア、テックリード井上です。PS5は買えました。蜘蛛の糸でNYをビュンビュン飛んでます。

さて、弊社ではアソビュー!やその他の大後のアプリケーション開発のフロントエンドのUIフレームワークとしてはReactを活用しています。おそらくReact触ってる人は一度は悩むであろう、css環境どうするか問題。今回はこちらについて書きたいと思います。

はじめに

私がジョインしたのは約3年前ですが、そこからフロントの基盤は色々とブラッシュアップを続けており、特にcss環境についてはその場の状況に応じて結果的に色々と乗り換えてきたのでそちらの流れについてまとめてみたいと思います。

ほとんど思い出話に近くなると思います!

第一章: Sass with BEM

時は2017年後半、私がアソビューにジョインする直前くらいに設計、実装されていたasoview.comのフロントでは下記のような構成でwebpackでビルドしているJSとは別にgulpでSassのビルドを行っておりました。

getbem.com

(久しぶりに公式みたらロゴ変わってた)

 

使ってたもの

  • gulp
  • Sass(gulp-sass)
  • BEM

Reactとは別にSassが階層構造を持って画面ごとのscssファイルおよび共通部品は切り出したモジュールとしてscssファイルを分けてimportする形でした。

BEMはスコープの無いCSSの世界で命名ルールを設けることで擬似的にスコープを閉じて衝突しないようにするものです。

 

gist.github.com

 

gist.github.com

導入当初はページ単位でBEMの定義をしている感じでしたが、Reactはコンポーネント単位で共通化したりするためそこの乖離が出てしまい設計が壊れてしまったりしていました。

👍デザイナーとの協業

普通のCSSなのでデザイナーさんがデザインHTMLをBEMで組んで、それをそのままフロントエンジニアが取り込むみたいなことが出来る。
(後述しますが、今はその開発スタイルはほぼ有りません。)

❎コーディング時のストレス

JSファイルとscssファイルでクラスを合わせるのは目検&手作業なので手間がかかり、間違えやすいです。クラス名はただの文字列なので当然サジェストも効きません。
普通に文字列連結などすると見通しが悪くなるのでclassnamesなどを活用するか自前でutil作るなどしないといけない。

gist.github.com

❎BEMとatomicデザインと命名ルールと

BEMは擬似的なスコープなので間違えて別のコンポーネントなどで同じブロック名をつけてしまうような事故も起こりえます。
ページ単位でマークアップするならなんの問題ないですがページのコンポーネントをatomicデザインに従って設計して細分化していくと、命名のルールを厳密にしていかない限り間違ってブロック名が衝突してしまう可能性があります。そもそも毎回ブロックの名前を悩むというのも結構ストレスでした。

パフォーマンス

コンパイルしたcssファイルを画面で読み込むことになりますが、画面の要素が増えれば増えるほどファイルが肥大化してしまい、表示速度に著しく悪影響をもたらします。

🤔感想

マークアップとReact実装を分業したスタイルには適しているが、単純にReact実装の部分だけで見ると良い開発体験とは言えないし、完全にレガシーなスタイルで時代に沿っていないので変えたい。(@2017年)

第二章: css-modules

Reactでコンポーネント化を進めていく中、Sassが独自でモジュール化してたり、JSとscssファイルが別の階層構造でファイルが離れてることで開発体験が著しく損なわれるのがいよいよ辛くなってきたのでwebpackのcss-loaderを使ってcss-modulesを使いディレクトリ構成を工夫してファイルの距離も近づけました。

 

github.com

css-loaderの modules オプションとをtrueにするとSassで定義したクラスをJSでオブジェクトとしてインポートしてclassNameに紐付けることができ、ビルドの結果としてはhashのついたクラス名が吐き出されます

gist.github.com

 

※JSの変数として使われるのでcamel caseにする必要があるのでBEMでは無くなるがBEMのBのスコープがそのままコンポーネントに閉じる感じ。

 

gist.github.com

ファイル構成はこんな感じで同じディレクトリにjs、scssファイルを入れました。

そのタイミングでstyled-componentsについても検討したのですが、デザイナーやフロントエンドにそこまで精通していないメンバーも触ることも多くハードルが高いということもあり、css-modulesでしばらく運用してみました。

🎉コーディングのしやすさ

上記のscssの記述にあるように composes を活用することで拡張が出来ますし(BEMでいうmodifierの代わり)、JSの近くscssファイルが有ることで開発がしやすく、styleをimportしたコンポーネントでスコープも閉じる(BEMでいうBlockの代わりであり、もうBlock名で悩まなくてもいい)のでだいぶストレスが減りました。
また、あくまでscssファイルなのでstylelintやIDEの補完などもなんの問題もなく使えます。
eslint-plugin-cssmodules(not maintainedになってる、、)で使ってないクラス名があったり定義してないクラス名を使ったりしてる場合にエラーにすることもできます。

🎉移行のしやすさ

上述の通りBEMでコンポーネントの単位が整理されてればそのまま書き換えることがスムーズにできます。(BEM形式からBlock消してクラス名をcamel caseにしてmodifierをcomposesにして…という機械的な作業)
また、一応これならまだReact不慣れなデザイナーでもいけますね。

🎉パフォーマンス

コンポーネントとセットでバンドルされるため、React.lazyでdynamic importで読み込む場合は別chunkに分かれて遅延読み込みになるため、初期表示時のファイルサイズを抑えることが出来ます。ページごとに読み込んでいたcssファイルのサイズを純減できるため軽量化できパフォーマンスは向上します。

❎まだ残るストレス

やはりclassnamesなどの活用は見通し良くするためには変わらず必要なことや、相変わらずクラス定義で切り替える形なので切り替えのロジック(jsx)とスタイル定義(scss)は別の箇所にあることは変わらないため、ファイル行ったり来たりして作っていくやりづらさは残ります。(これははるか昔からそうなわけでこの感想自体CSS in JSの存在を前提としたものなわけですが、、)
クラス定義が増えてくるとscssファイルが煩雑になり定義が探しづらくなったり、単純にファイルが長くなって見通しが悪くなり、クラスの設定ミスも発生しがちでした。

🤔感想

scssの資産はそのまま活かしつつ移行でき、スコープは閉じるし、stylelint、eslintのpluginもあり概ね問題なく開発出来ます。
導入事例をあまり見かけないのが気になりどころでしたが、、
CSS in JSにする選択肢も有ったのですが、デザイナーとの協業や教育コストなども考えて一旦css-modulesを踏むことにしました。(@2018)

第三章: CSS in JS (styled-components)

基本的にはcss-modulesで上述の欠点は有りつつも問題なく使えていたのですが、世はすでにCSS in JSが流行りだしており、使っているサイトが少なく、情報も少ないなあとは思っていました。

そんな中、いよいよcss-modulesはメンテナンスモードで非推奨である、みたいな話も出てきたのでcss in jsへの移行を数ヶ月前に検討し始めました。

※一方Next.jsではcss-modulesを標準に見据えている?ような動きもあり、各所で混乱中な現在ではありますね。。流石にcss-modulesがリバイバル見たいなことは無いと思いますが、何が起こるかわからないのがフロントエンド界隈です。。

styled-components vs emotion

比較検討したのはstyled-componentsとemotionです。
機能的にはどちらもやりたいことは満たせるので結構甲乙つけがたかったです。
調べていく中で、できることはほぼ同じながら、思想が結構違うと思いました。

  • styled-componentsの思想
    タグをスタイリングしたコンポーネント=styled-componentsとして扱っていくイメージで、あくまでコンポーネントにスタイルが紐付いているものである。
  • emotionの思想
    JS内で書けるもののcss-modulesや今までのcss定義の仕方にも近く、スタイル定義をしてそれをタグのcss propsに紐付ける。ある意味スタイルとタグの関係性は希薄で、自由度高く、例えば一つの定義を複数のタグ種類に使いまわしたりもやろうと思えばできる。

emotionの方がライブラリとして後発ということも有り、自由度高く色々な書き方ができます。

https://emotion.sh/docs/composition

 

gist.github.com

 

styled-componentsもAPIが増えており(emotionに歩み寄ってる?)css propsも使えたりとしていますが、基本的にはタグとスタイルが紐付いており、そこから拡張(extend)して使っていくスタイルです。
タグコンポーネントを中心に添えているので秩序を保って行きやすいと思いました。
styled-componentsとして定義したものをexport importして使っていける点も良いです。(emotionでもStyled Components形式ができるので結局できること同じではあるんですが、、)

https://styled-components.com/docs/basics#extending-styles

 

gist.github.com

 

将来的にデザインシステムの構築を念頭と置いて、コンポーネントの定義やそれに対するスタイルの紐付けなどの秩序を保って行きたいと考えていたこと、
styled-componentsの方がコミュニティが大きくネット上で見つかる情報も多いことに加えて、
React.Fragmentのショートカット(<></>です。)が現時点では使えないという決定打(最近Reactのバージョン上げて使えるようになって意気揚々と書き換えていっていたので、、)が大きく(主に気持ち的に、、)、結果styled-componentsを選択することにしました。

🎉コーディングのしやすさ

css-modules以前のようにクラス名を介さずにスタイルの切り分けロジックをtemplate literalsの中に直接書けるので見通しがいいです。
また、Styled Componentを直接exportして共通コンポーネントとして扱えるのもレイアウトの共通化などがしやすくて良いです。
css propsなどは使えるものの、標準的な使い方でルールを守ればコンポーネントの定義、利用、拡張の秩序が保たれる。(細かいルールは触りながら整えていきます。)

😶LinterやIDEの対応

stylelintとeslintそれぞれのfixが設定によってはかち合ってしまうので調整が必要(コミットフックのstyled-componentsのstylelint fixは諦めてエラーにしてる)ですが、VS Codeでエラー表示やstylelint fixなどもできるため許容範囲と言えます。

ちゃんとlintエラー表示される @VSCode

😶別の仕組みへの移行について

良くも悪くもReactに依存しており次に別の仕組みに乗り換える際は大変そう。Reactとは別の仕組みに乗り換えたり、CSSについて全く違うパラダイムが生まれてくるのも今の所想像付かないので、これは現時点での開発効率などを優先するで行こうと思います。

❎パフォーマンス

React.lazyのdynamic importの恩恵を受けることはcss-modulesと変わらないのですがstyled-componentsをwebpackのsplitChunks設定で共通のchunkに分けることで、キャッシュの効く共通chunkの微増するもののそれ以上に各コンポーネントのサイズが軽くなり、結果的に軽量化に繋がりました。移行すればするほどパフォーマンスに良いというのはやりがいがありますね。

❎デザイナー協業は?

完全にReactの世界の中でスタイリングしていくため、デザインHTMLを作ってから手作業でコンバートのようなことは現実的ではなくなりました。
ですが、そもそも弊社では現在Figmaをデザインツールとして全面的に活用しており、細かい調整も全てデザイナーにFigmaで行ってもらい、フロントエンジニアがそこからスタイルを抽出するスタイルになっており、以前のようにデザインHTMLでcss組んで、という開発スタイルがほぼ無くなっています。
このことも今回css in jsの移行に踏み出せた要因として大きいと思います。
※Figma活用については昨年、下記ブログ、というかWEB DB PRESS様で書かせていただきました。

 

tech.asoview.co.jp

 

🤔感想

Reactでの開発のしやすさに振り切る形としてはCSS in JSという選択は現時点ではベストと言える選択肢ではないか。
なかなか更新の少ない画面のリファクタに手が回らず、未だ残るBEM、css-modules、styled-componentsが混在しているのですが、本腰を入れて移行していきたいと思っています。
(何か別の魅力的な選択肢が現れてしまう前にw)

まとめ

ライブラリや基盤が変わるというのはより便利になる部分もある一方、一定コストがかかりますね(検討コスト、他メンバーへの展開などの教育コスト、書き換えていくコストなど)
特にフロントエンド開発に関しては3年前と今ではやはり見えてる景色が違っており、今選ぶならこれ、というものがそのタイミングによって変わってくるというのはもはや当たり前になってきていますね。 😅
辛い!は辛いですが、こういった環境の変化は刺激になりますし、より良いものに変わっていって開発効率や品質の向上を図れば、サービスの価値提供につなげていけると思いますので、今後も積極的に取り入れていけたらいいなと思っています! 💪

現在アソビューでは多方面での積極採用をしていますので、興味がありましたらお気軽に応募(まずはお話からでも)いただければと思います。

 

www.wantedly.com