v3から上げるだけだと思ってたんですよね、正直。
自分が関わっているプロジェクトでTailwindをv3からv4に上げる作業を進めたんですが、マイグレーションガイドを眺めた時点では「まあ数時間で終わるでしょ」と舐めていました。結果的に丸2日くらいかかったので、踏んだ地雷を忘れないうちに書き残しておきます。
公式ドキュメントの手順自体は綺麗に書いてあるんですが、実際にやってみるとドキュメントに出てこない落とし穴がちょこちょこあって、そのあたりは自分みたいに「ゼロから書き直すほどじゃない、既存プロジェクトを上げるだけ」という立場の人に刺さるはずです。
まずは @config から CSS-first config への移行で戸惑う
v3の頃は tailwind.config.js にテーマもプラグインも全部書いていました。v4からはCSSファイルに @theme を書く方式が公式推奨になっていて、コマンド一発で雰囲気は移行できます。
npx @tailwindcss/upgrade
このアップグレードツールは想像以上に優秀で、既存の tailwind.config.js を解析して、必要なCSS変数を @theme ブロックとして吐き出してくれます。自分のプロジェクトでは、ここまでは拍子抜けするくらい簡単に通りました。
ただ、落とし穴はここから。既存の tailwind.config.js をそのまま残したい場合、v4では @config ディレクティブで読み込む形になります。
@import "tailwindcss";
@config "../../tailwind.config.js";
公式ドキュメントにはこう書いてあるんですが、自分の環境ではこの書き方だと theme.extend.colors の一部が効いたり効かなかったりする現象が発生しました。結局、段階的に @theme に移した上で @config は捨てる、という方針にしたほうが精神衛生上よかったです。
theme() 関数の廃止が地味に効く
これが一番ハマりました。
v3で何気なく書いていた theme('colors.brand.500') みたいなやつ、v4では段階的に非推奨になっていて、CSS変数 var(--color-brand-500) に置き換える必要があります。
/* v3 */
.btn {
background-color: theme('colors.brand.500');
}
/* v4 */
.btn {
background-color: var(--color-brand-500);
}
問題は、これを単に文字列置換すればよい、という話ではなかったことです。theme() は計算式の中でも使えていたので、こんなのが紛れていました。
/* v3: theme() の戻り値を calc で加工していたパターン */
.card {
padding: calc(theme('spacing.4') + 2px);
}
CSS変数に置き換えたあとの挙動が微妙にズレて、カードの余白が数px変わって見た目の違和感だけ残る、みたいな件が何件か出ました。正規表現では検知しきれなくて、結局 theme( で全文検索して一個ずつ目視チェックするハメに。ここは改善の余地ありで、チーム開発なら事前にESLintルールでも仕込んでおくべきでした。
カスタムユーティリティが静かに壊れる
v3で @layer utilities に書いていたカスタムクラスが、v4で妙な挙動をする件もありました。具体的には、@utility ディレクティブという新しい書き方に移るのが推奨です。
/* v3 */
@layer utilities {
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
}
/* v4 */
@utility scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
見た目は似ていますが、v4の @utility は variants(hover: や md: など)が自動で効くようになっていて、これ自体はメリットです。ただ、v3スタイルのまま @layer utilities に残していると、特定のバリアントで効かないケースがあって、本番ビルドで初めて気づいたりします。ローカルだと気づきにくいんですよね。今思えば、ここでちゃんとしたビジュアルリグレッションテストを入れておくべきだった、というのが反省点です。
Vite プラグイン (@tailwindcss/vite) で開発体験が変わる
ここは素直に嬉しいポイントでした。v4から @tailwindcss/vite プラグインが提供されていて、PostCSS経由ではなくViteに直接組み込めます。
// vite.config.ts(または astro.config.mjs の vite 内)
import tailwindcss from '@tailwindcss/vite';
export default {
plugins: [tailwindcss()],
};
体感で一番変わったのは開発サーバーのHMRの速さで、CSS1行変えたときの反映がかなり早くなります。自分の手元の計測だとv3 + PostCSS構成で400ms弱だったのが、v4 + Viteプラグインで100ms台に落ちました。ここは好みが分かれるところですが、開発中にCSSを何十回も書き換える自分にはかなり効きます。
振り返って
実作業の時間で言えば、v4のコア機能のマイグレーション自体はそこまで大変じゃありませんでした。むしろ、v3の頃にチームで書き溜めた「ちょっとした工夫」が、そのままだと動かなくなるケースを一個ずつ潰す時間のほうが長かったです。
今後の予防策としては、メジャーアップグレードを見据えて、なるべく公式のAPIに寄せた書き方を普段から意識するようにしました。theme() の中で複雑な計算をしない、カスタムユーティリティは @utility 前提で書く、など。地味ですが、次のメジャーで同じ轍を踏まないための投資だと思っています。
もっと良い移行の進め方があったら教えてください。