`@utility`名でエスケープ文字を許容し、Biomeなどのフォーマッターとの互換性を向上
#19607で報告された、@utilityでエスケープ済みのユーティリティ名が使えない問題が解消されました。これにより、BiomeなどのCSSパーサーとの互換性を保ちながら、スラッシュやパーセント記号を含むユーティリティを定義できるようになります。
背景
Tailwind CSS v4では、@utilityディレクティブでカスタムユーティリティを定義できます。@utility push-1/2のようにスラッシュを含む名前を指定すると、Tailwind CSSが自動的にエスケープして.push-1\/2というクラスを生成していました。
しかし、CSSの仕様上スラッシュは演算子として扱われるため、@utility push-1/2という記述は構文的に不正です。Biomeのような厳格なCSSパーサーを使用すると、この記述でパースエラーが発生していました。回避策として@utility push-1\/2のように事前にエスケープすると、今度はTailwind CSSが「Utilities should be alphanumeric and start with a lowercase letter」というエラーを返していました。
この問題により、コードフォーマッターやリンターのCSSパース機能と、Tailwind CSSの独自構文の両立が困難になっていました。
技術的な変更
packages/tailwindcss/src/utilities.tsのcreateCssUtility関数が修正され、@utilityのパラメータからエスケープ文字を除去するようになりました。
変更前:
export function createCssUtility(node: AtRule) {
let name = node.params
// ...
}
変更後:
export function createCssUtility(node: AtRule) {
// Allow escaped characters in the name for compatibility with formatters and
// other parsers, to ensure valid CSS syntax. E.g.: `@utility foo-1\/2`.
//
// Note: the actual utility will be `foo-1/2`
let name = unescape(node.params)
// ...
}
unescape関数により、@utility push-1\/2というパラメータからpush-1/2という名前が抽出されます。この名前に対して既存のエスケープ処理が適用されるため、最終的に.push-1\/2というクラスが生成されます。
追加されたテストケースでは、スラッシュとパーセント記号の両方のエスケープに対応していることが検証されています:
test('@utility can handle escape sequences correctly', async () => {
let { build } = await compile(css`
@layer utilities {
@tailwind utilities;
}
@utility push-1\/2 {
right: 50%;
}
@utility push-50\% {
right: 50%;
}
`)
let compiled = build(['push-1/2', 'push-50%'])
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
"@layer utilities {
.push-1\\/2, .push-50\\% {
right: 50%;
}
}"
`)
})
@utility push-1\/2と@utility push-50\%の両方が正しく処理され、同一の出力を生成することが確認できます。
設計判断
入力段階でのエスケープ除去という実装方式が採用されました。
PR内のコメントでは「バックスラッシュを単純に無視すればよい」と述べられています。これは、エスケープ文字を取り除いた後は既存の処理フローをそのまま利用できるという判断です。unescape関数を追加するだけで、名前の検証ロジックやクラス生成ロジックに変更を加える必要がありません。
この設計により、@utility push-1/2と@utility push-1\/2は完全に同じ結果を生成します。開発者は、使用するツールチェーンに応じてどちらの記法も選択できます。Biomeのような厳格なパーサーを使う場合はエスケープ済みの記法を、そうでない場合は読みやすい非エスケープ記法を使えます。
本PRは、Tailwind CSSの構文を拡張するのではなく、既存の構文の入力形式を柔軟にすることで、エコシステム全体との互換性を高めた変更といえます。@utilityの名前処理にワンステップ追加するだけで、多様なツールチェーンとの共存が可能になりました。