Skip to content

Commit 113142a

Browse files
authored
Use amount of properties when sorting (#16715)
Right now we sort the nodes based on a pre-defined sort order based on the properties that are being used. The property sort order is defined in a list we maintain. We also have to make sure that the property count is taken into account such that if all the "sorts" are the same, that we fallback to the property count. Most amount of properties should be first such that we can override it with more specific utilities that have fewer properties. However, if a property doesn't exist, then it wouldn't be included in a list of properties therefore the total count was off. This PR fixes that by counting all the used properties. If a property already exists it is counted twice. E.g.: ```css .foo { color: red; &:hover { color: blue; } } ``` In this case, we have 2 properties, not 1 even though it's the same `color` property. ## Test plan: 1. Updated the tests that are now sorted correctly 2. Added an integration test to make sure that `prose-invert` is defined after the `prose-stone` classes when using the `@tailwindcss/typography` plugin where this problem originated from. Note how in this play (https://play.tailwindcss.com/wt3LYDaljN) the `prose-invert` comes _before_ the `prose-stone` which means that you can't apply the `prose-invert` classes to invert `prose-stone`.
1 parent f8d7623 commit 113142a

File tree

6 files changed

+120
-92
lines changed

6 files changed

+120
-92
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Vite: Ensure Astro production builds contain classes for client-only components ([#16631](https://github.com/tailwindlabs/tailwindcss/pull/16631))
2424
- Vite: Ensure utility classes are read without escaping special characters ([#16631](https://github.com/tailwindlabs/tailwindcss/pull/16631))
2525
- Allow `theme(…)` options when using `@import` ([#16514](https://github.com/tailwindlabs/tailwindcss/pull/16514))
26+
- Use amount of properties when sorting ([#16715](https://github.com/tailwindlabs/tailwindcss/pull/16715))
2627

2728
## [4.0.7] - 2025-02-18
2829

integrations/cli/plugins.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test(
1414
}
1515
`,
1616
'index.html': html`
17-
<div className="prose">
17+
<div className="prose prose-stone prose-invert">
1818
<h1>Headline</h1>
1919
<p>
2020
Until now, trying to style an article, document, or blog post with Tailwind has been a
@@ -28,9 +28,18 @@ test(
2828
`,
2929
},
3030
},
31-
async ({ fs, exec }) => {
31+
async ({ fs, exec, expect }) => {
3232
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
3333

34+
// Verify that `prose-stone` is defined before `prose-invert`
35+
{
36+
let contents = await fs.read('dist/out.css')
37+
let proseInvertIdx = contents.indexOf('.prose-invert')
38+
let proseStoneIdx = contents.indexOf('.prose-stone')
39+
40+
expect(proseStoneIdx).toBeLessThan(proseInvertIdx)
41+
}
42+
3443
await fs.expectFileToContain('dist/out.css', [
3544
candidate`prose`,
3645
':where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *))',

packages/tailwindcss/src/compat/plugin-api.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,6 +3047,14 @@ describe('addUtilities()', () => {
30473047
).toMatchInlineSnapshot(
30483048
`
30493049
"@layer utilities {
3050+
.j {
3051+
&.j {
3052+
color: red;
3053+
}
3054+
.j& {
3055+
color: red;
3056+
}
3057+
}
30503058
.a {
30513059
& .b:hover .c {
30523060
color: red;
@@ -3087,14 +3095,6 @@ describe('addUtilities()', () => {
30873095
color: red;
30883096
}
30893097
}
3090-
.j {
3091-
&.j {
3092-
color: red;
3093-
}
3094-
.j& {
3095-
color: red;
3096-
}
3097-
}
30983098
}"
30993099
`,
31003100
)

packages/tailwindcss/src/compile.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function compileCandidates(
2323
) {
2424
let nodeSorting = new Map<
2525
AstNode,
26-
{ properties: number[]; variants: bigint; candidate: string }
26+
{ properties: { order: number[]; count: number }; variants: bigint; candidate: string }
2727
>()
2828
let astNodes: AstNode[] = []
2929
let matches = new Map<string, Candidate[]>()
@@ -95,18 +95,19 @@ export function compileCandidates(
9595
// Find the first property that is different between the two rules
9696
let offset = 0
9797
while (
98-
aSorting.properties.length < offset &&
99-
zSorting.properties.length < offset &&
100-
aSorting.properties[offset] === zSorting.properties[offset]
98+
offset < aSorting.properties.order.length &&
99+
offset < zSorting.properties.order.length &&
100+
aSorting.properties.order[offset] === zSorting.properties.order[offset]
101101
) {
102102
offset += 1
103103
}
104104

105105
return (
106106
// Sort by lowest property index first
107-
(aSorting.properties[offset] ?? Infinity) - (zSorting.properties[offset] ?? Infinity) ||
107+
(aSorting.properties.order[offset] ?? Infinity) -
108+
(zSorting.properties.order[offset] ?? Infinity) ||
108109
// Sort by most properties first, then by least properties
109-
zSorting.properties.length - aSorting.properties.length ||
110+
zSorting.properties.count - aSorting.properties.count ||
110111
// Sort alphabetically
111112
compare(aSorting.candidate, zSorting.candidate)
112113
)
@@ -124,7 +125,10 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem
124125

125126
let rules: {
126127
node: AstNode
127-
propertySort: number[]
128+
propertySort: {
129+
order: number[]
130+
count: number
131+
}
128132
}[] = []
129133

130134
let selector = `.${escape(candidate.raw)}`
@@ -310,30 +314,42 @@ function applyImportant(ast: AstNode[]): void {
310314

311315
function getPropertySort(nodes: AstNode[]) {
312316
// Determine sort order based on properties used
313-
let propertySort = new Set<number>()
317+
let order = new Set<number>()
318+
let count = 0
314319
let q: AstNode[] = nodes.slice()
315320

321+
let seenTwSort = false
322+
316323
while (q.length > 0) {
317324
// SAFETY: At this point it is safe to use TypeScript's non-null assertion
318325
// operator because we guarded against `q.length > 0` above.
319326
let node = q.shift()!
320327
if (node.kind === 'declaration') {
328+
// Empty strings should still be counted, e.g.: `--tw-foo:;` is valid
329+
if (node.value !== undefined) count++
330+
331+
if (seenTwSort) continue
332+
321333
if (node.property === '--tw-sort') {
322334
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.value ?? '')
323335
if (idx !== -1) {
324-
propertySort.add(idx)
325-
break
336+
order.add(idx)
337+
seenTwSort = true
338+
continue
326339
}
327340
}
328341

329342
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.property)
330-
if (idx !== -1) propertySort.add(idx)
343+
if (idx !== -1) order.add(idx)
331344
} else if (node.kind === 'rule' || node.kind === 'at-rule') {
332345
for (let child of node.nodes) {
333346
q.push(child)
334347
}
335348
}
336349
}
337350

338-
return Array.from(propertySort).sort((a, z) => a - z)
351+
return {
352+
order: Array.from(order).sort((a, z) => a - z),
353+
count,
354+
}
339355
}

packages/tailwindcss/src/property-order.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export default [
7575
'translate',
7676
'--tw-translate-x',
7777
'--tw-translate-y',
78+
'--tw-translate-z',
7879
'scale',
7980
'--tw-scale-x',
8081
'--tw-scale-y',

0 commit comments

Comments
 (0)