`calc(var(--spacing)*…)` 式を `--spacing(…)` へ正規化
Tailwind CSS v4 において、任意値内の calc(var(--spacing)*…) 式を --spacing(…) 関数記法へ自動的に正規化する機能が追加されました。これにより、コピーペーストや他ツールからのコード移行時に生じる冗長な記述が、より簡潔な公式記法へと統一されます。
背景
Tailwind CSS v4 では、スペーシングスケールへのアクセスに --spacing(8) のような CSS カスタム関数記法 が導入されています。しかし、v3 からの移行時や CSS の calc() を使った計算式をそのまま任意値として貼り付ける場面では、calc(var(--spacing)*8) のような形式が用いられることがあります。
この calc(var(--spacing)*…) 形式は意味的には --spacing(…) と等価ですが、クラス名が正規化されないままだと、同じスタイルを表す複数の記法が混在することになります。これはクラス名の重複排除や IDE の補完精度にも影響します。
技術的な変更
正規化処理のエントリーポイントとして、UTILITY_CANONICALIZATIONS パイプラインに calcToSpacingFunction が追加されました。このパイプラインは既存の themeToVarUtility や arbitraryUtilities と同列に並び、候補クラス名の正規化フローの一部として実行されます。
const UTILITY_CANONICALIZATIONS: UtilityCanonicalizationFunction[] = [
bgGradientToLinear,
themeToVarUtility,
+ calcToSpacingFunction,
arbitraryUtilities,
bareValueUtilities,
deprecatedUtilities,
]
calcToSpacingFunction は候補の種別に応じて処理を分岐します。kind === 'arbitrary'(例: [padding-top:calc(…)])の場合はクラス全体の値を、kind === 'functional' かつ値が任意値(例: pt-[calc(…)])の場合は値部分のみを、それぞれ spacingCalcToSpacingFunction に渡します。
function calcToSpacingFunction(
candidate: Candidate,
options: InternalCanonicalizeOptions,
): Candidate {
if (candidate.kind === 'arbitrary') {
candidate.value = spacingCalcToSpacingFunction(candidate.value, options.designSystem)
} else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') {
candidate.value.value = spacingCalcToSpacingFunction(
candidate.value.value,
options.designSystem,
)
}
return candidate
}
核心となる spacingCalcToSpacingFunction は ValueParser で入力文字列を AST に変換し、calc(…) 関数ノードを探索します。見つかったノードが calc(var(--spacing) * <value>) の構造(5 ノード: var 関数・空白・*・空白・値)に一致する場合のみ、--spacing(<value>) へと書き換えます。プレフィックスが設定されている場合は --{prefix}-spacing を対象とします。
正規化の結果は以下の通りです:
| 変換前 | 変換後 |
|---|---|
pt-[calc(var(--spacing)*8)] |
pt-8 |
pt-[calc(var(--spacing)*var(--other))] |
pt-[--spacing(var(--other))] |
pt-[min(20%,calc(var(--spacing)*8))] |
pt-[min(20%,--spacing(8))] |
[padding-top:calc(var(--spacing)*8)] |
pt-8 |
[padding-top:min(20%,calc(var(--spacing)*var(--other)))] |
pt-[min(20%,--spacing(var(--other)))] |
数値リテラルとの乗算(*8)は既存のスペーシングスケール値と照合され、pt-8 のようなユーティリティクラスへと完全に畳み込まれます。一方、var(--other) のような動的な値との乗算は --spacing(var(--other)) として任意値内に保持されます。
設計判断
AST の構造的一致による変換条件の厳格化 が採用されています。node.nodes.length !== 5 のチェックにより、calc(var(--spacing) * 2 + 1px) のような複合式には変換を適用しません。これは誤変換を防ぐための保守的な設計です。
また、spacingCalcToSpacingFunction は designSystem.theme.prefix を参照してスペーシング変数名を動的に解決します。これにより、Tailwind のプレフィックス機能を利用しているプロジェクトでも正しく動作します。変換ロジックを独立した純粋関数として切り出し、arbitrary と functional の両ケースで共有している点も、単一責任の観点から整合的な設計といえます。
まとめ
本変更は、calc(var(--spacing)*…) という等価だが冗長な記法を正規化パイプラインに追加することで、クラス名の一貫性を保証する変更です。AST ベースの厳格なパターンマッチングにより誤変換リスクを排除しつつ、プレフィックス対応も含めた堅牢な実装となっており、v3 からの移行コードや外部ツールが生成したクラス名をそのまま取り込めるユースケースへの対応が強化されています。