Headline
Until now, trying to style an article, document, or blog post with Tailwind has been a
@@ -28,9 +28,18 @@ test(
`,
},
},
- async ({ fs, exec }) => {
+ async ({ fs, exec, expect }) => {
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css')
+ // Verify that `prose-stone` is defined before `prose-invert`
+ {
+ let contents = await fs.read('dist/out.css')
+ let proseInvertIdx = contents.indexOf('.prose-invert')
+ let proseStoneIdx = contents.indexOf('.prose-stone')
+
+ expect(proseStoneIdx).toBeLessThan(proseInvertIdx)
+ }
+
await fs.expectFileToContain('dist/out.css', [
candidate`prose`,
':where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *))',
diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts
index 14d0849a4..b786f2311 100644
--- a/packages/tailwindcss/src/compat/plugin-api.test.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.test.ts
@@ -3047,6 +3047,14 @@ describe('addUtilities()', () => {
).toMatchInlineSnapshot(
`
"@layer utilities {
+ .j {
+ &.j {
+ color: red;
+ }
+ .j& {
+ color: red;
+ }
+ }
.a {
& .b:hover .c {
color: red;
@@ -3087,14 +3095,6 @@ describe('addUtilities()', () => {
color: red;
}
}
- .j {
- &.j {
- color: red;
- }
- .j& {
- color: red;
- }
- }
}"
`,
)
diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts
index 6b48073b5..269acee03 100644
--- a/packages/tailwindcss/src/compile.ts
+++ b/packages/tailwindcss/src/compile.ts
@@ -23,7 +23,7 @@ export function compileCandidates(
) {
let nodeSorting = new Map<
AstNode,
- { properties: number[]; variants: bigint; candidate: string }
+ { properties: { order: number[]; count: number }; variants: bigint; candidate: string }
>()
let astNodes: AstNode[] = []
let matches = new Map()
@@ -95,18 +95,19 @@ export function compileCandidates(
// Find the first property that is different between the two rules
let offset = 0
while (
- aSorting.properties.length < offset &&
- zSorting.properties.length < offset &&
- aSorting.properties[offset] === zSorting.properties[offset]
+ offset < aSorting.properties.order.length &&
+ offset < zSorting.properties.order.length &&
+ aSorting.properties.order[offset] === zSorting.properties.order[offset]
) {
offset += 1
}
return (
// Sort by lowest property index first
- (aSorting.properties[offset] ?? Infinity) - (zSorting.properties[offset] ?? Infinity) ||
+ (aSorting.properties.order[offset] ?? Infinity) -
+ (zSorting.properties.order[offset] ?? Infinity) ||
// Sort by most properties first, then by least properties
- zSorting.properties.length - aSorting.properties.length ||
+ zSorting.properties.count - aSorting.properties.count ||
// Sort alphabetically
compare(aSorting.candidate, zSorting.candidate)
)
@@ -124,7 +125,10 @@ export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem
let rules: {
node: AstNode
- propertySort: number[]
+ propertySort: {
+ order: number[]
+ count: number
+ }
}[] = []
let selector = `.${escape(candidate.raw)}`
@@ -310,24 +314,33 @@ function applyImportant(ast: AstNode[]): void {
function getPropertySort(nodes: AstNode[]) {
// Determine sort order based on properties used
- let propertySort = new Set()
+ let order = new Set()
+ let count = 0
let q: AstNode[] = nodes.slice()
+ let seenTwSort = false
+
while (q.length > 0) {
// SAFETY: At this point it is safe to use TypeScript's non-null assertion
// operator because we guarded against `q.length > 0` above.
let node = q.shift()!
if (node.kind === 'declaration') {
+ // Empty strings should still be counted, e.g.: `--tw-foo:;` is valid
+ if (node.value !== undefined) count++
+
+ if (seenTwSort) continue
+
if (node.property === '--tw-sort') {
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.value ?? '')
if (idx !== -1) {
- propertySort.add(idx)
- break
+ order.add(idx)
+ seenTwSort = true
+ continue
}
}
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.property)
- if (idx !== -1) propertySort.add(idx)
+ if (idx !== -1) order.add(idx)
} else if (node.kind === 'rule' || node.kind === 'at-rule') {
for (let child of node.nodes) {
q.push(child)
@@ -335,5 +348,8 @@ function getPropertySort(nodes: AstNode[]) {
}
}
- return Array.from(propertySort).sort((a, z) => a - z)
+ return {
+ order: Array.from(order).sort((a, z) => a - z),
+ count,
+ }
}
diff --git a/packages/tailwindcss/src/property-order.ts b/packages/tailwindcss/src/property-order.ts
index 90290d54c..f2ed4bc51 100644
--- a/packages/tailwindcss/src/property-order.ts
+++ b/packages/tailwindcss/src/property-order.ts
@@ -75,6 +75,7 @@ export default [
'translate',
'--tw-translate-x',
'--tw-translate-y',
+ '--tw-translate-z',
'scale',
'--tw-scale-x',
'--tw-scale-y',
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index 79ac47006..2f00dcebe 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -4213,14 +4213,15 @@ test('translate-y', async () => {
})
test('translate-z', async () => {
- expect(await run(['translate-y-px', '-translate-z-[var(--value)]'])).toMatchInlineSnapshot(`
- ".translate-y-px {
- --tw-translate-y: 1px;
- translate: var(--tw-translate-x) var(--tw-translate-y);
+ expect(await run(['translate-z-px', '-translate-z-[var(--value)]'])).toMatchInlineSnapshot(`
+ ".-translate-z-\\[var\\(--value\\)\\] {
+ --tw-translate-z: calc(var(--value) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
}
- .-translate-z-\\[var\\(--value\\)\\] {
- --tw-translate-z: calc(var(--value) * -1);
+ .translate-z-px {
+ --tw-translate-z: 1px;
+ translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
}
@@ -5458,12 +5459,7 @@ test('touch-pan', async () => {
'touch-pan-down',
]),
).toMatchInlineSnapshot(`
- ".touch-pan-down {
- --tw-pan-y: pan-down;
- touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
- }
-
- .touch-pan-left {
+ ".touch-pan-left {
--tw-pan-x: pan-left;
touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
}
@@ -5473,13 +5469,18 @@ test('touch-pan', async () => {
touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
}
- .touch-pan-up {
- --tw-pan-y: pan-up;
+ .touch-pan-x {
+ --tw-pan-x: pan-x;
touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
}
- .touch-pan-x {
- --tw-pan-x: pan-x;
+ .touch-pan-down {
+ --tw-pan-y: pan-down;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-up {
+ --tw-pan-y: pan-up;
touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
}
@@ -14155,15 +14156,7 @@ test('contain', async () => {
'contain-[unset]',
]),
).toMatchInlineSnapshot(`
- ".contain-\\[unset\\] {
- contain: unset;
- }
-
- .contain-content {
- contain: content;
- }
-
- .contain-inline-size {
+ ".contain-inline-size {
--tw-contain-size: inline-size;
contain: var(--tw-contain-size, ) var(--tw-contain-layout, ) var(--tw-contain-paint, ) var(--tw-contain-style, );
}
@@ -14173,10 +14166,6 @@ test('contain', async () => {
contain: var(--tw-contain-size, ) var(--tw-contain-layout, ) var(--tw-contain-paint, ) var(--tw-contain-style, );
}
- .contain-none {
- contain: none;
- }
-
.contain-paint {
--tw-contain-paint: paint;
contain: var(--tw-contain-size, ) var(--tw-contain-layout, ) var(--tw-contain-paint, ) var(--tw-contain-style, );
@@ -14187,15 +14176,27 @@ test('contain', async () => {
contain: var(--tw-contain-size, ) var(--tw-contain-layout, ) var(--tw-contain-paint, ) var(--tw-contain-style, );
}
- .contain-strict {
- contain: strict;
- }
-
.contain-style {
--tw-contain-style: style;
contain: var(--tw-contain-size, ) var(--tw-contain-layout, ) var(--tw-contain-paint, ) var(--tw-contain-style, );
}
+ .contain-\\[unset\\] {
+ contain: unset;
+ }
+
+ .contain-content {
+ contain: content;
+ }
+
+ .contain-none {
+ contain: none;
+ }
+
+ .contain-strict {
+ contain: strict;
+ }
+
@property --tw-contain-size {
syntax: "*";
inherits: false
@@ -14424,10 +14425,6 @@ test('font-variant-numeric', async () => {
font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
}
- .normal-nums {
- font-variant-numeric: normal;
- }
-
.oldstyle-nums {
--tw-numeric-figure: oldstyle-nums;
font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
@@ -14458,6 +14455,10 @@ test('font-variant-numeric', async () => {
font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
}
+ .normal-nums {
+ font-variant-numeric: normal;
+ }
+
@property --tw-ordinal {
syntax: "*";
inherits: false
@@ -16385,28 +16386,28 @@ test('@container', async () => {
'@container-[size]/sidebar',
]),
).toMatchInlineSnapshot(`
- ".\\@container {
- container-type: inline-size;
- }
-
- .\\@container-\\[size\\] {
- container-type: size;
- }
-
- .\\@container-\\[size\\]\\/sidebar {
+ ".\\@container-\\[size\\]\\/sidebar {
container: sidebar / size;
}
- .\\@container-normal {
- container-type: normal;
- }
-
.\\@container-normal\\/sidebar {
container: sidebar;
}
.\\@container\\/sidebar {
container: sidebar / inline-size;
+ }
+
+ .\\@container {
+ container-type: inline-size;
+ }
+
+ .\\@container-\\[size\\] {
+ container-type: size;
+ }
+
+ .\\@container-normal {
+ container-type: normal;
}"
`)
expect(
@@ -17579,24 +17580,24 @@ describe('custom utilities', () => {
'example-[12px]/[16px]',
]),
).toMatchInlineSnapshot(`
- ".example-\\[12px\\] {
- --value: 12px;
- }
-
- .example-\\[12px\\]\\/\\[16px\\] {
+ ".example-\\[12px\\]\\/\\[16px\\] {
--value: 12px;
--modifier: 16px;
--modifier-with-calc: calc(16px * 2);
}
- .example-sm {
- --value: var(--value-sm);
- }
-
.example-sm\\/7 {
--value: var(--value-sm);
--modifier: var(--modifier-7);
--modifier-with-calc: calc(var(--modifier-7) * 2);
+ }
+
+ .example-\\[12px\\] {
+ --value: 12px;
+ }
+
+ .example-sm {
+ --value: var(--value-sm);
}"
`)
expect(
@@ -17651,15 +17652,15 @@ describe('custom utilities', () => {
`
expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(`
- ".example-xs {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .example-xs\\/6 {
+ ".example-xs\\/6 {
font-size: var(--text-xs);
line-height: var(--text-xs--line-height);
line-height: 6;
+ }
+
+ .example-xs {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
}"
`)
expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('')
@@ -17682,15 +17683,15 @@ describe('custom utilities', () => {
`
expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(`
- ".example-xs {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .example-xs\\/6 {
+ ".example-xs\\/6 {
font-size: var(--text-xs);
line-height: var(--text-xs--line-height);
line-height: 6;
+ }
+
+ .example-xs {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
}"
`)
expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('')
@@ -17713,15 +17714,15 @@ describe('custom utilities', () => {
`
expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(`
- ".example-xs {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .example-xs\\/6 {
+ ".example-xs\\/6 {
font-size: var(--text-xs);
line-height: var(--text-xs--line-height);
line-height: 6;
+ }
+
+ .example-xs {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
}"
`)
expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('')
@@ -17744,15 +17745,15 @@ describe('custom utilities', () => {
`
expect(await compileCss(input, ['example-xs', 'example-xs/6'])).toMatchInlineSnapshot(`
- ".example-xs {
- font-size: var(--text-xs);
- line-height: var(--text-xs--line-height);
- }
-
- .example-xs\\/6 {
+ ".example-xs\\/6 {
font-size: var(--text-xs);
line-height: var(--text-xs--line-height);
line-height: 6;
+ }
+
+ .example-xs {
+ font-size: var(--text-xs);
+ line-height: var(--text-xs--line-height);
}"
`)
expect(await compileCss(input, ['example-foo', 'example-xs/foo'])).toEqual('')