Closed14

【key-front】ReactにおけるCSSのベストプラクティスについて議論してみた

1zushun1zushun

モチベーション

  • 毎週金曜日Slackのkey_frontチャンネルでハドル機能を使いお題に対してメンバー同士ディスカッションをする時間を15〜30分程度設けている
  • 今回のお題は「ReactにおけるCSSのベストプラクティス」について
  • ファシリテーターが筆者の番なので発表内容の骨子を作るついでに調査内容をスクラップに投げていく
  • 開催日は7/14(金)で最終的に議事録として結論をまとめる
1zushun1zushun

Sass(ITCSS / RSCSS)

Vue.js・Nuxt.jsを使った案件では主にSassを使った開発をしていた。

具体的にはITCSSとRSCSSを採用していた。

ITCSSはディレクトリ構成にフォーカスを当てたCSS設計として、RSCSSはクラス名にフォーカスを当てたCSS設計として活用していた。

FROCSSとかも触ったことはあるが今回は触れない。

改めて振り返ってみるとITCSSはディレクトリ構成としては使われる想定ではないCSS設計であることがわかった。

https://github.com/itcss/itcss-netmag/tree/master/css

https://medium.com/@luboskmetko/itcss-scalable-and-maintainable-css-architecture-d7eba3225138

https://qiita.com/makotot/items/2c3e99f15dca2789d403

ITCSSでは全てのパーシャルファイルを1つのディレクトリにフラットに配置するのが推奨されているっぽい。

恐らくITCSSをディレクトリ構成として取り入れた背景としては次の記事があったからではないかと思っている。

https://techblog.zozo.com/entry/itcss-to-zozotown

1年くらいSass(ITCSS / RSCSS)を使っていたがフロントの技術スタックがReactに移行してから社内の雰囲気が「Sass(ITCSS / RSCSS)ではなくCSS modulesやCSS in JSを使うのが良い!」になってSassを使った開発からは離れていった。

CSS in JS(emotion / styled-components)

フロントの技術スタックがReactに移行してから「React開発でCSSをどうするか問題」が発生した。候補としてCSS modulesかCSS in JSか迷ったが次の記事を参考にCSS in JS(emotion)をメインとして使うようになった。

https://note.com/tabelog_frontend/n/n2541778b81e3

https://techblog.zozo.com/entry/zozotown-css-in-js

20231110更新

CSS-in-JSを使ったみた記事が公開されていた

https://speakerdeck.com/hiro0218/one-year-after-introducing-css-in-js-to-zozotown

要点だけまとめると次になる。

  • CSS Modulesがメンテナンスオンリーになった背景からCSS in JSに白羽の矢が立った
  • 食べログさんではstyled-componentsを採用した
  • ZOZOTOWNさんではemotionを採用した

UIフレームワークはMUIを使っている点(MUIはデフォルトのスタイルエンジンとしてemotionを使用している点)と先ほどのZOZOTOWNのstyled-componentsとemotionの比較記事からemotionをメインとして使うようになった。

Material UI コンポーネントの CSS スタイルを生成するために使用されるデフォルトのスタイルライブラリは Emotion です。

https://mui.com/material-ui/guides/styled-engine/

この時からZero-runtime CSS in JSについてやTailwind CSSの存在は知っていたが、これらについてはまた後で取り上げる。

1zushun1zushun

スマートキャンプさんはCSS in JSではなくCSS Modulesを採用している(2023/2/8)

また、Next.jsはCSS Modulesをビルトインサポートしていて、特別な設定が不要なため、そのこともCSS Modulesを選択した理由となりました。

CSS Modulesはメンテナンスオンリーになったけどあまり関係なさそう?一次情報はみてないので後で確認する。

CSS Modulesについては、Next.js v13でも継続してサポートしていることから、意外と今後も使われ続けるのではないかと思っています。

今回のお題的にもNext.js13を見据えたものに絞っても良いかもしれない。

https://tech.smartcamp.co.jp/entry/renewal-recruiting-site#スタイリングにCSS-in-JSではなくCSS-Modulesを使用した理由

Next.jsのStylingに関するドキュメントでは下記が載っていた

  • Global CSS
  • CSS Modules
  • Tailwind CSS
  • Sass
  • CSS-in-JS

