任意値における単位の正規化を抑制し、`-mt-[20in]` → `mt-[-20in]` に改善

tailwindlabs/tailwindcss

キャノニカライゼーション処理において、任意値(arbitrary value)内の単位を基本単位へ変換しないよう修正されました。これにより、-mt-[20in]mt-[-1920px] という意図不明な値に変換されてしまう問題が解消されます。

背景

Tailwind CSS のキャノニカライゼーション機能は、等価なクラスを正規の形式に統一するものです。たとえば -mt-[20in]mt-[-20in] は意味的に同一であるため、後者の形式に正規化されます。この処理の一部として、シグネチャ計算のために単位を基本単位(px など)へ変換する処理が行われていました。

シグネチャ計算自体は正しく機能しており、たとえば .foo { margin-top: 20in; }.bar { margin-top: 1920px; } が同一シグネチャを持つと判定されるのは意図通りの動作です。しかし、この単位正規化のロジックが任意値の変換処理にも漏れ出していました。

tailwindcss-intellisense#1573 では、印刷用ドキュメントで in(インチ)単位を意図的に使用しているにもかかわらず、Tailwind CSS IntelliSense が -mt-[0.04in]mt-[-3.84px] と書き換えるよう提案するという問題が報告されました。px への変換はまったく「簡略化」になっておらず、かえって混乱を招く動作でした。

根本的な問題は、任意値内の - を値の中に移動する処理において、定数畳み込み(constant folding)と単位正規化が両方実行されていた点にあります。-mt-[20in]mt-[calc(20in_*_-1)] に展開したあと、この calc 式を評価する際に 20in1920px に変換されてしまい、最終的に mt-[-1920px] という値が生成されていました。

技術的な変更

constantFoldDeclarationAst 関数に normalizeUnit フラグが追加され、呼び出し側が単位正規化の有無を制御できるようになりました。

packages/tailwindcss/src/constant-fold-declaration.ts にて、constantFoldDeclarationconstantFoldDeclarationAst の両関数のシグネチャが変更されました。

変更前:

export function constantFoldDeclaration(input: string, rem: number | null = null): string
export function constantFoldDeclarationAst(
  ast: ValueParser.ValueAstNode[],
  rem: number | null = null,
): [folded: boolean, ast: ValueParser.ValueAstNode[]]

変更後:

export function constantFoldDeclaration(
  input: string,
  rem: number | null = null,
  normalizeUnit = true,
): string
export function constantFoldDeclarationAst(
  ast: ValueParser.ValueAstNode[],
  rem: number | null = null,
  normalizeUnit = true,
): [folded: boolean, ast: ValueParser.ValueAstNode[]]

canonicalizeDimension 内部関数も同様に normalizeUnit パラメータを受け取るよう拡張され、normalizeUnit = false の場合は単位変換をスキップする分岐が追加されています。

任意値の最適化を担う packages/tailwindcss/src/canonicalize-candidates.tsoptimizeArbitraryValueExpressions 関数では、constantFoldDeclarationAst の呼び出し箇所2か所に false を渡すよう変更されました。

変更前:

let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst)
// ...
let [folded, foldedExpressionAst] = constantFoldDeclarationAst(expressionAst)

変更後:

let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst, null, false)
// ...
let [folded, foldedExpressionAst] = constantFoldDeclarationAst(expressionAst, null, false)

これにより、任意値の処理では定数畳み込みのみが実行され、単位の変換は行われません。変換の結果は以下のようになります:

  • -mt-[20in]mt-[-20in](従来は mt-[-1920px]
  • -mt-[0.04in]mt-[-0.04in](従来は mt-[-3.84px]

デフォルト値は normalizeUnit = true のまま維持されており、シグネチャ計算など単位正規化が必要な既存の呼び出し箇所への影響はありません。

設計判断

normalizeUnit フラグによる関数の拡張という方式が採用されました。

単位正規化を行う処理と行わない処理を別関数として切り出す設計も考えられますが、今回は既存の constantFoldDeclarationAst を拡張するアプローチが選ばれています。デフォルト値を true にすることで、既存の呼び出し箇所への影響をゼロに抑えつつ、任意値処理の呼び出し側だけが false を明示的に渡す形になっています。

修正の対象はあくまで「任意値のキャノニカライゼーション」という文脈に限定されています。シグネチャ計算において .foo { margin-top: 20in; }.bar { margin-top: 1920px; } を同一とみなす動作は変更されておらず、単位正規化が必要な箇所と不要な箇所を関数の引数レベルで分離することで、それぞれの正しい動作を保っています。

まとめ

本PRは、定数畳み込みと単位正規化という2つの独立した処理が意図せず結合していたことで生じたバグを、normalizeUnit フラグによる制御の導入で解消したものです。任意値の文脈では単位をそのまま保持するという直感的な動作が回復され、incm など非px単位を意図的に使用するユースケースでも正しいキャノニカライゼーションが行われるようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
afc33501

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文→背景→技術詳細→設計判断→まとめ」という総論→各論→結論の構成が明確で、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト、GitHubのIssue/PRへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Tailwind CSSの内部実装に関する内容で、専門知識を持つエンジニアという対象読者に適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション・各パラグラフが総論→各論の構成になっており、トピックセンテンスが明確で読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコードブロックは、提供されたDiffの内容(関数シグネチャの変更、呼び出し箇所の引数追加)を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「キャノニカライゼーション」「定数畳み込み」など、PRで使われている技術用語を正確に使用しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

単位正規化が意図せず任意値の変換に影響していた問題と、それをフラグで制御する解決策について、技術的に正確かつ論理的に説明されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事のすべての主張は、PRのDescriptionやDiffの内容によって裏付けられています。「設計判断」セクションも、コードの変更から導き出される妥当な洞察であり、ハルシネーションはありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#19988)、Issue番号(#1573)、コード例の数値(20in, 1920pxなど)がすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRの主題「任意値の単位正規化の抑制」を具体例とともに的確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれない外部知識(バージョンのサポート状況やリリース日程など)の追記はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「修正されました」「解消されます」など、完了した変更を報告する時間表現が適切に使用されています。