Skip to content

Commit bb2f170

Browse files
authored
Improve canonicalization for bare values exceeding default spacing scale suggestions (#19809)
This PR adds support for canonicalization of utilities that accept bare values and exceed the default spacing scale we use for intellisense. Right now, all utilities are behind functions, so the only way to know whether something compiles is by compiling a candidate, e.g. `w-8` and passing it to the utility functions. To help us, we use the intellisense APIs that we use for suggestions. Most utilities that accept bare values, have suggestions up until `*-96`, so `w-96 h-96` would be canonicalized to `size-96`. But the moment we exceed that, the result stays as-is. ``` → w-96 h-96 = size-96 → w-1234 h-1234 = h-1234 w-1234 ``` This PR ensures that the last scenario also gets canonicalized to `size-1234` instead of staying as `h-1234 w-1234`. ``` → w-96 h-96 = size-96 → w-1234 h-1234 = size-1234 ``` ## Test plan 1. Existing tests pass 2. Added new tests for utilities with bare values [ci-all] just to see if this additional logic doesn't cause timeouts in CI for WIndows. In my testing this doesn't have a significant impact on performance at all.
1 parent aaaefe8 commit bb2f170

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- Fix crash in canonicalization step when handling utilities with empty property maps ([#19727](https://github.com/tailwindlabs/tailwindcss/pull/19727))
1919
- Skip full reload for server only modules scanned by client CSS when using `@tailwindcss/vite` ([#19745](https://github.com/tailwindlabs/tailwindcss/pull/19745))
2020
- Add support for Vite 8 in `@tailwindcss/vite` ([#19790](https://github.com/tailwindlabs/tailwindcss/pull/19790))
21+
- Improve canonicalization for bare values exceeding default spacing scale suggestions (e.g. `w-1234 h-1234``size-1234`) ([#19809](https://github.com/tailwindlabs/tailwindcss/pull/19809))
2122

2223
## [4.2.1] - 2026-02-23
2324

packages/tailwindcss/src/canonicalize-candidates.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,13 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
10541054
// To completely different utility
10551055
['w-4 h-4', 'size-4'],
10561056

1057+
// Goes beyond the default spacing scale that's being used in intellisense
1058+
// for code completion. Since it's about bare values, we should still be
1059+
// able to combine them.
1060+
['w-123 h-123', 'size-123'],
1061+
['w-128 h-128', 'size-128'], // `w-128` on its own would become `w-lg`
1062+
['mt-123 mb-123', 'my-123'],
1063+
10571064
// Do not touch if not operating on the same variants
10581065
['hover:w-4 h-4', 'hover:w-4 h-4'],
10591066

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,52 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st
312312
}
313313
}
314314

315+
let dynamicUtilities = new DefaultMap((candidate: string) => {
316+
let result = new DefaultMap(
317+
(_property: string) => new DefaultMap((_value: string) => new Set<string>()),
318+
)
319+
320+
let relevantProperties = new Set(computeUtilitiesPropertiesLookup.get(candidate).keys())
321+
if (relevantProperties.size === 0) return result
322+
323+
for (let parsedCandidate of parseCandidate(designSystem, candidate)) {
324+
if (
325+
parsedCandidate.kind !== 'functional' ||
326+
parsedCandidate.value?.kind !== 'named' // Necessary for bare values
327+
) {
328+
continue
329+
}
330+
331+
for (let root of designSystem.utilities.keys('functional')) {
332+
if (root === parsedCandidate.root) continue // Skip self
333+
334+
let replacement = printUnprefixedCandidate(designSystem, {
335+
...cloneCandidate(parsedCandidate),
336+
root,
337+
})
338+
339+
let propertyValues = computeUtilitiesPropertiesLookup.get(replacement)
340+
for (let [property, values] of propertyValues) {
341+
if (!relevantProperties.has(property)) continue // Skip properties that are not relevant for the current candidate
342+
343+
for (let value of values) {
344+
result.get(property).get(value).add(replacement)
345+
}
346+
}
347+
}
348+
349+
return result
350+
}
351+
352+
return result
353+
})
354+
315355
// For each property, lookup other utilities that also set this property and
316356
// this exact value. If multiple properties are used, use the intersection of
317357
// each property.
318358
//
319359
// E.g.: `margin-top` → `mt-1`, `my-1`, `m-1`
320-
let otherUtilities = candidatePropertiesValues.map((propertyValues) => {
360+
let otherUtilities = candidatePropertiesValues.map((propertyValues, idx) => {
321361
let result: Set<string> | null = null
322362
for (let property of propertyValues.keys()) {
323363
let otherUtilities = new Set<string>()
@@ -327,6 +367,12 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st
327367
}
328368
}
329369

370+
for (let value of propertyValues.get(property)) {
371+
for (let candidate of dynamicUtilities.get(candidates[idx]).get(property).get(value)) {
372+
otherUtilities.add(candidate)
373+
}
374+
}
375+
330376
if (result === null) result = otherUtilities
331377
else result = intersection(result, otherUtilities)
332378

0 commit comments

Comments
 (0)