128
Help us understand the problem. What are the problem?

posted at

updated at

それでも私がTailwind CSSではなく、CSS Modulesを推す理由

*2021 6/11追記 『でもクラス名考えるのめんどくさい』問題についての私の見解を大幅加筆しました。
*本記事はピュアなCSSについてのある程度の知識があり、Tailwind CSSの採用について考えている層を対象読者としています。ピュアCSSの知識が乏しく、最適なCSSフレームワークを探している読者は対象としていません。

色々書き比べた結果Tailwind CSSにしたという話
こちらの記事がバズっていた(6/9現在 over 200likes)為、読ませて頂きました。

これまで主観的な印象と薄い議論で賛否が分かれていたTailwind CSSについてこれまでのcssの技術の変遷を踏まえて技術的にかなり踏み込まれた考察の上で選定の理由が書かれており、Tailwind CSSアンチ派の私にとっても非常に勉強になる記事でありました。リスペクト。

その上で、こちらの記事では私が『それでもCSS Modulesを推す理由』について書かせていただきたいと思います。

*2021 6/9追記 「Tailwind CSSこそCSSのあるべき姿でありフロントエンドの未来である」というフロントエンド界隈の感情が「急激に」高まりつつある現状に対しての一つの問題提起となれば幸いです。

Next.js,Gatsby などのフレームワークでBuild-inサポートされている

CSS Modulesは上述のフレームワークなどを用いて開発をスタートした際、デフォルトでサポートされている為、追加のライブラリなどは不要です(とはいえsassなどはほとんどの場合追加すると思います)。

ちなみにNext.jsの開発元のVercelはstyled-jsxというCSS in JSライブラリーを管理しており、こちらもNext.jsではbuild-inでサポートされています。
とはいえ、Next.jsの公式サイトなどを見る限りあくまで公式の推しはCSS Modulesっぽいです。
CSS in JSと CSS Modulesの比較になりますが、CSS Modulesの方が、『ロードタイム・ランタイムともに、パフォーマンス面で有利』なようです。ソース
また、CSS Modulesで編集するのはただのCSS(SCSS)ファイルになるので、エディターの補完機能やショートハンドもフルに働きます。

ピュアなcssの知識さえあれば設定不要で書き始めることができるCSS Modulesですが、これ自体がTailwind CSSに対するCSS Modulesの一つのメリットとなります。
CSS Modulesを書くのに当たっては、Tailwind CSSのコンフィグ方法や、クラス名などを覚える必要がありません。また、Tailwind CSSそのものが今後進化していった場合などに、それらの変更に追従する保守コストもかかりません。

冒頭で紹介した記事では、webpack依存技術である点や、『ファイルが増えてめんどくさい』点についてCSS Modulesのデメリットとして述べられています。
しかし、webpack依存に関しては現代的なフロントエンド環境の95%以上がwebpackを使用している現状などから考えてそれほど憂慮することでもないのかなと思っています。
また、次世代のbuildツールとして話題になりつつあるViteなどでもCSS Modulesはサポートされているようです。

.BemTailwindBlock__Inner {
  @apply lg:flex w-full;
}

また、セマンティックなクラス名(上のコードではBEM命名方)とTailwind CSSを併用する例として、@applyを使用した例を載せていただいておりますが、@applyはTailwind CSSを使用しているときのみ機能します。ビルドプロセスからTailwind CSSを削除すると、そのCSSは機能せず、レイアウトは壊れてしまいます。つまり、@applyメソッドなどでセマンティックな命名をユーティリティクラスと使い分けたとしても、cssはtailwind依存になってしまうので、仮に将来cssフレームワークの交換などが発生してしまった場合、多大な作業コストがかかると思われます。スタイルがTailwindと密結合してしまうということは、Tailwindでスタイリングをしたコンポーネントを別のプロジェクトで再利用したくなった場合、その別のプロジェクトでもTailwindを採用していないと再利用ができないということも意味します。つまり、React(Vue)コンポーネントの再利用性に一つの制限が追加されるということです。