特にCSS-in-JSに関してランタイムCSS-in-JSは現在Server Componentsではサポートされておらず、一方でappディレクトリのClient Componentsでは、以下のライブラリがサポートされている。

  • kuma-ui(これはpoteboyさんのやつ)
  • pandacss(これは最近Twitterで流行ってるやつ)
  • styled-jsx
  • styled-components
  • style9
  • tamagui
  • vanilla-extract

Next.jsは「Next.jsでは、サーバーコンポーネントのスタイルを設定するためにCSS ModulesやCSSファイル出力ソリューション(PostCSSやTailwind CSSなど)を使用すること」をお勧めしている。

https://nextjs.org/docs/app/building-your-application/styling

1zushun1zushun

面白いイシューを見つけた

https://github.com/styled-components/styled-components/issues/3856#issuecomment-1591947905

React自体からは、現時点ではスタイリングに関する公式なアップデートが提供されていないことは驚くべきことです。しかし、React 19には新しいAPIが追加され、この問題を解決する可能性があるかもしれません。もしかしたら、Reactチームは安定性が確保された後に新しいAPIをリリースしているのかもしれません。そのまでの間は、クライアントコンポーネントに重点を置くことが最も簡単なアプローチだと私は考えています。

要するに、現時点ではReact自体がスタイリングに関して新しい解決策を提供していないため、クライアントコンポーネントを重視することが現実的な方法です。

このイシュー自体styled-componentsに関する内容になるので、今後使える想定でCSS-in-JSを視野に入れるのもありなのかもしれない。

1zushun1zushun

Why We're Breaking Up with CSS-in-JS

今回を機に積読にしていた次の記事を読んだ

https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b

筆者がemotionのメンテナーでランタイムCSS-in-JSの問題点について述べている。英語の記事でもあるので取りこぼしがないようにこの記事に関する内容をディグって行くと次の記事を見つけた。

https://zenn.dev/poteboy/articles/e9f63b87b3cd69#ランタイムcss-in-jsの問題点

Reactの公式ドキュメントにおいても以下の2つの理由からランタイムでstyleタグ挿入を行うCSS-in-JSライブラリは非推奨とされています。

現時点でのCSS-in-JSの問題点を踏まえながらゼロランタイムCSS-in-JS(Linariaやvanilla-extract等)と言う解決策を提示している。

1zushun1zushun

まとめ

  • ランタイムCSS-in-JSを選ぶならゼロランタイムCSS-in-JSを採用するのが良さそう?
  • emotionのメンテナーがパフォーマンス上の理由からCSS Modulesに移行したことやNext.jsがCSS Modulesをビルトインサポート観点からCSS Modulesを採用するのが丸い?
  • Next.jsでは「サーバーコンポーネントのスタイルを設定するためにCSS ModulesやCSSファイル出力ソリューション(PostCSSやTailwind CSSなど)を使用すること」をお勧めしているのでTailwind CSSも良さそう。

Tailwind CSS取り上げ損ねたのでレバレジーズさんの記事を載せておく

https://tech.leverages.jp/entry/2021/10/29/180213

一方でUIフレームワークの兼ね合いでemotionも簡単には手放せないし、今後ランタイムCSS-in-JSがアップデートされることを込みでemotionやstyled-componentsを導入するのもありなのかもしれない。

個人的にはReact・Next.js(appディレクトリ使わない)案件ならMUIコンポーネント使っていきたいのでemotionを引き続き採用するかな...悩みどころ。

CSSに知見の少ないメンバーが入ることを想定してCSS Modulesはかなりありかもしれない。個人開発とかならTailwind CSSは触っていきたいが未経験のメンバーとかの負担を考えると(ゼロ)ランタイムCSS-inJSまたはCSS Modulesが無難かも。

あとはメンバーとディスカションをして結論を出す。

1zushun1zushun

議事録_20230714

  • MUIを使うべきではないのではないか?
  • UIフレームワークはそもそも使わない方がいいと思う
  • UIフレームワークは細かいところのスタイルのチューニングでかなり時間がかかってしまう
  • CSS Modules、制作の観点からもCSS(Sass)ファイルに書きたい
  • 理由としては制作だとemmetを使う(使いたい)から→コーディングスピードが格段に上がる
  • CSS-in-JSだとemmetが使えないことがある
  • Stylelintも確実にきく、エディタの拡張機能もきく
  • CSSの標準機能が使える。メディアクエリやコンテナクエリが使える。CSS-in-JSで標準では使えないことがある
  • CSS Modulesならインポートして使うのでスコープの汚染も気にしなくて良さそう
  • Tailwind CSSはクラス名が長くなって汚くなるし、覚えゲーなのでそこまで慣れるまでの学習コストがかかる。だったらCSS Modulesが良いのではないか?

