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.
This commit is contained in:
parent
aaaefe8b5d
commit
bb2f170514
3 changed files with 55 additions and 1 deletions
|
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fix crash in canonicalization step when handling utilities with empty property maps ([#19727](https://github.com/tailwindlabs/tailwindcss/pull/19727))
|
||||
- Skip full reload for server only modules scanned by client CSS when using `@tailwindcss/vite` ([#19745](https://github.com/tailwindlabs/tailwindcss/pull/19745))
|
||||
- Add support for Vite 8 in `@tailwindcss/vite` ([#19790](https://github.com/tailwindlabs/tailwindcss/pull/19790))
|
||||
- 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))
|
||||
|
||||
## [4.2.1] - 2026-02-23
|
||||
|
||||
|
|
|
|||
|
|
@ -1054,6 +1054,13 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
|
|||
// To completely different utility
|
||||
['w-4 h-4', 'size-4'],
|
||||
|
||||
// Goes beyond the default spacing scale that's being used in intellisense
|
||||
// for code completion. Since it's about bare values, we should still be
|
||||
// able to combine them.
|
||||
['w-123 h-123', 'size-123'],
|
||||
['w-128 h-128', 'size-128'], // `w-128` on its own would become `w-lg`
|
||||
['mt-123 mb-123', 'my-123'],
|
||||
|
||||
// Do not touch if not operating on the same variants
|
||||
['hover:w-4 h-4', 'hover:w-4 h-4'],
|
||||
|
||||
|
|
|
|||
|
|
@ -312,12 +312,52 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st
|
|||
}
|
||||
}
|
||||
|
||||
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 // Skip properties that are not relevant for the current candidate
|
||||
|
||||
for (let value of values) {
|
||||
result.get(property).get(value).add(replacement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// For each property, lookup other utilities that also set this property and
|
||||
// this exact value. If multiple properties are used, use the intersection of
|
||||
// each property.
|
||||
//
|
||||
// E.g.: `margin-top` → `mt-1`, `my-1`, `m-1`
|
||||
let otherUtilities = candidatePropertiesValues.map((propertyValues) => {
|
||||
let otherUtilities = candidatePropertiesValues.map((propertyValues, idx) => {
|
||||
let result: Set<string> | null = null
|
||||
for (let property of propertyValues.keys()) {
|
||||
let otherUtilities = new Set<string>()
|
||||
|
|
@ -327,6 +367,12 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st
|
|||
}
|
||||
}
|
||||
|
||||
for (let value of propertyValues.get(property)) {
|
||||
for (let candidate of dynamicUtilities.get(candidates[idx]).get(property).get(value)) {
|
||||
otherUtilities.add(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
if (result === null) result = otherUtilities
|
||||
else result = intersection(result, otherUtilities)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue