Collapse more utilities by expanding their declarations (#19842)
This PR adds more declaration expansions such that we can collapse more utilities. While testing #19837 I noticed that in my tests some utilities weren't canonicalized correctly. As part of that PR, we check for `parsedCandidate.value === null`, which means that a functional utility without a value is skipped. We do have utilities like that such as `border` (which is equivalent to `border-1`). But while testing, I noticed that `border-x border-y` should collapse to `border` but they didn't. This PR fixes that. By expanding these properties to their long-form physical properties (instead of the shorter logical properties) we make the signatures of utilities a bit bigger, but also more correct such that we can collapse the physical form into logical utilities. To make this more concrete, this PR allows for the following canonicalizations now: | Input | Output | | --- | --- | | `border-t-123 border-r-123 border-b-123 border-l-123` | `border-123` | | `border-t-1 border-r-1 border-b-1 border-l-1` | `border` | | `border-t-123 border-b-123` | `border-y-123` | | `border-l-123 border-r-123` | `border-x-123` | | `border-t-red-500 border-r-red-500 border-b-red-500 border-l-red-500` | `border-red-500` | | `border-t-red-500 border-b-red-500` | `border-y-red-500` | | `border-l-red-500 border-r-red-500` | `border-x-red-500` | | `scroll-mt-123 scroll-mr-123 scroll-mb-123 scroll-ml-123` | `scroll-m-123` | | `scroll-mt-123 scroll-mb-123` | `scroll-my-123` | | `scroll-ml-123 scroll-mr-123` | `scroll-mx-123` | | `scroll-pt-123 scroll-pr-123 scroll-pb-123 scroll-pl-123` | `scroll-p-123` | | `scroll-pt-123 scroll-pb-123` | `scroll-py-123` | | `scroll-pl-123 scroll-pr-123` | `scroll-px-123` | | `overflow-x-hidden overflow-y-hidden` | `overflow-hidden` | | `overscroll-x-contain overscroll-y-contain` | `overscroll-contain` | ## Test plan 1. Existing tests pass 2. Added a few more tests to verify that these canonicalizations work
This commit is contained in:
parent
b55d96002c
commit
28d526859d
4 changed files with 513 additions and 3 deletions
|
|
@ -16,7 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Improve canonicalizations for `tracking-*` utilities ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827))
|
||||
- Fix crash due to invalid characters in candidate ([#19829](https://github.com/tailwindlabs/tailwindcss/pull/19829))
|
||||
- Ensure query params in imports are considered unique resources when using `@tailwindcss/webpack` ([#19723](https://github.com/tailwindlabs/tailwindcss/pull/19723))
|
||||
- Collapse arbitrary values into shorthand utilities during canonicalization ([#19837](https://github.com/tailwindlabs/tailwindcss/pull/19837))
|
||||
- Canonicalization: collapse arbitrary values into shorthand utilities (e.g. `px-[1.2rem] py-[1.2rem]` → `p-[1.2rem]`) ([#19837](https://github.com/tailwindlabs/tailwindcss/pull/19837))
|
||||
- Canonicalization: collapse `border-{t,b}-*` into `border-y-*`, `border-{l,r}-*` into `border-x-*`, and `border-{t,r,b,l}-*` into `border-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842))
|
||||
- Canonicalization: collapse `scroll-m{t,b}-*` into `scroll-my-*`, `scroll-m{l,r}-*` into `scroll-mx-*`, and `scroll-m{t,r,b,l}-*` into `scroll-m-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842))
|
||||
- Canonicalization: collapse `scroll-p{t,b}-*` into `scroll-py-*`, `scroll-p{l,r}-*` into `scroll-px-*`, and `scroll-p{t,r,b,l}-*` into `scroll-p-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842))
|
||||
- Canonicalization: collapse `overflow-{x,y}-*` into `overflow-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842))
|
||||
- Canonicalization: collapse `overscroll-{x,y}-*` into `overscroll-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842))
|
||||
|
||||
## [4.2.2] - 2026-03-18
|
||||
|
||||
|
|
|
|||
|
|
@ -1044,9 +1044,21 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
|
|||
test.each([
|
||||
// 4 to 1
|
||||
['mt-1 mr-1 mb-1 ml-1', 'm-1'],
|
||||
['border-t-123 border-r-123 border-b-123 border-l-123', 'border-123'],
|
||||
['border-t-1 border-r-1 border-b-1 border-l-1', 'border'], // `border` is shorter than `border-1`
|
||||
['border-t-red-500 border-r-red-500 border-b-red-500 border-l-red-500', 'border-red-500'],
|
||||
['scroll-mt-1 scroll-mr-1 scroll-mb-1 scroll-ml-1', 'scroll-m-1'],
|
||||
['scroll-pt-1 scroll-pr-1 scroll-pb-1 scroll-pl-1', 'scroll-p-1'],
|
||||
|
||||
// 2 to 1
|
||||
['mt-1 mb-1', 'my-1'],
|
||||
['border-t-123 border-b-123', 'border-y-123'],
|
||||
['border-t-1 border-b-1', 'border-y'], // `border-y` is shorter than `border-y-1`
|
||||
['border-t-red-500 border-b-red-500', 'border-y-red-500'],
|
||||
['scroll-mt-1 scroll-mb-1', 'scroll-my-1'],
|
||||
['scroll-pt-1 scroll-pb-1', 'scroll-py-1'],
|
||||
['overflow-x-hidden overflow-y-hidden', 'overflow-hidden'],
|
||||
['overscroll-x-contain overscroll-y-contain', 'overscroll-contain'],
|
||||
|
||||
// Different order as above
|
||||
['mb-1 mt-1', 'my-1'],
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function expand(input: string, options: SignatureFeatures): string {
|
|||
describe('expand declarations', () => {
|
||||
let options = SignatureFeatures.ExpandProperties
|
||||
|
||||
test('expand to 4 properties', () => {
|
||||
test('inset', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
inset: 10px;
|
||||
|
|
@ -71,7 +71,7 @@ describe('expand declarations', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('expand to 2 properties', () => {
|
||||
test('gap', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
gap: 10px;
|
||||
|
|
@ -95,6 +95,294 @@ describe('expand declarations', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('border-width', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.two {
|
||||
border-width: 1px 2px;
|
||||
}
|
||||
|
||||
.three {
|
||||
border-width: 1px 2px 3px;
|
||||
}
|
||||
|
||||
.four {
|
||||
border-width: 1px 2px 3px 4px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
.two {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 2px;
|
||||
}
|
||||
.three {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 3px;
|
||||
border-left-width: 2px;
|
||||
}
|
||||
.four {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 2px;
|
||||
border-bottom-width: 3px;
|
||||
border-left-width: 4px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-style', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.two {
|
||||
border-style: solid dashed;
|
||||
}
|
||||
|
||||
.three {
|
||||
border-style: solid dashed dotted;
|
||||
}
|
||||
|
||||
.four {
|
||||
border-style: solid dashed dotted double;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
border-top-style: solid;
|
||||
border-right-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-left-style: solid;
|
||||
}
|
||||
.two {
|
||||
border-top-style: solid;
|
||||
border-right-style: dashed;
|
||||
border-bottom-style: solid;
|
||||
border-left-style: dashed;
|
||||
}
|
||||
.three {
|
||||
border-top-style: solid;
|
||||
border-right-style: dashed;
|
||||
border-bottom-style: dotted;
|
||||
border-left-style: dashed;
|
||||
}
|
||||
.four {
|
||||
border-top-style: solid;
|
||||
border-right-style: dashed;
|
||||
border-bottom-style: dotted;
|
||||
border-left-style: double;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-color', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.two {
|
||||
border-color: red green;
|
||||
}
|
||||
|
||||
.three {
|
||||
border-color: red green blue;
|
||||
}
|
||||
|
||||
.four {
|
||||
border-color: red green blue black;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
border-top-color: red;
|
||||
border-right-color: red;
|
||||
border-bottom-color: red;
|
||||
border-left-color: red;
|
||||
}
|
||||
.two {
|
||||
border-top-color: red;
|
||||
border-right-color: green;
|
||||
border-bottom-color: red;
|
||||
border-left-color: green;
|
||||
}
|
||||
.three {
|
||||
border-top-color: red;
|
||||
border-right-color: green;
|
||||
border-bottom-color: blue;
|
||||
border-left-color: green;
|
||||
}
|
||||
.four {
|
||||
border-top-color: red;
|
||||
border-right-color: green;
|
||||
border-bottom-color: blue;
|
||||
border-left-color: black;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-margin', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
scroll-margin: 1px;
|
||||
}
|
||||
|
||||
.two {
|
||||
scroll-margin: 1px 2px;
|
||||
}
|
||||
|
||||
.three {
|
||||
scroll-margin: 1px 2px 3px;
|
||||
}
|
||||
|
||||
.four {
|
||||
scroll-margin: 1px 2px 3px 4px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
scroll-margin-top: 1px;
|
||||
scroll-margin-right: 1px;
|
||||
scroll-margin-bottom: 1px;
|
||||
scroll-margin-left: 1px;
|
||||
}
|
||||
.two {
|
||||
scroll-margin-top: 1px;
|
||||
scroll-margin-right: 2px;
|
||||
scroll-margin-bottom: 1px;
|
||||
scroll-margin-left: 2px;
|
||||
}
|
||||
.three {
|
||||
scroll-margin-top: 1px;
|
||||
scroll-margin-right: 2px;
|
||||
scroll-margin-bottom: 3px;
|
||||
scroll-margin-left: 2px;
|
||||
}
|
||||
.four {
|
||||
scroll-margin-top: 1px;
|
||||
scroll-margin-right: 2px;
|
||||
scroll-margin-bottom: 3px;
|
||||
scroll-margin-left: 4px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-padding', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
scroll-padding: 1px;
|
||||
}
|
||||
|
||||
.two {
|
||||
scroll-padding: 1px 2px;
|
||||
}
|
||||
|
||||
.three {
|
||||
scroll-padding: 1px 2px 3px;
|
||||
}
|
||||
|
||||
.four {
|
||||
scroll-padding: 1px 2px 3px 4px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
scroll-padding-top: 1px;
|
||||
scroll-padding-right: 1px;
|
||||
scroll-padding-bottom: 1px;
|
||||
scroll-padding-left: 1px;
|
||||
}
|
||||
.two {
|
||||
scroll-padding-top: 1px;
|
||||
scroll-padding-right: 2px;
|
||||
scroll-padding-bottom: 1px;
|
||||
scroll-padding-left: 2px;
|
||||
}
|
||||
.three {
|
||||
scroll-padding-top: 1px;
|
||||
scroll-padding-right: 2px;
|
||||
scroll-padding-bottom: 3px;
|
||||
scroll-padding-left: 2px;
|
||||
}
|
||||
.four {
|
||||
scroll-padding-top: 1px;
|
||||
scroll-padding-right: 2px;
|
||||
scroll-padding-bottom: 3px;
|
||||
scroll-padding-left: 4px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overflow', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.two {
|
||||
overflow: hidden visible;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
overflow-x: clip;
|
||||
overflow-y: clip;
|
||||
}
|
||||
.two {
|
||||
overflow-x: hidden;
|
||||
overflow-y: visible;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('overscroll-behavior', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.two {
|
||||
overscroll-behavior: auto contain;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".one {
|
||||
overscroll-behavior-x: none;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
.two {
|
||||
overscroll-behavior-x: auto;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('expansion with `!important`', () => {
|
||||
let input = css`
|
||||
.one {
|
||||
|
|
@ -147,6 +435,22 @@ describe('expand declarations', () => {
|
|||
describe('expand logical properties', () => {
|
||||
let options = SignatureFeatures.ExpandProperties | SignatureFeatures.LogicalToPhysical
|
||||
|
||||
test('margin-inline', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
margin-inline: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
margin-left: 10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('margin-block', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
|
|
@ -162,4 +466,164 @@ describe('expand logical properties', () => {
|
|||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('padding-inline', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
padding-inline: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('padding-block', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
padding-block: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-margin-inline', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
scroll-margin-inline: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
scroll-margin-left: 10px;
|
||||
scroll-margin-right: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-margin-block', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
scroll-margin-block: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
scroll-margin-top: 10px;
|
||||
scroll-margin-bottom: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-padding-inline', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
scroll-padding-inline: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
scroll-padding-left: 10px;
|
||||
scroll-padding-right: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('scroll-padding-block', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
scroll-padding-block: 10px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
scroll-padding-top: 10px;
|
||||
scroll-padding-bottom: 20px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-inline-width', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
border-inline-width: 1px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-block-width', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
border-block-width: 1px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
border-bottom-width: 1px;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-inline-style', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
border-inline-style: 1px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
border-left-style: 1px;
|
||||
border-right-style: 1px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('border-block-style', () => {
|
||||
let input = css`
|
||||
.example {
|
||||
border-block-style: 1px;
|
||||
}
|
||||
`
|
||||
|
||||
expect(expand(input, options)).toMatchInlineSnapshot(`
|
||||
".example {
|
||||
border-bottom-style: 1px;
|
||||
border-top-style: 1px;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -35,7 +35,32 @@ let VARIADIC_EXPANSION_MAP: Record<string, Record<number, [prop: string, index:
|
|||
inset: createBareQuad(),
|
||||
margin: createPrefixedQuad('margin'),
|
||||
padding: createPrefixedQuad('padding'),
|
||||
'scroll-margin': createPrefixedQuad('scroll-margin'),
|
||||
'scroll-padding': createPrefixedQuad('scroll-padding'),
|
||||
'border-width': createPrefixedQuad(
|
||||
'border',
|
||||
'top-width',
|
||||
'right-width',
|
||||
'bottom-width',
|
||||
'left-width',
|
||||
),
|
||||
'border-style': createPrefixedQuad(
|
||||
'border',
|
||||
'top-style',
|
||||
'right-style',
|
||||
'bottom-style',
|
||||
'left-style',
|
||||
),
|
||||
'border-color': createPrefixedQuad(
|
||||
'border',
|
||||
'top-color',
|
||||
'right-color',
|
||||
'bottom-color',
|
||||
'left-color',
|
||||
),
|
||||
gap: createPair('row-gap', 'column-gap'),
|
||||
overflow: createPair('overflow-x', 'overflow-y'),
|
||||
'overscroll-behavior': createPair('overscroll-behavior-x', 'overscroll-behavior-y'),
|
||||
}
|
||||
|
||||
// Depending on the length of the value, map to different properties
|
||||
|
|
@ -49,6 +74,10 @@ let VARIADIC_LOGICAL_EXPANSION_MAP: Record<
|
|||
'margin-inline': createPair('margin-left', 'margin-right'),
|
||||
'padding-block': createPair('padding-top', 'padding-bottom'),
|
||||
'padding-inline': createPair('padding-left', 'padding-right'),
|
||||
'scroll-margin-block': createPair('scroll-margin-top', 'scroll-margin-bottom'),
|
||||
'scroll-margin-inline': createPair('scroll-margin-left', 'scroll-margin-right'),
|
||||
'scroll-padding-block': createPair('scroll-padding-top', 'scroll-padding-bottom'),
|
||||
'scroll-padding-inline': createPair('scroll-padding-left', 'scroll-padding-right'),
|
||||
}
|
||||
|
||||
// The entire value is mapped to each property
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue