デフォルトスペーシングスケールを超えるベア値のcanonicalization改善
w-1234 h-1234 のようにデフォルトのスペーシングスケール上限(*-96)を超えるベア値を持つユーティリティも、size-1234 へと正しくcanonicalizeされるようになりました。これにより、任意の整数値を使った候補でも一貫した最適化が適用されます。
背景
canonicalization機能は、複数のユーティリティクラスを等価なより短い表現へまとめる最適化処理です。たとえば w-4 h-4 は size-4 に、mt-2 mb-2 は my-2 にまとめられます。この処理を実現するため、内部ではインテリセンス(コード補完)用のサジェストAPIを活用して、あるユーティリティが他のユーティリティと同じCSSプロパティ・値を生成するかを判定しています。
しかし、このサジェストAPIが返す候補は *-96 までに限定されていました。w-96 はサジェストの範囲内にあるためlookupテーブルに含まれますが、w-1234 は範囲外のため含まれません。結果として、w-1234 h-1234 のペアは同一値と認識されず、size-1234 へのcanonicalizeが行われませんでした。
この制約により、*-96 を超えるベア値を使うユーティリティは、等価な短縮形が存在するにもかかわらず最適化されないまま残っていました。
技術的な変更
canonicalize-candidates.ts に dynamicUtilities という DefaultMap が追加され、サジェスト範囲外のベア値を動的にlookupできる仕組みが導入されました。
従来の処理では、otherUtilities の生成はインテリセンス用のlookupテーブル(computeUtilitiesPropertiesLookup)の結果だけに依存していました。新たに追加された dynamicUtilities は、このlookupで結果が得られない候補に対して補完的に機能します。具体的な処理フローは以下の通りです:
- 対象候補のCSSプロパティセット(
relevantProperties)を取得する -
parseCandidateで候補をパースし、functionalかつnamedな値(ベア値)を持つ場合のみ処理する - 全ての
functionalユーティリティのrootを列挙し、自身以外のrootで同じ値の候補文字列(replacement)を生成する - その
replacementのプロパティ・値ペアがrelevantPropertiesと一致するかチェックし、一致すれば結果に追加する
let dynamicUtilities = new DefaultMap((candidate: string) => {
let result = new DefaultMap(
(_property: string) => new DefaultMap((_value: string) => new Set<string>()),
)
let relevantProperties = new Set(computeUtilitiesPropertiesLookup.get(candidate).keys())
if (relevantProperties.size === 0) return result
for (let parsedCandidate of parseCandidate(designSystem, candidate)) {
if (
parsedCandidate.kind !== 'functional' ||
parsedCandidate.value?.kind !== 'named' // Necessary for bare values
) {
continue
}
for (let root of designSystem.utilities.keys('functional')) {
if (root === parsedCandidate.root) continue // Skip self
let replacement = printUnprefixedCandidate(designSystem, {
...cloneCandidate(parsedCandidate),
root,
})
let propertyValues = computeUtilitiesPropertiesLookup.get(replacement)
for (let [property, values] of propertyValues) {
if (!relevantProperties.has(property)) continue
for (let value of values) {
result.get(property).get(value).add(replacement)
}
}
}
return result
}
return result
})
また、テストケースとして以下の3パターンが追加されています:
-
['w-123 h-123', 'size-123']— スケール上限超えの基本ケース -
['w-128 h-128', 'size-128']—w-128単体ではw-lgになる値でも、ペアとしてはベア値で統合されるケース -
['mt-123 mb-123', 'my-123']—size以外の短縮形への統合
設計判断
サジェストAPIへの依存を補完する形で動的処理を追加するアプローチが採用されました。
既存のlookupテーブルベースの仕組みを置き換えるのではなく、functional かつ named(ベア値)の場合にのみ動的処理を追加する設計になっています。parsedCandidate.value?.kind !== 'named' のガードにより、任意値(w-[100px] など)や別の種類の値には適用されず、処理対象を必要最小限に絞っています。
PR本文では、この追加ロジックによる性能への影響についても言及されており、CIでのタイムアウトを懸念してWindowsを含む全プラットフォームでのCI実行([ci-all])が実施されています。実測では有意な性能劣化はなかったとされており、全ユーティリティのrootを列挙する処理コストは許容範囲と判断されています。
まとめ
本PRは、インテリセンス用サジェストAPIの範囲制限がcanonicalizationの精度に与えていた影響を、動的なフォールバック処理で解消した変更です。DefaultMap の遅延評価と処理対象の絞り込みにより、性能を維持しつつスペーシングスケールを超えるベア値でも一貫したユーティリティの最適化を実現しています。