Skip to content

Commit

Permalink
Sort classes using position of first matching rule (#11504)
Browse files Browse the repository at this point in the history
* Refactor

* Sort based on first occurence of a candidate

This primarily affects components and utilities which contain multiple matched classes

* Simplify

* Update changelog

* Update
  • Loading branch information
thecrypticace committed Jun 29, 2023
1 parent 615c157 commit 40cb82c
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,11 @@ 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

// 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) => {
Expand Down
38 changes: 38 additions & 0 deletions tests/getSortOrder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,41 @@ 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' },
'.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)
}
})

0 comments on commit 40cb82c

Please sign in to comment.