ということに仕事で困らされて、最近それなりの時間を持っていかれた記念のメモ。
とりあえずのワークアラウンドは見出したけど、あとでどこかの誰かがもっといい感じにやってくれへんかな〜って。
ViteのMPA
あえて書くまでもないけど、`vite.config.js`でこういう指定をするだけ。
const { resolve } = require('path') const { defineConfig } = require('vite') module.exports = defineConfig({ build: { rollupOptions: { input: { pageA: resolve(__dirname, 'pageA/index.html'), pageB: resolve(__dirname, 'pageB/index.html') } } } })
こうすると、
- `pageA/index.html`からなる一連の依存グラフによるページ
- `pageB/index.html`からなる一連の依存グラフによるページ
それぞれが生成されるようになる。
あとは互いを単なる`a[href]`でつなげれば、SPAではないけど、あたかも1つのアプリのように見せることができる。
これがなかなかに気の利くやつで、例えばこのアプリをReactでやってた場合、それはAでもBでも使われる共通のチャンクに入れて最適化してくれたりする。
AにはいろんなUIがあってバンドルサイズが多少ふくらんでも、BにそれがないならBは小さいままになる。いい感じ。
最適化されないCSS
最適化される = 冒頭で書いたやりたいことが実現できるということ。
正確には、ViteがデフォルトでサポートしてるCSS Modulesの仕組みだと、JSと一緒でちゃんと最適化される。
CSS in JSの場合も、もちろんそれはもはやJSなので、最適化される。
記事タイトルにもあるように、Tailwindを使った場合だけが問題。
というのを検証したリポジトリがこちら!
READMEに`vite build`のログを貼ってあるけど、AとBで同じサイズのCSSが出力されてしまってる・・。
Aではたくさんのユーティリティクラスを書いてて、Bではほとんど書いてないのにも関わらず。
原因
従来のCSSは、
- 使うものを自分で定義
- ソースにもそれをそのまま書く
- そしてバンドルする
いわばボトムアップのアプローチで、書き手にすべてが委ねられてた。
Tailwindはその反対で、
- デフォルトで莫大なカタログが用意されてる
- 必要なものをソースに書く
- 使われてるものだけをバンドルする
っていうトップダウンなアプローチになってる。
なので、Tailwindで最終的なバンドルサイズを削減するためには、使われてるものを明示して後は捨てるための`purge`の指定が必須。
さもないと、ものすごい数のユーティリティクラスがバンドルされてしまう。
で、この指定は`tailwind.config.js`で静的に対象ファイルのリストを定義する必要がある。`["./app1/**/*.jsx"]`みたいに。
そして、その`tailwind.config.js`が`postcss.config.js`から参照されて、それが`vite.config.js`で参照される。
ここに、「今どのページのためのCSSをバンドルしてるのか?」っていう実行コンテキストの概念がないのが原因。
ページAのためのCSSを処理してるなら、`purge`にはAで使ってるファイル群だけが列挙されてほしい。
しかし、設定ファイルがいわばグローバルなので、せめてもの抵抗として全ページを並べるしかない・・。
その結果できあがるのは、`全ページの最大公約数.css`であり、ぜんぜんUIのないページBにおいてもそれが使われてしまう・・というわけ。
そもそも、グローバルで後からふるい落とすっていうTailwindの考え方が、Viteやそれ系のLoader/Pluginの考え方にマッチしてないだけなのかなーとは思う。
実行コンテキストも何も、そんな概念にとらわれずに`.css`だけ処理すればいいはずちゃうんけ!って。私もそう思います。
ワークアラウンド
と言ってしまっていいのか微妙やけど、いちおう。
というのは、
というもの。
開発中は無駄なサイズを気にしないことにして、ビルド時にだけつじつま合わせをする作戦。
NODE_ENV=production ./node_modules/.bin/tailwindcss \ -i ./app2/style.css \ -o ./dist/assets/app2.74ba676f.css \ --minify \ --purge ./app2/**/*.jsx
こういうコマンドのイメージ。
実際には上書きするファイル名は動的に変わるので、スクリプトでやるにはちゃんと書き換えが必要ではある。
ちなみにNextの場合も考えてみたけど、たぶん同様にページ単位で最適化することはできなくて、アプリ単位になっちゃうと予想。
あ〜誰かなんとかしてくれますように☆(別にTailwind推しでもなんでもない勢より)