パクチーパクパク小宮山です。
掲題通りTailwind CSSの始め方を最小手数で書いていきます。余談は一切ありません。
tl;dr
CSS管理は諦めてTailwind CSSを使おう。
Get Started
ひたすら公式通りに進めます。例によってフロントエンドプロジェクトの環境構築はひたすら面倒なので、Tailwind CSS以外のツールチェインはなるべく使わない構成を目指します。
installします。
$ yarn init $ yarn add tailwindcss
セットアップします。
$ yarn tailwindcss init
こういうファイルが作られました。
tailwind.config.js
module.exports = { purge: [], theme: { extend: {}, }, variants: {}, plugins: [], }
スタイルのエントリーポイントなるCSSファイルを作成します。ファイル名は任意です。このファイルをTailwind CSSが用意しているCLIでビルドすることで、実際にhtmlファイルで読み込むCSSファイルが出力されます。
tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
TODOリスト感のある素朴なHTMLファイルを用意します。この時点ではpublic/style.css
はまだ生成されていません。
見た目だけの実装なのでフォームも飾りです。
public/index.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="style.css"> <title></title> </head> <body> <h2>New Todo</h2> <form> <input type="text" /> <button>submit</button> </form> <h2>List Todo</h2> <ul> <li> <p>todo 1</p> <p>2020 05/12</p> </li> <li> <p>todo 2</p> <p>2020 05/12</p> </li> <li> <p>todo 3</p> <p>2020 05/12</p> </li> </ul> </body> </html>
public/style.css
を生成するためのscript
を用意しておきます。
package.json
{ "name": "minimum-tailwindcss", "version": "1.0.0", "scripts": { "build:css": "tailwindcss build tailwind.css -o public/style.css" }, "dependencies": { "tailwindcss": "^1.4.6" } }
実行します。
$ yarn build:css
お好きなwebサーバーを起動してpublic/index.html
を開きます。package.json
を汚したくなかったのでnpx
でずるして最小手数の体裁を保ちます。
$ npx http-server ./public
スタイルが何もあたっていない状態のwebサイトが完成しました。Tailwind CSSにはnormalize.cssが含まれている(v1.4.6時点)ので、リセット系CSSを別で用意する必要はありません。
準備が整ったので早速Tailwind CSS流にスタイルを当てていきます。
<body> <h2 class="mb-2 px-2 text-xl">New Todo</h2> <form class="mb-4 px-4"> <input type="text" class="p-2 border" /> <button class="ml-2 p-2 rounded text-white bg-blue-500">submit</button> </form> <h2 class="mb-2 px-2 text-xl">List Todo</h2> <ul class="py-2 px-4"> <li class="p-2 border"> <p class="border-b">todo 1</p> <p class="text-sm">2020 05/12</p> </li> <li class="mt-2 p-2 border"> <p class="border-b">todo 2</p> <p class="text-sm">2020 05/12</p> </li> <li class="mt-2 p-2 border"> <p class="border-b">todo 3</p> <p class="text-sm">2020 05/12</p> </li> </ul> </body>
大分それっぽくなりました。
使い方は見た目通りで、class
がそれぞれ特定のCSS定義として用意されています。
例えばp-4
ならpadding: 1rem;
、mt-2
ならmargin-top: 0.5rem;
といった感じです。インラインスタイルを簡略化したような使用感です。
デフォルトスタイル余談
Tailwind CSSを使う上でまず最初に注意したほうがよいことは、line-height
のデフォルト値です。デフォルトでline-height: 1.5;
がhtml
にあたっているので、全ての余白を自力で指定してピクセルパーフェクトを目指す場合は少し厄介です。
html
に反映されていることもあり、気にせずスタイルを当てていって途中で変更したくなってしまうと相当な被害になることが予想されます(私です)。それっぽいline-height
を全体に当てておくか、パーフェクトを目指して全てを自力で当てるかの方針はなるべく早期フェーズでの選択がおすすめです。
Vendor Prefixes余談
ターゲットとするブラウザ次第では必要になるであろう、みんな大好きVendor Prefixesです。結論を言ってしまうとTailwind CSS自体にはVendor Prefixes的な対応は入っていません。
それを解決するのはもっとうまくやれる他のツールに任せているというのが公式スタンスです。ドキュメントでもAutoprefixerとの併用が紹介されています。
この先はもうPostCSSの話題になってしまうので深入りはしませんが、Tailwind CSSをPostCSSのプラグインとして利用することも可能なので導入もそんなに手間ではありません。
ファイルサイズ問題
Tailwind CSSを活用する上で無視することのできない非常に重要な問題がファイルサイズです。
実際にtailwindcss build
を叩いてみた方なら、このような表示がされて既に嫌な予感を持っていたかもしれません。
🚀 Building... tailwind.css ✅ Finished in 1.56 s 📦 Size: 1.95MB 💾 Saved to public/style.css ✨ Done in 4.11s.
「📦 Size: 1.95MB」です。これは相当に大容量です。normalize.cssが含まれているとはいえ、scriptもfontも含まれていないただのCSSファイルでこれは流石に無視できるサイズではありません。
ファイルサイズが肥大化する理由は明白で、p-4
やmt-2
といったCSSのプロパティと数値の組み合わせが無数に存在するからです。さらにレスポンシブ対応でsm:p-4
なんて指定も用意されているので、それら全てが含まれていると考えれば膨れるのも当然なわけです。
実はあのBootstrapにもこのようなutility的なclass
群は存在しています。しかし用意されているものは必要最低限で、Tailwind CSSほどの汎用性も拡張性もありません。
内部事情までは知りませんが、おそらくファイルサイズの肥大化という問題は少なからず意識して絞っているのではないでしょうか。
Bootstrapがおそらく敢えて避けているであろう、ファイルサイズがひたすら肥大化していくutility的なclass
群という方向にTailwind CSSは振り切っているわけです。その方向に振り切る以上、便利さと引き換えにファイルサイズは諦めなければならない・・という時代もかつてはあったのかもしれません。しかし今は令和です。あれも欲しい、これも欲しいもっともっと欲しいを実現してくれる強力なツールが存在します。
PurgeCSSです。
PurgeCSS
まただよ、またフロントエンド開発環境に登場人物が増えたよ即ブラウザバックしかけた方はちょっとだけ待ってください。なんとTailwind CSSは最近のリリースでPurgeCSSも内包するようになったので、設定ファイルを微修正するだけです。Tailwind CSS陣営としても、ファイルサイズ肥大化は重要な問題で、その解決法を明示する必要があると判断したのでしょう。
デフォルトの設定ファイルから、purge
部分を少しだけ変更します。Tailwind CSSのclass
表現を使っているファイルが全て含まれるようにパスを指定します。そうすることで、そのファイル内で現れていない不要なclass
が全て削除されます。
tailwind.config.js
module.exports = { purge: ['./public/**/*.html'], theme: { extend: {}, }, variants: {}, plugins: [], }
パージ機能を有効にするにはNODE_ENV=production
の指定が必要です。早速実行してみます。
NODE_ENV=production yarn build:css
🚀 Building... tailwind.css ✅ Finished in 1.29 s 📦 Size: 12.08KB 💾 Saved to public/style.css ✨ Done in 1.87s.
CSSのファイルサイズ肥大化は、人類にとって最早克服された問題だったのです。PurgeCSSが存在するからこそTailwind CSSのutility-firstという方針が成立すると言っても過言ではないかもしれません。これぞシナジーです。Cookie Clickerをやり込んだ皆さんなら、シナジーが如何に強力かつ重要なのかは身をもって体験しているはずです。
続けて拡張の話題に移りますが、そこでもPurgeCSSという存在が控えていることが非常に重要となります。
PurgeCSS余談: 禁忌事項
PurgeCSSを使う上で、いつか足元を撃ち抜くかもしれない禁忌事項が1つあります。それは、「class
を必ず完全な形で記述する」ことです。
例えばfont-size
を動的に指定しようとして、こんな記述をしてしまうかもしれません。
fontSize = 'text-' + size; // size: 4 | 6 | 8
撃ち抜きました。完全に撃ち抜いて水中から氷の天井を見上げています。理由は単純で、PurgeCSSは正規表現によって、使われているclass
を探します。つまり動的に生成されたclass
は発見のしようがなく、無慈悲にproductionビルド時に削除されます。
多少遠回りになっても、class
を完全一致な文字列としてファイル内に記述しなければいけません。例えばこのように。
fontSize = { 4: 'text-4', 6: 'text-6', 8: 'text-8' };
PurgeCSSの要請からこのような完全一致で書く必要があるわけですが、CSSセレクタをこのように完全一致で書くことを習慣つけるのはものすごくおすすめです。以前似たような話題で開発ブログも書きました。
以前まではgrepがしにくいというやや個人的かもしれない理由だったんですが、今ではPurgeCSSの要請という強力な後ろ盾を得たのでバンバン推していきます。
拡張
デフォルトで用意されているclass
でも不便はあまりないんですが、どうしてもそれだけでは足りないシーンというのもあります。
例えばTailwind CSSはrem
ベースの指定が基本となっています。p-4
ならpadding: 1rem;'、
p-6なら
1.5rem`といった具合です。
なんかそれっぽい感あってrem
指定いいですよね。しかし残念ながら世の中そんな甘くなく、往々にしてpx
単位ベタ打ちのピクセルパーフェクトを求められてしまうことだってあります。px
をrem
に変換してなんとか表現するという努力も悪くないですが、なかなかに不毛な作業です。
そんなときはさくっとTailwind CSSを拡張してしまいましょう。
tailwind.config.js
module.exports = { purge: ['./public/**/*.html'], theme: { extend: { spacing: { // px単位 ...[...Array(120)].reduce((m, _, i) => { m[`${i}px`] = `${i}px` return m }, {}), }, }, }, variants: {}, plugins: [], }
豪快にp-1px
からp-120px
まで用意してみました。ピクセルパーフェクトし放題です。僅かばかりの良心で120px
にしましたが、必要な分だけ増やしてしまってください。
さて、こんなことをしたらファイルサイズがどんなことになるか想像はつくと思いますが、せっかくなのでそのままビルドしてみます。
🚀 Building... tailwind.css ✅ Finished in 4.59 s 📦 Size: 4.97MB 💾 Saved to public/style.css ✨ Done in 6.11s.
やばいですねぇ、これはやばい。それではオチも何もなく結果も見えていますがPurgeCSSを通したビルドを行なってみます。
🚀 Building... tailwind.css ✅ Finished in 4.42 s 📦 Size: 11.79KB 💾 Saved to public/style.css ✨ Done in 4.99s.
そういうことなんですね。Tailwind CSSの拡張は本来ならばファイルサイズ肥大とトレードオフで、神経すり減らしながら必要最小限になるよう調整しなければなりません。しかし今は令和です。我々の後ろにはPurgeCSSという対不要CSS最終防衛兵器が控えています。
ファイルサイズが2倍に膨れるようなこんな拡張を施しても、使わなかった分は全て削除されます。常に必要最小限の拡張が達成可能です。
レンダリング関数との組み合わせ
この頃流行りのライブラリと組み合わせてみます。最小手数と宣言してしまっているのでなるべく最小手数で使えそうなツールを探しました。探す手間は私が負ったので見逃してください。
サンプルコードが何の環境構築もなしに簡単に動いたので今回はPreactでいきたいと思います。まともに使った経験はないのでなんとなくで使っていきます。
Getting Startedにて紹介されている最小手数っぽい方法でさきほどのTodoページを書き換えてみます。リスト部分のみです。
<body class="p-2"> <script type="module"> import { h, render } from "https://unpkg.com/preact?module"; const li = i => h( 'li', { class: 'mt-2 p-2 border border-red-500' }, [ h('p', { class: 'border-b' }, `todo ${i}`), h('p', { class: 'text-sm' }, '2020 05/12'), ] ) const app = h('div', null, [ h('ul', { class: 'py-2 px-4 border border-red-500' }, [1, 2, 3].map(i => li(i))) ]); render(app, document.body); </script> </body>
jsxのセットアップをしだすと最小手数をはみ出しそうなのでh
関数でゴリゴリと書きます。ReactしかりVueしかりElmしかり大体同じ使い心地です。結局のところTailwindCSSを使うときはclass
の当て方にしか関心を持つ必要がないので、どんなツールを使おうが相性が悪くなることはないです。
レンダリングをscriptで制御できる利点の1つといえばリストをループでまとめて書けることです。ただループで回す欠点として、当然ですが全ての要素に同じclass
が当たります。
'mt-2 p-2 border border-red-500'
そうするとこのように、端っこの要素に付けたくないmargin
やborder
が付いてしまうことがよくあります。
まず浮かぶ解決策は普通にclass
を付けてcssを当てていく方法です。
.item:first-child { margin-top: 0; }
しかしせっかくTailwindCSS使っているのだから、見通しをよくするためにも独自のclass
は極力使いたくないという欲が出てきます。よし分かったcssを使いたくないなら、scriptで制御してしまえばよいではないか方針に切り替えます。
'p-2 border border-red-500' + (i === 0 ? '' : 'mt-2')
確かにこれで解決して世界は平和になったように見えるんですが、我々が求めているのは本当にこれだったのかという疑問が残ります。
そんなところで、TailwindCSSは新たな解を用意してくれています。
これを、
'mt-2 p-2 border border-red-500'
↓こうする。
'mt-2 p-2 border border-red-500 first:mt-0'
first:mt-0'
というclass
が増えました。見た通りです。これを付けると&:first-child { margin-top: 0; }
と同様な効果があり、リストの先頭要素だけmargin-top
を0
にすることができてしまいます。
後出しですが注意点として、このfirst:mt-0
という機能はデフォルト設定のままでは使えません。このようなprefixで制御するスタイル機能は複数あり、すべてを有効にすると相当なファイルサイズになってしまうからです。
有効にするにはvariantsという設定を拡張します。first
、last
、hover
など有用なものは揃っているので、気になったものはとりあえず有効にしちゃいましょう。使っていないものはどうせPurgeCSSで削除されます。
追加した設定はデフォルトのものとマージはされず、上書きされるので注意してください。
tailwind.config.js
module.exports = { purge: ['./public/**/*.html'], theme: { extend: {}, }, variants: { margin: ['responsive', 'first', 'last'], }, plugins: [], }
first:m-0
、first:m-1
、first:mt-0
という風に用意されるclass
が倍々で増えるので当然ですがcssファイルサイズも相当に膨れます。
🚀 Building... tailwind.css ✅ Finished in 1.69 s 📦 Size: 2.12MB 💾 Saved to public/style.css
はい、PurgeCSSの出番です。
🚀 Building... tailwind.css ✅ Finished in 1.44 s 📦 Size: 11.79KB 💾 Saved to public/style.css
レンダリング関数との組み合わせ: Elm余談
メドピアでElmは一切使われていないので完全に余談なんですが、Tailwind CSSはElmプロジェクトにものすごくおすすめです。
自分自身Elmは最近少し触っているくらいでそこまで詳しくはないという前置きをしておいて、script部分に関してはその徹底した関数型言語特性から非常に強力なんですが、スタイルに関しては重要視されていないのかあまりよい戦略が見つかりません。探せばなくもないんですが、模索中な段階だったりやたらとややこしかったりするものが多いです。
ベストプラクティスじゃなくてもいいからとにかく手軽にスタイルを当てたいんだということで、結局素のCSSファイルをindex.htmlで読み込んだり、インラインスタイルを使っていくとう場面が結構あるのではないでしょうか。そして古き良きweb開発におけるスタイル管理苦難の旅路を追体験していくわけです。
そんなあなたにTailwind CSS。
div [ class "p-2 border text-blue-500" ] [ text "hello world!" ]
ただclass
を提供するだけで特定フレームワークに依存しないので、もちろんElmとも相性ばっちりです。PurgeCSSは正規表現で使われているclass
名を探しているだけなので.html
でも.jsx
でも.elm
でもファイル形式は問題になりません。
インラインスタイルを超えて
Tailwind CSSの使い心地はインラインスタイルライクですが、そのポテンシャルはインラインスタイル特有の制約をものともしません。先ほども紹介した、first:mt-0
というprefix付きの指定方法(variants)がそれです。
インラインスタイルの泣き所として、first:
やhover:
といったセレクタや、レスポンシブのためのメディアクエリを使うことができません。まともなwebページを作る上でこれらの制約は致命的です。仕方ないから無理な部分だけCSSを別で作って対応したとしても、今度はインラインスタイルとCSSファイル内のスタイルが散らばって管理が面倒になっていきます。
一方でTailwind CSSはインラインスタイルライクではあっても中身はセレクタ指定のCSSなので、このような制約もうまく回避してくれています。
first:
やhover:
は先の例で既に示しました。そしてレスポンシブも同じく、xs:p-1
、sm:p-2
という風にprefixを付けるだけで分岐が可能です。
余談: そうはいっても万能ではなかった
残念ながら万能ではありません。実際に使ってみて、これは辛いなと感じたシーンもちょくちょくあります。
例えば親要素がhover
されたら子要素をdisplay: none;
にしたいなんていう場面です。1つ1つのDOMに対してclass
を当てていくという使い方になるので、親子であろうとDOMを跨いだスタイル制御をすることは現状だと厳しそうです。敗北した気分でしぶしぶCSSを書きましょう。
Tailwind CSS上級者の方々ならもしかしたら解決策を持っているかもしれません。求む情報発信。
実際に導入したNuxt.jsプロジェクトでのCSS比率余談
Tailwind CSS流のclass
だけで実際どこまでスタイルを作れるのかは気になる点だと思います。どうしても素のCSSを書かなければならない場面があったとしても、そういう場面が多すぎたらTailwind CSSの導入はかえってスタイル定義の散逸を招いてしまうからです。
ということでNuxt.js利用の実際のプロダクトで集計してみました。.vue
という拡張子のファイル153個に対して、<style
という文字列grepでヒットしたファイルが13個です。
その13個の内容はざっと見た限りこのようなものです。
- html全体にかかる
font-family
などの設定 <slot />
で挿入した要素に対してのスタイル指定<select>
へのappearance: none;
- アニメーション関係(
transition
、animation
、@keyframes
) - DOM跨ぎの
hover:
制御
少なくないといえば少ないかもしれませんが、前向きに捉えればこれら以外はすべてTailwind CSS流儀でカバーできているわけです。まずまずな結果ではないでしょうか。
ちなみにそのプロダクトというのはこちらです。こっそりデバッグツールで覗いてみてもらうとTailwind CSSの雰囲気が分かるかもしれません。
まとめ
近年のフロントエンド関連技術は激しく進化しまくっているものの、CSS関連の話題はどうしても置き去りにされやすいです。そうはいっても辛さは無視できないので様々な手法も考案されてきてはいますが、フレームワーク依存だったりまた新たな辛さが出てきたりとなかなか明る い未来は見えてきません。
そういう状況の中で、主観ベースですが、Tailwind CSSは過去最高に使い勝手が良かったです。インラインスタイルライクなのに制約が少なく拡張性が高い、そしてCSSファイルを管理する必要がほぼない。この特徴が非常に強力です。
CSS管理は諦めてTailwind CSSを使おう。現時点で私から提示できるCSS戦略のベストプラクティスです。
これは全く余談ではないんですがメドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら
https://medpeer.co.jp/recruit/workplace/development.html