空クラスの @utility 変換を防止しクラッシュを回避
この PR は、npx @tailwindcss/upgrade 実行時に空のクラス定義が @utility へ変換されて発生していたクラッシュを防止し、空ルールはそのまま @layer utilities に残すようにしています。
背景
@layer utilities 内に本体が無いクラス(例: .empty {})が存在すると、アップグレード処理は従来通り @utility empty {} に変換しますが、@utility は 少なくとも1つのプロパティノード を必要とするため、後続ステップで Error: @utility … is empty がスローされてプロセスが終了していました。
この挙動は、空ルール自体がブラウザに影響しないことから「削除すべき」でもありますが、利用者が外部の CSS を直接管理できないケースも想定されるため、変換前に安全にスキップする対策が必要とされました。
技術的な変更
migrate-at-layer-utilities.ts に空ルール判定ロジックを追加しました。具体的には isEmpty(node) により ノードが存在しないかコメントのみ の場合は処理対象から除外し、walk(atRule, ...) の冒頭で if (isEmpty(node)) return としています。
@@
-import { type AtRule, type Comment, type Plugin, type Rule } from 'postcss'
+import { type AtRule, type Comment, type Container, type Plugin, type Rule } from 'postcss'
@@
- if (node.type !== 'rule') return
+ if (node.type !== 'rule') return
+ if (isEmpty(node)) return
さらに、空ルールに付随するコメントを正しく除去するための commentsAfter 集計ロジックを導入し、toRemove 配列に追加して同時に削除できるようにしました。また、@utility のクローン生成後に空ルールが残らないよう、node.type === 'atrule' && node.nodes?.length === 0 および clone !== defaultsAtRule && node.type === 'rule' && node.nodes?.length === 0 の条件で削除を行います。
@@
- // Remove empty rules
- if ((node.type === 'rule' || node.type === 'atrule') && node.nodes?.length === 0) {
+ // Remove empty rules from `@utility` clones and empty wrapper at-rules
+ if (
+ (node.type === 'atrule' && node.nodes?.length === 0) ||
+ (clone !== defaultsAtRule && node.type === 'rule' && node.nodes?.length === 0)
+ ) {
node.remove()
}
テストコード migrate-at-layer-utilities.test.ts へも空クラスが @layer utilities に残ることを検証するケースが追加され、スナップショット期待値が更新されています。さらに、process の結果文字列を改行で囲むことで出力整形を統一しています。
@@
- .then((result) => result.css)
+ .then((result) => `\n${result.css.trim()}\n`)
CHANGELOG.md には本変更が "Upgrade: don't migrate empty class rules to invalid @utility rules" として記載され、リリースノートに反映されています。
設計判断
空ルールを 変換せずに残す 方針は、既存の @layer utilities 構造を崩さずにエラー回避を実現するという 最小侵入性 を重視した判断です。代替案としては新規設定キーや除外オプションを追加する案も議論されましたが、isEmpty 判定だけで済む実装は既存コードベースへの影響が最小で、後方互換性を保てます。
また、コメントの除去ロジックを統合したことで、空ルールの直前・直後にあるコメントが誤って残存する問題も同時に解決しています。これにより、アップグレード後の CSS が 余計なコメントや空ブロック を含まないクリーンな状態になります。
まとめ
本 PR は、@layer utilities 内の空クラスが @utility へ変換されることで発生していたクラッシュを防止し、空ルールはそのまま残す安全策を実装しました。判定ロジックの追加と削除条件の見直しにより、アップグレードツールの堅牢性が向上し、利用者は既存の CSS を変更せずに安心してマイグレーションできるようになります。