`@custom-variant`のネスト時に発生する無限ループを解消
@custom-variant内で@variantを使用し、その@variantが別の@custom-variantを参照する場合に発生していた無限ループが解消されました。この修正により、カスタムバリアントのネストが安全に動作するようになります。
背景
@custom-variantのネスト機能において、特定の構成で無限ループが発生する問題が報告されていました(#19618)。以下のような設定でb:バリアントを使用すると、アプリケーションがクラッシュする現象です:
@custom-variant a {
@slot;
}
@custom-variant b {
@variant a {
@slot;
}
}
この問題の原因は、@custom-variantが内部の@slotをASTノードに置換する際、置換後のノードに再び@slotが含まれていると、その@slotを再度置換しようとすることで無限ループに陥る設計にありました。
技術的な変更
packages/tailwindcss/src/variants.tsのsubstituteAtSlot関数が修正され、@slotの置換後にそのノードのウォーク処理をスキップするようになりました。
変更前:
walk(ast, (node) => {
if (node.kind === 'at-rule' && node.name === '@slot') {
return WalkAction.Replace(nodes)
}
})
変更後:
walk(ast, (node) => {
if (node.kind === 'at-rule' && node.name === '@slot') {
return WalkAction.ReplaceSkip(nodes)
}
})
WalkAction.ReplaceからWalkAction.ReplaceSkipへの変更により、置換されたノード群のトラバースがスキップされます。この結果、最終的なASTには@slotノードが残りますが、次の@custom-variantの処理時に置換されるため問題になりません。
設計判断
置換後のノードをスキップする方式が採用されました。
PR内の説明では、置換直後のノードに含まれる@slotは後続の処理で解決されることが明記されています。これは、複数の@custom-variantが連鎖する場合でも、各段階で必要な置換のみを実行し、残りは次の段階に委ねる設計です。
一度の走査で全ての@slotを解決しようとするのではなく、段階的な処理を前提とすることで、ネストの深さに関わらず安定した動作を実現しています。
まとめ
本PRは、ASTウォーク時のアクションをWalkAction.ReplaceからWalkAction.ReplaceSkipに変更する1行の修正で無限ループを解消しています。この変更により、@custom-variantのネスト構造が任意の深さで安全に動作し、a:b:flexやb:a:flexのような複雑なバリアント組み合わせも正しく機能します。