@aumy_f, 2024-08-10-tailwind:
Tailwind CSSは特異なコードベースの見た目から負債になりそうとたまに言われるが、俺はあんまりそう思っていないので説明したい。
Tailwind CSS1 を一目見た人、特にCSS初学者のうちけっこうな割合が「これエグい負債になりそう」と思う気がする。なぜなら実際にそのような意見をちらほら見るからなんだけども、自分はあんまりそうは思っていないし、微妙に今のCSSについて誤解があるような空気も感じるのでその理由を説明したい2。JSXと同じで嬉しさを理解して使い慣れればなんてことはないのだけど、一方でその背景にある話はJSXより複雑なので単純に使って慣れればいいという話でもなさそう。
なお、この記事は私の以下の2ツイートを膨らませたものです。
Tailwind CSS、剥がすのは大変そうだけどそれをもって重大な負債になると評せるかは微妙に思っている
https://x.com/aumy_f/status/1822094147853209734
コードベース上のスタイル記述方法が素のCSSだったとしても、CSS modulesやstyled-components、vanilla-extractなど、スタイルの塊に名前を付けた時点で具体的スタイルとDOM要素の間には1枚の抽象化層が挟まっていて、そいつが腐る可能性はつねにあるんだよな
https://x.com/aumy_f/status/1822096999044505890
ところで予防線を張るのを許してほしいのだが、自分は業務でTailwind CSSを利用したことはないので、実際にはメンテエグいキツみたいな話はありえるかもしれない。
技術的負債の原典を引いたりするのはめんどくさいが、ここで負債とは何か明確にしないとめちゃくちゃ雑な議論になってしまうのでとりあえず定義をしてやや雑な議論ぐらいにしておく。技術的負債とは変更を妨げるものである。たとえばタイトルの文字を黒から赤にしたいとき他の部分の色が変わってしまわないか気にする必要があったりすると負債がでかいことになる。
いきなり強めの表現なので申し訳ない気持ちでいっぱいだが、これは言っておきたい。けっこう「マークアップとスタイルの分離」の観点からTailwindに難色を示す人がいるようだが、どうがんばってもスタイリングのためのマークアップは避けられず、複雑化したセレクタの詳細度はパズルとなり、かつて言われた「HTMLとCSSを分けるべき」という指針はいまや幻想だと言わざるをえない。
全セレクタが巨大なマークアップ全体を射程に収めているスタイルシートというのはどのスタイルがどこに影響するのか追跡するのに限界があり、BEM、CSS Modules、CSS in JS、Vue/Svelte SFCのscoped CSSなど、小さなマークアップとそれに密結合したスタイルシートという構成が長いこととられてきた(『Tailwind CSS実践入門』p9–も参照)。実際デプロイされるアプリケーションは巨大なマークアップとグローバルにセレクタ名前空間を共有するスタイルシートで構成されているかもしれない3が、コードを書くときの気持ちとしてはずいぶん昔から「小さなマークアップとそれに密結合したスタイルシート」なのだ。
Tailwind CSSはマークアップ内にスタイルを書かせることで、この「小さなマークアップとそれに密結合したスタイルシート」というモデルを字面上でも実現する。実際に密結合なものは字面でも近いほうがいい、という原則はコロケーションという名前で知られている。
ご存じのようにTailwind CSSを使ったコードベースにおいてスタイルは以下のように書かれる。
<div class="flex gap-4 items-center">子要素略</div>当然ながらflex gap-4 items-center という文字列は妥当なCSSルールではないが、これが素のCSSではないために将来に負債になりそうという意見を述べる人がそれなりにいるように思う。Tailwind CSSからCSS Modulesのような素のCSSを書く手法に移行するためには、flex gap-4 items-center を素のCSSに変換する必要があり、これはstyled-componentsのような同じく素のCSSを書く手法ならだいたいコピペで済むことと比較してあきらかに手間がかかりそうである。
深刻な前例としてSass(SCSS)やStylusといったCSSプリプロセッサを想起する人もいるかもしれない。彼らは素のCSSとは異なる文法や機能(変数とかmixinとか)を持っており、剥がす際にはそれらをなんとかする必要がある。
しかし、自分の意見としては、構文だけを見て「これは標準準拠でないから剥がしづらく、負債になる」と考えるのは少し違う と思う。CSSプリプロセッサはコンパイル時に展開される変数やmixinといったWeb標準にない独自の機能が移行の難易度を上げているのであって、Tailwind CSSの特異なスタイル記法はものすごく剥がしづらいとはいえない4と主張したい。Tailwindのクラスは基本CSSプロパティと1:1対応するものなので素のCSSに書き換えるときも負荷はそれほど高くなく、ある程度機械的に変換することも可能だろう。
そして、いざというとき剥がすのが大変だから負債になってよくないというのも理屈として微妙とも思う。技術を剥がしたくなる大きな要因として日ごろのアプリケーションへの変更を阻害しているというのがあり、Tailwindはいざというとき剥がすのがやや大変かもしれないが、どんな技術も剥がすのはそれなりに大変であり、日頃のアプリケーションへの変更はむしろかなりやりやすくなる技術なので十分リターンが得られるのではないだろうか。
一応、Tailwindから脱出せざるを得ないシナリオの1つとしてTailwindの終了というものがあると思うが、Tailwindは多数の企業に利用され、すでにそうそう死なない5フェーズに入ったと自分は見ている。また究極的にはflex gap-4 items-center はただのクラスであるため、各クラスに対応するスタイルを別途用意すればコードベースを維持しつつTailwindを剥がすこともできるとは思う。このあたりがChakra UIなどとの差別化要素になるだろう。
BEM、CSS Modules、styled-components、vanilla-extractなど既存のほとんどの手法はスタイルの塊に(多くの場合、セマンティックな—それがなんであるかではなく、どこに使われるかに着目した—)名前をつける。
CSS Modulesの例:
.actionList { display:flex; gap:16px; align-items:center;}vanilla-extractの例:
export const actionList = css({ display:"flex", gap:"16px", alignItems:"center",});display、gap、align-items、この3つの宣言は「交差軸の中心に隙間16pxでアイテムを配置するflexbox」ということだけを示している。波括弧の中だけ見ればこいつらがアクションリスト(ボタンとかが並んでるんだろう、たぶん)のスタイルであるとはわからない。これがアクションリストのスタイルであるとわかるのは、こいつらをまとめたものに変数名あるいはクラス名としてactionList が与えられているからだ。
この「スタイルの塊に名前をつける」という行為は抽象化の層を一段増やしている。主語のデカい私見だが、いかなる抽象化層も潜在的に技術的負債になりえる。こいつはactionList と名付けられているが将来にわたってその名前が適切であり続ける保証はない。actionListInner とかactionListBody が適切な名前になるのかもしれない。そのたびに我々は適切な名づけを考えることになる6。
その点でTailwind CSSのflex gap-4 items-center という文字列は(デフォルト設定で解釈すると)「交差軸の中心に隙間16pxでアイテムを配置するflexbox」というそのままの意味しか持っていない。なんなら<div> というHTML片のレベルで見てもこいつは「交差軸の中心に隙間16pxでアイテムを配置するflexboxなdiv」であってアクションリストだとはどこにも書いていない、というか書く必要がない。この特性は、従来の手法ならactionListWrapper とかactionListLabelHelpLink みたいなクラスの名づけが必要になるレベルでレイアウトが複雑化していくほど負荷を減らしていく7。
Tailwindが使っているユーティリティクラスだってクラスに名前をつけているから抽象化であって負債のもとになるのではないか?という疑問があり得ると思うが、それに回答しておく。
従来のCSS手法を使うコードベースでアクションリストの背景を黒くしたいとき、あなたはactionList クラスのスタイルにbackground-color: black; を足すだろう。一方ユーティリティクラスの場合はclass 属性にbg-black を足す。このとき従来のCSS手法では変更を加えるのはactionList クラスのスタイルの中身である。変更の前後で同じクラスだが、スタイルの内容が変わっている。一方でユーティリティクラスの場合、変えるのはclass 属性の文字列であってクラスそのものではない8。このことはつまり、(仕様が固まった)ユーティリティクラスはアプリケーションレベルの変更についてイミュータブル9であるといえる。
イミュータブルなクラスでスタイルを構成していると、クラスの中身が何らかの変更で書き換わってしまい意図しないスタイル変動が起こる、といったことが起きない。1クラスにつき1回のみの使用を徹底すれば同様の効果は得られるが、やや労力がいる10。ある程度盤石なパーツを組み合わせていくのはソフトウェア開発の重要なプラクティスの1つだ。
style 属性によるインラインスタイルは疑似要素などに対応しておらず明らかに機能不足だが、Emotionのようなcss タグ付きテンプレートリテラルでCSSを書く手法を持ち出して比較するのは価値がある。ざっくりTailwindの利点を説明すると「短い」「とりうる値の種類を制限できる」の2つになる。やや脱線するので「Tailwind デザイントークン」とかで検索してほしい。
この記事でおもに説明しているTailwindのいいところである「名前付けからの解放」についてはインラインスタイル系でもだいたい同じ議論が通用するだろう。ここだけに着目し、素のCSSであることによる将来的移行性を見据えるならTailwindでなくてもいいが、デザイントークン的な話も面白いのでぜひ見てみてほしい。
Tailwind CSSはReset CSSとしてmodern-normalizeを採用し、さらにその上にPreflightというかなりopinionatedなスタイル群を適用する。こいつに依存するのがけっこう将来的な脱出で足枷になる可能性はあると思うが、こういう系のベーススタイルはなに選んでも心中みたいなとこあるので受け入れるしかない気がする。
とまあこのようにTailwind CSSのよさを語ってきたわけだけども、Tailwind CSSはBootstrapなどいい感じなありもののスタイルがあるようなフレームワークとは異なり、CSSプロパティを理解し操ってレイアウトを組み見た目を作る必要があるので、少なくとも普通にCSSを書けるだけのスキルは要求される。
それなりにCSSを書いていると最終的にTailwindええっすわ…になりがちと言われている(要出典)のだが、逆にコンポーネントベースCSSを枷と感じていない段階の入門者が同じような理解に至るのは難しく、CSSのカスケーディングの難しさとTailwindの特異さで右も左も意味不明な技術ばっかや!という気持ちになってしまうのは仕方ないところがある。まあ手続き的DOM操作のつらさを味わわずにReact直行する人(おれとかわりとそう)がいっぱいいるだろう昨今を考えればガチ最低限のCSS知識つけたうえでTailwind直行しても別に問題はないのかもしれない。
ところでCSSはヤバとかCSSは世界最難のプログラミング言語とか冗談めかしていわれているわけだが、その難しさにはインタラクティブなビジュアルを作るのは本質的に難しく、CSSを滅ぼしてもおそらく人類は同じ困難にぶち当たるというものが含まれているのでそのへんは認識してもらえるとうれしい気持ちがあります。
わりとあると思う。パッと思いつくのは制作と以後のメンテで担当が分かれててメンテ担当にビルドツールを扱える技量を期待できないとき。あとTailwindはどちらかというとアプリケーション向けの技術で、そこら中に複雑なレイアウトやアニメーションが登場するようなクリエイティブ寄りのWeb制作物だと微妙そう。『Tailwind考』などを参照されてもいいかもしれない。
これは大マジ。実際Tailwind Labs(=公式)がメンテしてるのでそうそう起きないとは思うが、Tailwindの体験の8割はエディタプラグインに支えられていて、前UnoCSSちょっと触ったときもエディタプラグインの挙動が合わなくて挫折した。逆にプラグインまだ入れてない人は今すぐ入れてください。
これは大マジ。align-items がalign- (これはvertical-align) じゃなくてitems- なのとか。長さとの兼ね合いではあるんでしょうが。
なるほどな~と思った順でソートしてあります。
data- 属性しか思いつかないんですがいい手法ありますか?aria とかか以下、気分によってTailwindと略す。正式名称ではないが、公式ドキュメントでも使われている略し方である↩
この記事には断定形が全然ないが、がまんしてほしい。↩
BEMは特に見かけ上直接「巨大なマークアップとグローバルにセレクタ名前空間を共有するスタイルシート」を書いているが、これも実際に規約を運用するときのメンタルモデルとしてはコンポーネントという細切れのマークアップの存在に基づいている。↩
脱線する話をすると自分はたまに見かけるWeb標準への過剰な信仰はあまりよくないという意見を持っている。本当にWeb標準に準拠していないというのはAdobe FlashとかSilverlightのような勝手拡張で造営されたインフラやそれに依存する物体であって、その視点でいえば現在のWebブラウザ開発でわれわれ依存するのはすでに確立されてそうそう廃止されないWeb標準であり、比較的長期の寿命が期待できる。Flashのようなオレオレインフラが消え去ったからか「Web標準に準拠」の意味が「Web標準の構文やAPIを使用」に変容しつつあるように思う。それ自体は悪くない話だが、Flashに向けていた憎悪がそのままWeb標準でない構文やAPIに向けられてはいないか、ということはたまに考える。ブラウザAPIとの字面の近さでAxiosとFetch APIを評価するのがはたして正しいことなのだろうか?↩
有料化したらウケるが、どちらかといえばそっちのほうがありえる↩
ちょっとこのへん具体例出せるとうれしいが、余力がない↩
結局(たとえば)Reactコンポーネントという形で名前をつけることにはなるのではないか、という疑問にここで回答しておくと、コンポーネントを構成するすべてのスタイルの塊に名前をつけなければならない状況と、必要なときに一定のスタイルの塊に名前をつけることができる状況は異なると考える。↩
本当に一応の確認なんですけど、ユーティリティクラスを使う場合、text-4xl (たとえばfont-size: 2.25rem) が当たっている要素のフォントサイズを1.5remにしたいときにやることはtext-4xl をtext-2xl に替えることであって、text-4xl の定義をfont-size: 1.5rem に編集することではない。↩
いきなり独自な言い回しを持ち出して申し訳ないんだけど、要するに「アプリケーションへの変更(たとえば、アクションリストの背景を赤くしたい)をするためにユーティリティクラスの中身を変更する必要はない」ぐらいの意味合い↩
マジで関係ないんだけどこれshared XOR mutableっぽくておもろい↩
やや屁理屈めいた脚注…あらゆる技術は潜在的に負債なので慣れれば便利な技術も全部潜在的に負債という考え方ならわかるが、『「慣れれば」って時点で負債の発生源になってる』という文章の意味としてはそういうことではないだろう↩