Skip to content

Commit ad66635

Browse files
Fix sorting of utilities that share multiple candidates (#12173)
* Fix sorting of utilities that share multiple candidates * Update changelog
1 parent 31a80b1 commit ad66635

File tree

4 files changed

+45
-3
lines changed

4 files changed

+45
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Don’t crash when important and parent selectors are equal in `@apply` ([#12112](https://github.com/tailwindlabs/tailwindcss/pull/12112))
2525
- Eliminate irrelevant rules when applying variants ([#12113](https://github.com/tailwindlabs/tailwindcss/pull/12113))
2626
- Improve RegEx parser, reduce possibilities as the key for arbitrary properties ([#12121](https://github.com/tailwindlabs/tailwindcss/pull/12121))
27+
- Fix sorting of utilities that share multiple candidates ([#12173](https://github.com/tailwindlabs/tailwindcss/pull/12173))
2728

2829
### Added
2930

src/lib/generateRules.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ function getImportantStrategy(important) {
877877
}
878878
}
879879

880-
function generateRules(candidates, context) {
880+
function generateRules(candidates, context, isSorting = false) {
881881
let allRules = []
882882
let strategy = getImportantStrategy(context.tailwindConfig.important)
883883

@@ -912,7 +912,9 @@ function generateRules(candidates, context) {
912912
rule = container.nodes[0]
913913
}
914914

915-
let newEntry = [sort, rule]
915+
// Note: We have to clone rules during sorting
916+
// so we eliminate some shared mutable state
917+
let newEntry = [sort, isSorting ? rule.clone() : rule]
916918
rules.add(newEntry)
917919
context.ruleCache.add(newEntry)
918920
allRules.push(newEntry)

src/lib/setupContextUtils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ function registerPlugins(plugins, context) {
938938

939939
// Sort all classes in order
940940
// Non-tailwind classes won't be generated and will be left as `null`
941-
let rules = generateRules(new Set(sorted), context)
941+
let rules = generateRules(new Set(sorted), context, true)
942942
rules = context.offsets.sort(rules)
943943

944944
let idx = BigInt(parasiteUtilities.length)

tests/getSortOrder.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,42 @@ it('sorts based on first occurrence of a candidate / rule', () => {
178178
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
179179
}
180180
})
181+
182+
it('Sorting is unchanged when multiple candidates share the same rule / object', () => {
183+
let classes = [
184+
['x y', 'x y'],
185+
['a', 'a'],
186+
['x y', 'x y'],
187+
]
188+
189+
let config = {
190+
theme: {},
191+
plugins: [
192+
function ({ addComponents }) {
193+
addComponents({
194+
'.x': { color: 'red' },
195+
'.a': { color: 'red' },
196+
197+
// This rule matches both the candidate `a` and `y`
198+
// When sorting x and y first we would keep that sort order
199+
// Then sorting `a` we would end up replacing the candidate on the rule
200+
// Thus causing `y` to no longer have a sort order causing it to be sorted
201+
// first by accident
202+
'.y .a': { color: 'red' },
203+
})
204+
},
205+
],
206+
}
207+
208+
// Same context, different class lists
209+
let context = createContext(resolveConfig(config))
210+
for (const [input, output] of classes) {
211+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
212+
}
213+
214+
// Different context, different class lists
215+
for (const [input, output] of classes) {
216+
context = createContext(resolveConfig(config))
217+
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
218+
}
219+
})

0 commit comments

Comments
 (0)