https://docs.emmet.io/css-abbreviations/

https://zenn.dev/moneyforward/articles/css-container-query

結論

  • UIフレームワークを使うのはやめよう
  • CSS Modulesを使おう

感想

  • 参加人数は4名(以下エビデンス)

  • 今回のディスカッションでは開発ではなく制作に精通しているメンバーが参加してくださっていたため制作の観点からも議論できるいい機会だった。
  • 特にランタイムCSS-in-JSでは日々アップデートされるCSSの新しい機能を使えない・emmetが対応されていないデメリット、散々調査してきたパフォーマンス上の問題点など普通のCSSを書く方がメリットを享受できるのではないと思った。
  • いつしか先輩に「Tailwind CSSやCSS-in-JSなど使うのはいいけどちゃんとCSSかけるようになろうよ」と言われたことを思い出した。
  • もちろん、案件によっては既に導入されているスタイルがあると思うので、その際はその案件に沿って実装を進めるのはかわらない
  • だがNext.js13公式からおすすめされている点、今回のディスカッションで学んだCSS(CSS Modules)のメリットから今後の案件ではCSS Modulesを使おうと思った
  • UIフレームワーク(MUI・emotion)を使っていたのは案件で使っているテンプレートコンポーネントを拡張することで素早く実装に取り組めるから。言い訳を言うと案件の工数に基盤開発の時間をとってもらえないから。
  • 既に走っている案件に入る場合はどうしようもないが工数だしから入れる案件の場合は基盤開発の時間だけは死守してCSS Modulesで実装を進めるようにしたい。
1zushun1zushun

LTに参加して得た知見

下記のLTに参加してCSS周りの知見でアップデートがあったのでメモします。

https://offers.connpass.com/event/298939/

  • よしこさん:CSS Modules大好き。小回りがきくし潰しも効くから(CSS好き奴のポジショントークであることは否めない)
  • うひょさん:いわゆるCSS Modulesが好き。CSSの構文が直接使えるのはやっぱり偉い。パフォーマンスも基本的には良好

パネルディスカションで上記のように明言していた。個人的にはCSS Modules推し(になった)ので嬉しい。

CSS Modulesのメンテナンス状況について質問してみた

他の参加者も同様の質問をしていたのですが、CSS Modulesがメンテナンスモードになっていることなど、将来性をどう思っているのか質問し、回答を聞くことができました。

  • Next.jsでサポートされている感じなのでシンプルかつ良いと思っている
  • デファクトスタンダード化しているので「非推奨」はないと思っている
  • 誰もメンテナンスしないのはないんじゃないかと思っている
  • 手堅い選択肢としては長く残ると思っている
  • 仮にCSS Modulesが使えなくなったとしても生のCSSとしてのfallbackがあるのも良い

確かにメンテナンスモードなだけで非推奨になるのは考えにくいし、Next.jsがサポートしている背景や、最悪CSS Modulesがダメになっても生のCSSとしてリプレイスできるのは強みだと思った。

議事録_20240129

議事録を見つけたので添付します。

https://offers.jp/media/event-report/a_3060

その他

  • ちょっとまだ触れていないがvanilla-extractがCSS Modulesのつらみを解決してくれるのかも

https://vanilla-extract.style/

https://tech.plaid.co.jp/karte-blocks-vanilla-extract-adr

  • CSS Modules使うならtyped-css-modulesも一緒に使いたい

https://www.npmjs.com/package/typed-css-modules

https://zenn.dev/cybozu_frontend/articles/2528ad2935be9f

  • typed-css-modulesの強化版→個人開発ではこちらを使う

https://www.mizdra.net/entry/2022/11/14/102506

1zushun1zushun

「動的なスタイルの切り替えにはインライン・スタイルを使う」

The React core team finally have opinions about CSS

以下Reactチームのコメントになります。詳細は添付記事からさらに引用されている一次情報(YouTube)を見ると良いかと思います。

全体的には、動的なものにはインライン・スタイルを使えばいいと考えています。変化しないものには、CSSにコンパイルされるものを使い、実行時に余計なコストがかからないようにする。

https://dev.to/hypeddev/the-react-core-team-finally-have-opinions-about-css-16f0

Emotionのベストプラクティスにも類似の内容があったのを思い出したのでメモ