『ファイルが増えてめんどくさい』点に関してですが、これは『めんどくさい』です。CSS Modulesは一々CSSファイルをつくらないといけないという『めんどくささ』から逃れることは出来ません。
『状態管理とレンダリングロジック』と『スタイル』 の責務をそれぞれのファイルに分割するというアーキテクチャは、『ファイル分割のめんどくささ』とトレードオフになります。
私はjsxファイルは出来るだけ、『state,propsのデータの流れとレンダリングロジック』に集中できる見通しのいい状態が好ましいと考えています。

<button
  type="button"
  className="
    relative py-2
    w-1/2 sm:w-auto sm:px-8
    text-gray-900 bg-white
    text-sm font-medium whitespace-nowrap
    border-gray-200 rounded-md shadow-sm
    focus:outline-none
    focus:ring-2 focus:ring-indigo-500
    focus:z-10
  "
>

このようなTailwindのクラス名で数行視界が奪われてしまうのは、私は絶対に避けたいと思っています。
Tailwindはコードのクリーンさと引き換えに、初速と最低限のデザインシステムを保障しますが、それは保守性の悪化というコストを将来に残すリスクを孕んでいることは選定の際に、考慮しなければならないと思います。
CSSファイルを増やさなければならないめんどくささと、jsxがtailwindのclass名に圧せられるツラミを天秤にかけると私はCSSファイルへの分割を選びます。

