From 8ea7269ed674681b92ddd4ad1a119a71f375ce5a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 28 Jun 2023 15:10:54 -0400 Subject: [PATCH 1/5] Refactor --- src/lib/setupContextUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 0bd5639833c7..305664773de8 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -942,7 +942,9 @@ function registerPlugins(plugins, context) { let idx = BigInt(parasiteUtilities.length) for (const [, rule] of rules) { - sortedClassNames.set(rule.raws.tailwind.candidate, idx++) + let candidate = rule.raws.tailwind.candidate + + sortedClassNames.set(candidate, idx++) } return classes.map((className) => { From 2aa12bba68ca6f4bf59caad0464cba0e97b5b110 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 28 Jun 2023 15:17:40 -0400 Subject: [PATCH 2/5] Sort based on first occurence of a candidate This primarily affects components and utilities which contain multiple matched classes --- src/lib/setupContextUtils.js | 4 +++- tests/getSortOrder.test.js | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 305664773de8..fe2191b7d5fa 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -944,7 +944,9 @@ function registerPlugins(plugins, context) { for (const [, rule] of rules) { let candidate = rule.raws.tailwind.candidate - sortedClassNames.set(candidate, idx++) + // When multiple rules match a candidate + // always take the position of the first one + sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++) } return classes.map((className) => { diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 0863d268d091..324543c886be 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -140,3 +140,42 @@ it('sorts classes deterministically across multiple class lists', () => { expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) } }) + +it('sorts based on first occurence of a candidate / rule', () => { + let classes = [ + ['foo-1 foo', 'foo foo-1'], + ['bar', 'bar'], + ['foo-1 foo', 'foo foo-1'], + ] + + let config = { + theme: {}, + plugins: [ + function ({ addComponents }) { + addComponents({ + '.foo': { display: 'block' }, + '.foo-1': { display: 'block' }, + '.foo-2': { display: 'block' }, + '.bar': { display: 'block' }, + + // This rule matches both the candidate `foo` and `bar` + // But when sorting `foo` — we've already got a + // position for `foo` so we should use it + '.bar .foo': { display: 'block' }, + }) + } + ], + } + + // Same context, different class lists + let context = createContext(resolveConfig(config)) + for (const [input, output] of classes) { + expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) + } + + // Different context, different class lists + for (const [input, output] of classes) { + context = createContext(resolveConfig(config)) + expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) + } +}) From c80c1c22355fe967a363f8a64bf4462ccf08063d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 28 Jun 2023 15:44:11 -0400 Subject: [PATCH 3/5] Simplify --- tests/getSortOrder.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 324543c886be..6c37aec6263e 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -155,7 +155,6 @@ it('sorts based on first occurence of a candidate / rule', () => { addComponents({ '.foo': { display: 'block' }, '.foo-1': { display: 'block' }, - '.foo-2': { display: 'block' }, '.bar': { display: 'block' }, // This rule matches both the candidate `foo` and `bar` From af3f5fe41d099b8ec36e585c72245dae289eeda0 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 28 Jun 2023 15:44:15 -0400 Subject: [PATCH 4/5] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e17969c52074..7ef52f518ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fallback to RegEx based parser when using custom transformers or extractors ([#11335](https://github.com/tailwindlabs/tailwindcss/pull/11335)) - Move unknown pseudo-elements outside of `:is` by default ([#11345](https://github.com/tailwindlabs/tailwindcss/pull/11345)) - Escape animation names when prefixes contain special characters ([#11470](https://github.com/tailwindlabs/tailwindcss/pull/11470)) +- Sort classes using position of first matching rule ([#11504](https://github.com/tailwindlabs/tailwindcss/pull/11504)) ### Added From 448ee54153541cd7d9ca4027a7ecb1607d1c1301 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 28 Jun 2023 15:49:42 -0400 Subject: [PATCH 5/5] Update --- tests/getSortOrder.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 6c37aec6263e..10740880f8bb 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -162,7 +162,7 @@ it('sorts based on first occurence of a candidate / rule', () => { // position for `foo` so we should use it '.bar .foo': { display: 'block' }, }) - } + }, ], }