静的スタイルにはcss propまたはstyledを使用し、真の動的スタイルにはstyle prop(インラインスタイル)を使用します。動的スタイルとは、頻繁に変更されるスタイルや単一の要素に固有のスタイルを意味します。

https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles

1zushun1zushun

happy-css-modulesを使ってみた

少し時間が経ってしまいましたが個人開発にhappy-css-modulesを導入しました。導入するメリットは存在しないクラス名を宣言した時にコンパイルエラーが出るのとコードジャンプが可能になる点です。

https://github.com/mizdra/happy-css-modules

その際に*.d.ts*.d.ts.mapが生成されますがフラットに置いてしまうと可読性が落ちるので以下の記事を参考に*.module.scssを起点に折りたたんでいます。

https://zenn.dev/yumemi_inc/articles/make-css-modules-happy

実際に試してみると以下のようになります。

先ほどのButton/styles.module.scssの中身は以下のようになっています。

@import '@/assets/styles/objects/base-button.module';

.button {
  composes: button;
}

スタイルはlayerとfeatureに分けていて(この言い回しはあっているのか?)layerの場合は複数箇所で使われる想定なので別途assets/stylesディレクトリを設け、ITCSSで管理しています。

逆にfeatureの場合はコロケーションさせています(上記の場合はButtonディレクトリの中に配置している)なので改めてButton/styles.module.scssのコードを解説するとButton以外にもIconButtonやLabelButtonがあり、それぞれスタイルの共通部分はlayerで管理していて、独自のスタイルはfeatureで管理しています。Buttonの場合は独自スタイルが不要なためlayerで管理しているスタイルをインポートしているだけになります。

package.jsonのscriptsは以下のように設定しています。

{
  "scripts": {
    "hcm": "hcm 'app/**/*.module.{css,scss,less}' --webpackResolveAlias='{\"@\": \"app\"}'",
    "hcm:watch": "npm run hcm -- --watch",
    "hcm:remove": "ts-node scripts/remove-hcm.ts"
  }
}
1zushun1zushun

積読を消化する(以下は2024年4月の記事)

https://www.joshwcomeau.com/react/css-in-rsc/

CSS-in-JSとRSCの非互換性

The fundamental incompatibility is that styled-components are designed to run in-browser, whereas Server Components never touch the browser.

→ styled-componentsはブラウザで動作するように設計されているのに対しRSCはブラウザに触れません。

こちららでも同じことについて触れている

The reason why runtime CSS-in-JS (not just CSS in JS in general to be exact) has so many problems in the new architecture of React is because as its name suggests, it needs JS in runtime to function, while RSC does the exact opposite.

→ Runtime CSS-in-JSが機能するためにはランタイムにJSが必要なのに対し、RSCはその反対になる。

https://github.com/mui/material-ui/issues/38137#issuecomment-1648754338

さらに詳細に触れたい場合は別途スクラップを作っているのでこっちをみる

https://zenn.dev/link/comments/0a0ca7dde09d56

Zero-Runtime CSS-in-JS

主に紹介されているのは以下の3つになる

  • Linaria
    • コンパイルの段階でLinariaはすべてのスタイルをCSS Modulesに移行する
  • Panda CSS
    • Tailwindスタイルのユーティリティクラスにコンパイルされる
  • Pigment CSS
    • Pigment CSSはコンパイル時に実行され、CSS ModulesにコンパイルするというLinariaと同じ戦略をとっている

Pigment CSSの補足

  • Pigment CSSはMUIが開発をしているZero-Runtime CSS-in-JS
  • RSC の互換性を阻害する唯一の要因は Emotion の使用だった。そのためLinariaとCompiledのアイデアを用いて、ゼロランタイムCSS-in-JSライブラリを開発した
  • styled-componentsと同じでthemeをContext API(Providerコンポーネント)で提供されておりRSCとの互換性がなかった

https://github.com/mui/pigment-css

なんでZero-Runtime CSS-in-JSを開発するの?EmotionからネイティブCSSへの移行でもいいんじゃないか?Tailwind使わないの?みたいなディスカッションがあるので背景が気になるなら以下をみる

https://mui.com/blog/2023-material-ui-v6-and-beyond/

https://github.com/mui/material-ui/issues/38137

FOMO

FOMOは「常に周囲の行動や情報を追っていないと、自分だけが取り残されてしまうのではないか」といった不安や恐怖を抱いてしまう状態」のこと

このスクラップは2023/07/16にクローズされました