また、Tailwind CSSでユーティリティクラスをバンバン生やして快速にコーディングを進めていても、background-imageの画像パスを指定したり、::beforeや::afterなどの擬似要素を使用したくなったり、あるいはちょっとしたマイクロインタラクション的アニメーションの実装など、ピンポイントでcssを書きたくなる場面は出てくるのではないでしょうか?そのときは諦めてglobalにcssを書きますか?必要になったときだけCSSファイルをつくるというのも行き当たりばったり的でアーキテクチャとしては弱い感じがします。
モーダルなど、classの付け替えで見た目が大きく変更するuiなどcssの記述量が多くなるコンポーネントについてもTailwind CSSのユーティリティクラスだけでの対処はしんどくなりそうなので、例外的な処理をしたくなるのではないでしょうか。Tailwindを使っていると、『Tailwindだけではしんどい場面』の回避方が必要になり、その度にハック的な処理が増えていくのではないでしょうか。どんどんTailwindでしんどいところをカバーする為の非標準ライブラリーなどが追加されていき、ハウルの動く城みたいになっていきそうですよね。Reactと周辺ライブラリーなどの技術選定を自由に行って、保守の責任も自分で取り切れるプロジェクトならそれは構わないと思うのですが、Next.jsみたいなフレームワークを使用するのであれば、追加のライブラリーなどは出来るだけ抑えてできるだけNext.jsの標準構成から離れすぎないくらいに留めておくのが安牌ではないでしょうか。(開発・保守メンバーの学習コストを抑えられます
css modulesだとそういう場面的な対処や追加のライブラリーは必要なく、標準構成のみで一気通貫したアプローチが可能です。

Tailwindで出来ることはCSSで出来る(当たり前

Tailwind CSS好きな人が挙げるメリットとして、あらかじめ汎用クラスでサイズなどが設定されているので、クラスをどんどん当て込んでいったらある程度、統一性のあるデザインにまとまるというのがあるのではないでしょうか? Tailwind CSSのfont-sizeに関係するクラスの中身は例えば、このようなものになります。

.text-xs   { font-size: .75rem; }
.text-sm   { font-size: .875rem; }
.text-base { font-size: 1rem; }

これは、SCSSなどで変数として定義して、要素に入れると次のように書けます

$text-xs: .75em;
$text-sm: .875em;

.some-text{
  font-size:$text-xs;
}

*6/16追記 CSS標準のカスタムプロパティでも変数は使えます(*IE11はPolyfill必要

:root {
  --text-xs: .75em;
}
.some-text{
   font-size: var(--text-xs);
}

あるいは、Tailwind CSSのフォントサイズ周りのユーティリティクラスが便利ならそれだけコピーしてユーティリティクラスとしてそのまま使うのもアリです。ブレイクポイント、色、マージンなども同様です。変数・mixinだけのimport専用の共通cssファイルをうまく設計すれば、デザインシステムはTailwindを使わなくてもつくれます。
まぁ当たり前っちゃ当たり前なんですが、Tailwind CSSで出来ることはCSSで出来ます。(Ruby on Railsで出来ることはRubyで出来るくらい意味のない文章かもしれません)

Tailwind CSSが流行した背景を考える

しかし、これだけTailwind CSSが盛り上がってるのには何かそれ以前のReactなどのコンポーネントによるUI設計とCSSの設計によくある落とし穴というか問題があったのではと考えます。
私が推測するのは、『CSSクラスによるスタイルの過度の共通化』です。
あるいは、『CSSコンポーネントとReactコンポーネントの衝突』と言ってもいいかもしれない。
つまり、例えばCSSで

.sample-button{
  display: block;
  width: 100px;
  background: red;
}

というcssのクラスで見た目を定義し、そのクラスをそのまま複数のReactコンポーネントに適用すると問題は生じます。
スタイルは共通でも、機能の違うReactコンポーネントに同じCSSクラスを使い回してスタイルを当ててしまうと、後々、変更が入ったときに、CSSの影響範囲がわからなくなって、設計が破綻します。
再利用されるReactコンポーネントの中にCSSの文脈でコンポーネント化されたcssのクラス名が絡まってスパゲティ化するということです。
そのような事態を招くくらいならば、もちろん、Tailwindのような汎用ユーティリティティクラスを全てのコンポーネントに個別で指定していった方がマシです。

このような問題を引き起こさないためにCSS ModulesでのCSS運用にはルールを設けた方がいいでしょう。
『Reactコンポーネント1つあたり、1つの紐づくCSSファイルを必ずつくり、別のコンポーネントのCSSファイルは絶対に使用しない。共通スタイルはmixinで定義し、それをインポートして使う。』などを提案させていただきます。

Tailwind CSSはスタート速度と、最低品質を保証した?

確かにTailwind CSSを使うとCSS設計そのものが不要なので『設計の破綻によるカオス』は防げますし、デザインシステムの設定なしにある程度の見た目を保証してくれるといったメリットはあるのかなと思います。それによってヨーイドンでUIをつくっていく効率は確かに高まるのかもしれない…。

しかし、Tailwind はやはり『理想』ではない‥

TailwindCSSの上述のメリットは、『コードが汚くなる・スタイリングが完全にTailwind依存になる』という『将来的な保守性の悪化リスク』とトレードオフになっています。
結局、Tailwindは、ありがちなCSSにまつわる問題を解決し、最低品質は保障してくれるかもしれない一方で、必ずしも『理想のフロントエンド』への道筋にあるものではないのかなと思っています。
しかし、『CSSを絶対に書きたくない』『CSSを書かないで済む世界線が理想』と考えている開発者にはTailwindこそ理想の方向を向いているのかもしれません。
Webのスタイリングは異なったサイズのディスプレイの対応などもあって、ボリュームが大きくなることが多いです。私は、スタイリング要素を完全にUI構造と紐づけて記述していく未来よりは、やはりCSSは分離して書く方が筋かなぁと思っているのですが、確信はありません。Tailwindがこれからより普及してスタンダードになっていく可能性もあると思っています。JS強い方々でTailwindを推している人が多いのは事実です。しかし、私はどうしてもTailwindをスッキリ飲み込めません。この記事が一つの意見として、ある種の『ブレーキ』として働いてくれると幸いです。

2021:6/11追記 『でもクラス名考えるの面倒くさい』問題について

最初に、こちらの記事が予想以上のご反響をいただけたことに感謝します。それだけ、Tailwind CSSというものがこれまでのCSSフレームワークとは違った次元での関心を巻き起こしているということなのかなと思います。
さて、前置きは以上にして、『でも(セマンティックな)クラス名一々考えるのめんどくさい』問題についての私の見解を新たに追記させていただきます。

React, Vue以前のCSS設計を一度振り返る(BEM, SMACSS)

えっ、なんでいきなりReact,Vue 以前の歴史の話?となるかもしれませんが、これらの時代のCSS設計を改めて振り返ることでCSS Modulesが解決した前世代のペインポイントを改めて明らかにすることができますので、お付き合いいただけますと幸いです。

BEMについて

BEMはロシアのYandex社によって考案されたCSS設計手法です。『BEM』とは、Block、Element、Modifierの略語です。ここでは、詳細には踏み込みませんが、厳格なclass名の命名ルールによって、CSS設計に一定の秩序と機能•安全性と拡張性をもたらしました。

まずBEMが生まれた背景として次のような無設計にスタイリングされたHTMLとCSSの問題点を振り返ります。

BEM以前のhtml
<div id='someId'>
  <h2 class='title'>
    some title 
    <span class='name'>some name</span>
    <span class='date'>2021/06/11</span>
  </h2>
</div>
BEM以前のCSS
#someId{
  padding: 12px;
}

#someId .title > *{
  font-size: 24px;
}

この例のようにセレクタに ID,クラス名,タグ名などを混在させてCSSをつくると以下の問題が発生しました。

1.詳細度のカオス
2.HTML構造とCSS構造の密結合

1.詳細度のカオスとは、たとえば、↑の例の.name の部分のCSSを直したくなったとします。

詳細度カオス
#someId .title > *{
  font-size: 24px;
}

