Skip to content

Commit 96d4ce2

Browse files
authored
Expose context.sortClassList(classes) (#7412)
* add prettier-plugin-tailwindcss This will use the prettier plugin in our tests as well, yay consistency! * ensure that both `group` and `peer` can't be used in `@apply` This was only configured for `group` * expose `sortClassList` on the context This function will be used by the `prettier-plugin-tailwindcss` plugin, this way the sorting happens within Tailwind CSS itself adn the `prettier-plugin-tailwindcss` plugin doesn't have to use internal / private APIs. The signature looks like this: ```ts function sortClassList(classes: string[]): string[] ``` E.g.: ```js let sortedClasses = context.sortClassList(['p-1', 'm-1', 'container']) ``` * update changelog * add sort test for utilities with the important modifier e.g.: `!p-4`
1 parent 5ea67b0 commit 96d4ce2

25 files changed

+274
-90
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
Nothing yet!
10+
### Added
11+
12+
- Expose `context.sortClassList(classes)` ([#7412](https://github.com/tailwindlabs/tailwindcss/pull/7412))
1113

1214
## [3.0.19] - 2022-02-07
1315

integrations/parcel/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ describe.skip('watcher', () => {
164164
'index.html',
165165
html`
166166
<link rel="stylesheet" href="./index.css" />
167-
<div class="font-bold btn"></div>
167+
<div class="btn font-bold"></div>
168168
`
169169
)
170170

@@ -190,7 +190,7 @@ describe.skip('watcher', () => {
190190
191191
@layer components {
192192
.btn {
193-
@apply px-2 py-1 rounded;
193+
@apply rounded px-2 py-1;
194194
}
195195
}
196196
`
@@ -222,7 +222,7 @@ describe.skip('watcher', () => {
222222
223223
@layer components {
224224
.btn {
225-
@apply px-2 py-1 rounded bg-red-500;
225+
@apply rounded bg-red-500 px-2 py-1;
226226
}
227227
}
228228
`

integrations/postcss-cli/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('watcher', () => {
139139
})
140140

141141
test('classes are generated when the index.css file changes', async () => {
142-
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
142+
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
143143

144144
let runningProcess = $('postcss ./src/index.css -o ./dist/main.css -w --verbose')
145145

@@ -162,7 +162,7 @@ describe('watcher', () => {
162162
163163
@layer components {
164164
.btn {
165-
@apply px-2 py-1 rounded;
165+
@apply rounded px-2 py-1;
166166
}
167167
}
168168
`
@@ -193,7 +193,7 @@ describe('watcher', () => {
193193
194194
@layer components {
195195
.btn {
196-
@apply px-2 py-1 rounded bg-red-500;
196+
@apply rounded bg-red-500 px-2 py-1;
197197
}
198198
}
199199
`

integrations/rollup/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ describe('watcher', () => {
138138
})
139139

140140
test(`classes are generated when the index.css file changes`, async () => {
141-
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
141+
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
142142

143143
let runningProcess = $('rollup -c --watch')
144144
await runningProcess.onStderr(ready)
@@ -160,7 +160,7 @@ describe('watcher', () => {
160160
161161
@layer components {
162162
.btn {
163-
@apply px-2 py-1 rounded;
163+
@apply rounded px-2 py-1;
164164
}
165165
}
166166
`
@@ -191,7 +191,7 @@ describe('watcher', () => {
191191
192192
@layer components {
193193
.btn {
194-
@apply px-2 py-1 rounded bg-red-500;
194+
@apply rounded bg-red-500 px-2 py-1;
195195
}
196196
}
197197
`

integrations/tailwindcss-cli/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ describe('watcher', () => {
253253
})
254254

255255
test('classes are generated when the index.css file changes', async () => {
256-
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
256+
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
257257

258258
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')
259259
await runningProcess.onStderr(ready)
@@ -275,7 +275,7 @@ describe('watcher', () => {
275275
276276
@layer components {
277277
.btn {
278-
@apply px-2 py-1 rounded;
278+
@apply rounded px-2 py-1;
279279
}
280280
}
281281
`
@@ -306,7 +306,7 @@ describe('watcher', () => {
306306
307307
@layer components {
308308
.btn {
309-
@apply px-2 py-1 rounded bg-red-500;
309+
@apply rounded bg-red-500 px-2 py-1;
310310
}
311311
}
312312
`

integrations/vite/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ describe('watcher', () => {
169169
'index.html',
170170
html`
171171
<link rel="stylesheet" href="./index.css" />
172-
<div class="font-bold btn"></div>
172+
<div class="btn font-bold"></div>
173173
`
174174
)
175175

@@ -193,7 +193,7 @@ describe('watcher', () => {
193193
194194
@layer components {
195195
.btn {
196-
@apply px-2 py-1 rounded;
196+
@apply rounded px-2 py-1;
197197
}
198198
}
199199
`
@@ -224,7 +224,7 @@ describe('watcher', () => {
224224
225225
@layer components {
226226
.btn {
227-
@apply px-2 py-1 rounded bg-red-500;
227+
@apply rounded bg-red-500 px-2 py-1;
228228
}
229229
}
230230
`

integrations/webpack-4/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('watcher', () => {
140140
})
141141

142142
test(`classes are generated when the index.css file changes`, async () => {
143-
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
143+
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
144144

145145
let runningProcess = $('webpack --mode=development --watch')
146146

@@ -164,7 +164,7 @@ describe('watcher', () => {
164164
165165
@layer components {
166166
.btn {
167-
@apply px-2 py-1 rounded;
167+
@apply rounded px-2 py-1;
168168
}
169169
}
170170
`
@@ -196,7 +196,7 @@ describe('watcher', () => {
196196
197197
@layer components {
198198
.btn {
199-
@apply px-2 py-1 rounded bg-red-500;
199+
@apply rounded bg-red-500 px-2 py-1;
200200
}
201201
}
202202
`

integrations/webpack-5/tests/integration.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('watcher', () => {
140140
})
141141

142142
test(`classes are generated when the index.css file changes`, async () => {
143-
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
143+
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
144144

145145
let runningProcess = $('webpack --mode=development --watch')
146146

@@ -164,7 +164,7 @@ describe('watcher', () => {
164164
165165
@layer components {
166166
.btn {
167-
@apply px-2 py-1 rounded;
167+
@apply rounded px-2 py-1;
168168
}
169169
}
170170
`
@@ -196,7 +196,7 @@ describe('watcher', () => {
196196
197197
@layer components {
198198
.btn {
199-
@apply px-2 py-1 rounded bg-red-500;
199+
@apply rounded bg-red-500 px-2 py-1;
200200
}
201201
}
202202
`

package-lock.json

Lines changed: 19 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"postcss-nested": "5.0.6",
7878
"postcss-selector-parser": "^6.0.9",
7979
"postcss-value-parser": "^4.2.0",
80+
"prettier-plugin-tailwindcss": "^0.1.7",
8081
"quick-lru": "^5.1.1",
8182
"resolve": "^1.22.0"
8283
},

src/lib/expandApplyAtRules.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ function processApply(root, context) {
161161
}
162162

163163
for (let applyCandidate of applyCandidates) {
164-
if (!applyClassCache.has(applyCandidate)) {
165-
if (applyCandidate === prefix(context, 'group')) {
166-
// TODO: Link to specific documentation page with error code.
167-
throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
168-
}
164+
if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
165+
// TODO: Link to specific documentation page with error code.
166+
throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
167+
}
169168

169+
if (!applyClassCache.has(applyCandidate)) {
170170
throw apply.error(
171171
`The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
172172
)

src/lib/generateRules.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ function applyVariant(variant, matches, context) {
234234
// For example:
235235
// .sm:underline {} is a variant of something in the utilities layer
236236
// .sm:container {} is a variant of the container component
237-
clone.nodes[0].raws.tailwind = { parentLayer: meta.layer }
237+
clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
238238

239239
let withOffset = [
240240
{
@@ -387,7 +387,7 @@ function splitWithSeparator(input, separator) {
387387

388388
function* recordCandidates(matches, classCandidate) {
389389
for (const match of matches) {
390-
match[1].raws.tailwind = { classCandidate }
390+
match[1].raws.tailwind = { ...match[1].raws.tailwind, classCandidate }
391391

392392
yield match
393393
}
@@ -517,6 +517,8 @@ function* resolveMatches(candidate, context) {
517517
}
518518

519519
for (let match of matches) {
520+
match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
521+
520522
// Apply final format selector
521523
if (match[0].collectedFormats) {
522524
let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)

src/lib/setupContextUtils.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import { toPath } from '../util/toPath'
1919
import log from '../util/log'
2020
import negateValue from '../util/negateValue'
2121
import isValidArbitraryValue from '../util/isValidArbitraryValue'
22+
import { generateRules } from './generateRules'
23+
24+
function prefix(context, selector) {
25+
let prefix = context.tailwindConfig.prefix
26+
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
27+
}
2228

2329
function parseVariantFormatString(input) {
2430
if (input.includes('{')) {
@@ -733,9 +739,43 @@ function registerPlugins(plugins, context) {
733739
}
734740
}
735741

742+
// A list of utilities that are used by certain Tailwind CSS utilities but
743+
// that don't exist on their own. This will result in them "not existing" and
744+
// sorting could be weird since you still require them in order to make the
745+
// host utitlies work properly. (Thanks Biology)
746+
let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')])
747+
context.sortClassList = function sortClassList(classes) {
748+
let sortedClassNames = new Map()
749+
for (let [sort, rule] of generateRules(new Set(classes), context)) {
750+
if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue
751+
sortedClassNames.set(rule.raws.tailwind.candidate, sort)
752+
}
753+
754+
return classes
755+
.map((className) => {
756+
let order = sortedClassNames.get(className) ?? null
757+
758+
if (order === null && parasiteUtilities.has(className)) {
759+
// This will make sure that it is at the very beginning of the
760+
// `components` layer which technically means 'before any
761+
// components'.
762+
order = context.layerOrder.components
763+
}
764+
765+
return [className, order]
766+
})
767+
.sort(([, a], [, z]) => {
768+
if (a === z) return 0
769+
if (a === null) return -1
770+
if (z === null) return 1
771+
return bigSign(a - z)
772+
})
773+
.map(([className]) => className)
774+
}
775+
736776
// Generate a list of strings for autocompletion purposes, e.g.
737777
// ['uppercase', 'lowercase', ...]
738-
context.getClassList = function () {
778+
context.getClassList = function getClassList() {
739779
let output = []
740780

741781
for (let util of classList) {

0 commit comments

Comments
 (0)