Skip to content

Commit 58b126f

Browse files
committed
Fix sorting of utilities that share multiple candidates (#12173)
* Fix sorting of utilities that share multiple candidates * Update changelog
1 parent f57c2f9 commit 58b126f

File tree

4 files changed

+45
-3
lines changed

4 files changed

+45
-3
lines changed

CHANGELOG.md

+1
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
## [3.3.3] - 2023-07-13
2930

src/lib/generateRules.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ function getImportantStrategy(important) {
882882
}
883883
}
884884

885-
function generateRules(candidates, context) {
885+
function generateRules(candidates, context, isSorting = false) {
886886
let allRules = []
887887
let strategy = getImportantStrategy(context.tailwindConfig.important)
888888

@@ -917,7 +917,9 @@ function generateRules(candidates, context) {
917917
rule = container.nodes[0]
918918
}
919919

920-
let newEntry = [sort, rule]
920+
// Note: We have to clone rules during sorting
921+
// so we eliminate some shared mutable state
922+
let newEntry = [sort, isSorting ? rule.clone() : rule]
921923
rules.add(newEntry)
922924
context.ruleCache.add(newEntry)
923925
allRules.push(newEntry)

src/lib/setupContextUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ function registerPlugins(plugins, context) {
947947

948948
// Sort all classes in order
949949
// Non-tailwind classes won't be generated and will be left as `null`
950-
let rules = generateRules(new Set(sorted), context)
950+
let rules = generateRules(new Set(sorted), context, true)
951951
rules = context.offsets.sort(rules)
952952

953953
let idx = BigInt(parasiteUtilities.length)

tests/getSortOrder.test.js

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

0 commit comments

Comments
 (0)