/*詳細度で負ける*/
.name{
  font-size:12px;
}

/*この詳細度で指定してやっと反映される*/
#someId .title .name{
  font-size:12px;
}

つまり、#someId .title > * という箇所でプロパティが当たっていた.name要素にスタイルを当てるには、それより強い詳細度の#someId .title .name というセレクタで指定しなければいけませんでした。このようなcssの詳細度をコントロールしなければスタイルを調整できないcssの保守過程では、最終的には!importantという禁じ手を用いて強制的にプロパティを適用させようとする猛者が出現してきたりして、治安が崩壊します(汗。

2.HTML構造とCSS構造の密結合 とは上の例の文書構造が、

BEM以前のhtml
<div id='someId'>
  <h2 class='title'>
    some title zz
    <div class='title-inner'>
      <span class='name'>some name</span>
      <span class='date'>2021/06/11</span>
    </div>
  </h2>
</div>

などに変更があった場合に(.title-innerが追加された)、#someId .title > * というセレクタで適用されていたcssが効かなくなるということです。
上のセレクタは文書構造と密結合していました。
このような方法で書かれたcssは文書構造の変化にまともに影響を受けます。
文書構造が変わると、それに紐づいているどこのCSSが影響を受けるかわからないという恐怖のwebサイトの誕生です。(考えるだけで嫌になりますね。

そこで救世主BEMが登場しました。

BEMのhtml
<div class='something'>
  <h2 class='something__title'>
    some title 
    <span class='something__title-name'>some name</span>
    <span class='something__title-date'>2021/06/11</span>
  </h2>
</div>
BEMのCSS
.something__title{
  
}

.something__title-name{
  
}

これがBEMのルールによって設計されたcssです。はい、無設計時のえげつない問題が綺麗に解決されました。
セレクターは常に一意のクラス名(継承はなし)になるので、不毛な詳細度競争はもうありません。クラス名は冗長ですが、これによってクラス名の衝突が防げ、ある程度の安全性が担保されます。
そして、HTML構造とCSSはもう密結合していません。紐づいているのはクラス名のみです。文書構造が変わってもCSSは影響を受けません。

どうでしょう?
『クラス名の厳格な命名規則』という一つのルールと、クラス名を考えるというコストと引き換えに、エレガントに問題を解決しましたね。

そして、私がBEMを好きだった理由として、その設計手法そのものが非常にわかり易いというところがあります。
BEMという設計方法を理解するのに要する時間は5分もあれば十分です。
これは、後々保守点検するメンバーが入れ替わり立ち替わり変わったとしても、BEMの学習にかかるコストが極めて少ない為、心配なく運用できるというメリットになります。

SMACSSについて

もう一つ、代表的なCSS設計であったSMACSSを見てみましょう。
SMACSS(スマックス)とは、「Scalable and Modular Architecture for CSS」の頭文字を取った言葉です。
SMACSSではCSSの種類をベース・レイアウト・モジュール•ステート・テーマ の5種類に分類して記述を行いました。

SMACSSのHTML例
<div class='l-container'>
  <h1 class='title title-blue'>SMACSSの例</h1>
  <button class='button button-active'>Click!</button>
</div>

例えば、ページを10ページつくるとして、containerのパディングは常に一緒だし、buttonのベーススタイルも全部一緒ってときに、SMACSSだと分類されたCSSのクラス名を再利用しながらスマートにページをつくっていけそうですよね?
新しいページが増えたら、また、l-container(レイアウト要素)のdivタグでくくってその中に, .title(モジュール要素)を入れて、今度は色だけ変えたいから.title-red(モジュール要素)をつけてと‥
なんか良い感じに思えます。
私はある程度の期間、数プロジェクトで実際にSMACSSを利用してホームページ制作を行いました。
そして、いくつかの懸念を得て、最終的にはSMACSSのほんの一部だけを取り入れてBEMに戻りました。

懸念1 レイアウトかモジュールか迷うときがある
懸念2 再利用し続けることで、単一のクラス名の影響範囲が広がりすぎる

まず懸念1について説明します。例えば、サムネイル画像とタイトルと日付で構成される、カードコンポーネントがあったとします。このカードコンポーネント、他のページでもそのまま再利用できるパーツっぽいので分類としてはモジュールとして扱うべきだと思います。しかし話がややこしくなるのは、このカードコンポーネントはそれ自体が子要素にモジュール(サムネ・タイトル・日付)を複数持つ、親要素でもあるということです。要はレイヤー分けの粒度によって、レイアウト要素にみえたり、モジュール要素に見えたりする要素が出てくる。そして、実際にcssを書き始めると、モジュールであったはずのカードコンポーネントに当たるスタイルはdisplay:flexであったり paddingであったり『layout要素』っぽいものばっかりになったりします。そして混乱が始まります‥あれ、カードコンポーネントってもしかしてレイアウト要素?

はい、少し話が横道にそれますが、これは私がReact界隈のコンポーネント設計手法として一定の認知を得ている『Atomic Design』によるコンポーネントの分類分けを忌避している理由とも共通します。
Atomic Designの場合、MoleculesとOrganismsだったりすると思いますが、レイヤー分けの概念そのものに粒度の匙加減によってどっちにもなりうるという曖昧さが存在する為に、『どっちに分ければいいかわからない』という不毛な議論が発生しがちになります。
そもそもAtomic Designはコードの設計手法として考案されたコンセプトではなく、UI設計のデザインプロセスとして考案されたものであるはずです。それを、視覚的なデザインとは別の都合が様々に介入してくるコーディングの設計に利用するとなると、問題や混乱は発生して当然です。
これは、保守コストに重大な影響をもたらします。一度メンバーで認識を合わせて、プロジェクトを始めても後々新規メンバーが入ってくるとあらためて、そのプロジェクトのレイヤー分けの概念・粒度の説明を徹底しなければならず、また、どのコンポーネントがどこに入っているか直感的にわかりづらかったりするという問題が発生するのです。。

ここで、『あっ、5分で誰でも理解できるBEMに戻ろう!』っとなります

懸念2 再利用し続けることで、単一のクラス名の影響範囲が広がりすぎる
についてはそのままなのですが、.some-nameというクラス名を再利用しまくったプロジェクトで、誰かがうっかり数年後、.some-nameというクラスのプロパティそのものに変更を加えたら、目的の箇所以外が崩れていたという問題が発生する懸念です。もちろん、作業の効率化の為にある程度の再利用は必要だと思いますが、その方法は、

.some1__name,
.some2__name{
  font-size:12px;
}

というふうに、愚直に名付けられたBEMクラス名を並列して書くであったり

@mixin name-size() {
  font-size:12px
}
.some1__name{
   @include name-size();
}
.some2__name{
   @include name-size();
}

と言うふうにmixinのインポートで行ったりする方が安全だったりしました。
もちろん、作業コストとの天秤にはなるので、ここらへんはプロジェクトの規模によって求められる『堅さ』によって調整していくところではあると思います。
私は、結局、SMACSSの『ベース』、『ステート』の部分だけをBEMのプロジェクトに入れるという設計でホームページ制作を数年間やっていました。
ベースはcssリセット含めた、bodyやinputなど要素そのものへのスタイルをまとめたレイヤーです。
ステートは.is-activeとか.is-errorとか特にjsを絡めて見た目が変化する場合のスタイルを指定するレイヤーです。BEMではmodifiyerとしてsomething--activeというルールで記述しますが、.something.is-activeの方がjsファイルで操作するクラス名がシンプルになる(.is-active)こともあり、私はベターと考えたので、ここだけSMACSSを取り入れました。

そして CSS Modulesへ

いやぁ、歴史の話が長くなりましたね。休憩一息いれたくなりませんか?どうぞ。
では、ここでようやく時代はReact, VueなどによるJSによって管理されたコンポーネントの現代に戻りました!
CSS Modulesに戻ります。
CSS Modulesはwebpackなどのビルドツールでコンパイル時に処理を加えることによって、スコープの安全性を確保しました。
例えば,Profile.tsxというReactコンポーネントをつくります。Profileコンポーネント内のh2要素に、『.name』と雑にクラス名を振ってスタイルを書いてコンパイルします。するとCSS Modulesは『.Profile_name__RTQTT』という一意の安全なクラス名を吐き出して割り当ててくれるのです!
BEMは、不毛な詳細度競争を終わらせてくれましたが、スコープの安全性を確保するために、冗長なクラス名をコーダーは一々考えなければならなかった。
そのBEMのペインポイントをCSS Modulesは機械的に解決してくれたのです!
『セレクターの詳細度を保ちつつ、名前空間の衝突に気を煩わせず、シンプルなクラス名で管理する』という長年の夢がようやく叶ったのです!
この時点で、『クラス名考えるの面倒くさい』っていう主張、贅沢すぎると思えてきませんか?
それにTailwindを使ってない人からすると、Tailwindのクラス名を暗記する方が面倒くさいんです。
CSS ModulesはSMACSSの『単一のクラス名の影響範囲が広がりすぎる』という懸念にしたってある程度、解決してくれています。コードの再利用はCSSの共通クラスをペタペタ貼り付けるのではなく、JS Componentベースになるわけですから。ここで、気をつけておかないといけないのは、再利用はJS Componentベースで行うものであって、CSSクラスの再利用によって行うものではないという認識の徹底です。これを忘れると、CSS Modulesのメリットもクソもなくなるわけです。
いやぁ、長くなりましたね。こちらが、クラス名考えるの面倒くさいという主張に対する、歴史を振り返っての私の見解になります。CSS Modules思ったよりめんどくさくないんじゃね?って思っていただけますと幸いであります。

なにより、この記事の執筆の最大のモチベーションは『Tailwind CSSこそがCSSの問題を恒久的に解決する最適なソリューションであり、フロントエンドの未来である』という、飛ぶ鳥を落とす勢いのTailwind CSSに対する一つの批判的な視点を提供することになります。CSSについて、より深く考えるきっかけになれば、幸いです。

最後に

Twitterなどでエゴサーチをして、本当にたくさんの方がReactのCSS結局どれで書けばえぇの?って問題にぶち当たっていることが確認できました。
CSS in JSライブラリーは乱立しているし、フレームワークは流行り廃りがあるし‥
私が一つ問うた方がいいと思うのは、果たしてCSSをラップするライブラリーやらフレームワークを学んだり選んだり検討するのにそれだけのコストをかける価値があるのか?ということです。
CSSをライブラリーやフレームワークでラップしたところで、多少初期構築の効率は上がっても、表現力の最大値が上昇したりはしません(依存は増えます)。変わるのはどうCSSを適用させるかという中間部分だけで最終的にあたるCSSはほとんど大差のないものになります。
それに比べて、例えばWebGLとかShaderとかが書けたら、ダイレクトに表現力の最大値が上昇します。最近Google Map APIがWeb GLに対応して、3D表示が出来るようになったらしいです。

CSSのライブラリーを次から次に取り替え引っ替えして、費用対効果の低い学習コストを費やしたりするよりもっと、学習価値の高い(最終的なプロダクトの品質に直結する)分野はあると思うのです。

CSS Modulesで書くCSSはただのCSSです。昔からある標準技術です。そして、将来何があっても、そのまま動きます。
Reactの時代もVueの時代も終わって、標準Webコンポーネントの時代が来ても、あなたの書いたCSSは問題なく移植できます。

参考 Tailwind CSSが私には合わなかった理由

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
128
Help us understand the problem. What are the problem?