tailwindcss 0.0.0-insiders.ddec022 → 0.0.0-insiders.de00a62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/cli/build/plugin.js +6 -6
  2. package/lib/cli/build/watching.js +1 -1
  3. package/lib/corePluginList.js +5 -1
  4. package/lib/corePlugins.js +170 -13
  5. package/lib/css/preflight.css +24 -8
  6. package/lib/lib/content.js +36 -3
  7. package/lib/lib/defaultExtractor.js +33 -25
  8. package/lib/lib/evaluateTailwindFunctions.js +5 -3
  9. package/lib/lib/expandApplyAtRules.js +6 -0
  10. package/lib/lib/expandTailwindAtRules.js +23 -6
  11. package/lib/lib/generateRules.js +47 -25
  12. package/lib/lib/load-config.js +14 -3
  13. package/lib/lib/offsets.js +51 -2
  14. package/lib/lib/resolveDefaultsAtRules.js +3 -1
  15. package/lib/lib/setupContextUtils.js +76 -37
  16. package/lib/lib/setupTrackingContext.js +2 -1
  17. package/lib/oxide/cli/build/plugin.js +6 -6
  18. package/lib/plugin.js +3 -3
  19. package/lib/processTailwindFeatures.js +2 -2
  20. package/lib/util/cloneNodes.js +33 -13
  21. package/lib/util/color.js +1 -1
  22. package/lib/util/dataTypes.js +135 -16
  23. package/lib/util/formatVariantSelector.js +10 -3
  24. package/lib/util/isPlainObject.js +1 -1
  25. package/lib/util/pluginUtils.js +13 -0
  26. package/lib/util/prefixSelector.js +1 -1
  27. package/lib/util/pseudoElements.js +21 -34
  28. package/lib/value-parser/LICENSE +22 -0
  29. package/lib/value-parser/README.md +3 -0
  30. package/lib/value-parser/index.d.js +2 -0
  31. package/lib/value-parser/index.js +22 -0
  32. package/lib/value-parser/parse.js +259 -0
  33. package/lib/value-parser/stringify.js +38 -0
  34. package/lib/value-parser/unit.js +86 -0
  35. package/lib/value-parser/walk.js +16 -0
  36. package/nesting/index.d.ts +4 -0
  37. package/package.json +5 -6
  38. package/peers/index.js +701 -617
  39. package/resolveConfig.d.ts +22 -3
  40. package/scripts/generate-types.js +1 -2
  41. package/src/cli/build/plugin.js +6 -6
  42. package/src/cli/build/watching.js +1 -1
  43. package/src/corePluginList.js +1 -1
  44. package/src/corePlugins.js +149 -12
  45. package/src/css/preflight.css +24 -8
  46. package/src/featureFlags.js +1 -5
  47. package/src/lib/content.js +42 -1
  48. package/src/lib/defaultExtractor.js +30 -17
  49. package/src/lib/evaluateTailwindFunctions.js +4 -1
  50. package/src/lib/expandApplyAtRules.js +7 -0
  51. package/src/lib/expandTailwindAtRules.js +23 -6
  52. package/src/lib/generateRules.js +50 -26
  53. package/src/lib/load-config.ts +8 -0
  54. package/src/lib/offsets.js +61 -2
  55. package/src/lib/resolveDefaultsAtRules.js +5 -1
  56. package/src/lib/setupContextUtils.js +77 -38
  57. package/src/lib/setupTrackingContext.js +1 -3
  58. package/src/oxide/cli/build/plugin.ts +6 -6
  59. package/src/plugin.js +3 -3
  60. package/src/processTailwindFeatures.js +3 -2
  61. package/src/util/cloneNodes.js +35 -14
  62. package/src/util/color.js +1 -1
  63. package/src/util/dataTypes.js +143 -18
  64. package/src/util/formatVariantSelector.js +11 -3
  65. package/src/util/isPlainObject.js +1 -1
  66. package/src/util/pluginUtils.js +16 -0
  67. package/src/util/prefixSelector.js +1 -0
  68. package/src/util/pseudoElements.js +18 -17
  69. package/src/value-parser/LICENSE +22 -0
  70. package/src/value-parser/README.md +3 -0
  71. package/src/value-parser/index.d.ts +177 -0
  72. package/src/value-parser/index.js +28 -0
  73. package/src/value-parser/parse.js +303 -0
  74. package/src/value-parser/stringify.js +41 -0
  75. package/src/value-parser/unit.js +118 -0
  76. package/src/value-parser/walk.js +18 -0
  77. package/stubs/config.full.js +86 -14
  78. package/types/config.d.ts +17 -9
  79. package/types/generated/corePluginList.d.ts +1 -1
  80. package/types/generated/default-theme.d.ts +35 -9
  81. package/types/index.d.ts +7 -3
@@ -553,6 +553,13 @@ function processApply(root, context, localCache) {
553
553
  ? parent.selector.slice(importantSelector.length)
554
554
  : parent.selector
555
555
 
556
+ // If the selector becomes empty after replacing the important selector
557
+ // This means that it's the same as the parent selector and we don't want to replace it
558
+ // Otherwise we'll crash
559
+ if (parentSelector === '') {
560
+ parentSelector = parent.selector
561
+ }
562
+
556
563
  rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
557
564
 
558
565
  // And then re-add it if it was removed
@@ -98,7 +98,7 @@ function buildStylesheet(rules, context) {
98
98
  }
99
99
 
100
100
  export default function expandTailwindAtRules(context) {
101
- return (root) => {
101
+ return async (root) => {
102
102
  let layerNodes = {
103
103
  base: null,
104
104
  components: null,
@@ -145,11 +145,25 @@ export default function expandTailwindAtRules(context) {
145
145
  // getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
146
146
  // }
147
147
  } else {
148
- for (let { file, content, extension } of context.changedContent) {
149
- let transformer = getTransformer(context.tailwindConfig, extension)
150
- let extractor = getExtractor(context, extension)
151
- content = file ? fs.readFileSync(file, 'utf8') : content
152
- getClassCandidates(transformer(content), extractor, candidates, seen)
148
+ /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
149
+ let regexParserContent = []
150
+
151
+ for (let item of context.changedContent) {
152
+ let transformer = getTransformer(context.tailwindConfig, item.extension)
153
+ let extractor = getExtractor(context, item.extension)
154
+ regexParserContent.push([item, { transformer, extractor }])
155
+ }
156
+
157
+ const BATCH_SIZE = 500
158
+
159
+ for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
160
+ let batch = regexParserContent.slice(i, i + BATCH_SIZE)
161
+ await Promise.all(
162
+ batch.map(async ([{ file, content }, { transformer, extractor }]) => {
163
+ content = file ? await fs.promises.readFile(file, 'utf8') : content
164
+ getClassCandidates(transformer(content), extractor, candidates, seen)
165
+ })
166
+ )
153
167
  }
154
168
  }
155
169
 
@@ -251,6 +265,9 @@ export default function expandTailwindAtRules(context) {
251
265
  )
252
266
  }
253
267
 
268
+ // TODO: Why is the root node having no source location for `end` possible?
269
+ root.source.end = root.source.end ?? root.source.start
270
+
254
271
  // If we've got a utility layer and no utilities are generated there's likely something wrong
255
272
  const hasUtilityVariants = variantNodes.some(
256
273
  (node) => node.raws.tailwind?.parentLayer === 'utilities'
@@ -13,7 +13,7 @@ import {
13
13
  } from '../util/formatVariantSelector'
14
14
  import { asClass } from '../util/nameClass'
15
15
  import { normalize } from '../util/dataTypes'
16
- import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
16
+ import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
17
17
  import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
18
18
  import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
19
19
  import { flagEnabled } from '../featureFlags'
@@ -119,10 +119,20 @@ function applyImportant(matches, classCandidate) {
119
119
 
120
120
  let result = []
121
121
 
122
+ function isInKeyframes(rule) {
123
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
124
+ }
125
+
122
126
  for (let [meta, rule] of matches) {
123
127
  let container = postcss.root({ nodes: [rule.clone()] })
124
128
 
125
129
  container.walkRules((r) => {
130
+ // Declarations inside keyframes cannot be marked as important
131
+ // They will be ignored by the browser
132
+ if (isInKeyframes(r)) {
133
+ return
134
+ }
135
+
126
136
  let ast = selectorParser().astSync(r.selector)
127
137
 
128
138
  // Remove extraneous selectors that do not include the base candidate
@@ -193,13 +203,13 @@ function applyVariant(variant, matches, context) {
193
203
  // group[:hover] (`-` is missing)
194
204
  let match = /(.)(-?)\[(.*)\]/g.exec(variant)
195
205
  if (match) {
196
- let [, char, seperator, value] = match
206
+ let [, char, separator, value] = match
197
207
  // @-[200px] case
198
- if (char === '@' && seperator === '-') return []
208
+ if (char === '@' && separator === '-') return []
199
209
  // group[:hover] case
200
- if (char !== '@' && seperator === '') return []
210
+ if (char !== '@' && separator === '') return []
201
211
 
202
- variant = variant.replace(`${seperator}[${value}]`, '')
212
+ variant = variant.replace(`${separator}[${value}]`, '')
203
213
  args.value = value
204
214
  }
205
215
  }
@@ -230,9 +240,16 @@ function applyVariant(variant, matches, context) {
230
240
 
231
241
  if (context.variantMap.has(variant)) {
232
242
  let isArbitraryVariant = isArbitraryValue(variant)
243
+ let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {}
233
244
  let variantFunctionTuples = context.variantMap.get(variant).slice()
234
245
  let result = []
235
246
 
247
+ let respectPrefix = (() => {
248
+ if (isArbitraryVariant) return false
249
+ if (internalFeatures.respectPrefix === false) return false
250
+ return true
251
+ })()
252
+
236
253
  for (let [meta, rule] of matches) {
237
254
  // Don't generate variants for user css
238
255
  if (meta.layer === 'user') {
@@ -293,7 +310,7 @@ function applyVariant(variant, matches, context) {
293
310
  format(selectorFormat) {
294
311
  collectedFormats.push({
295
312
  format: selectorFormat,
296
- isArbitraryVariant,
313
+ respectPrefix,
297
314
  })
298
315
  },
299
316
  args,
@@ -322,7 +339,7 @@ function applyVariant(variant, matches, context) {
322
339
  if (typeof ruleWithVariant === 'string') {
323
340
  collectedFormats.push({
324
341
  format: ruleWithVariant,
325
- isArbitraryVariant,
342
+ respectPrefix,
326
343
  })
327
344
  }
328
345
 
@@ -366,7 +383,7 @@ function applyVariant(variant, matches, context) {
366
383
  // format: .foo &
367
384
  collectedFormats.push({
368
385
  format: modified.replace(rebuiltBase, '&'),
369
- isArbitraryVariant,
386
+ respectPrefix,
370
387
  })
371
388
  rule.selector = before
372
389
  })
@@ -489,13 +506,13 @@ function extractArbitraryProperty(classCandidate, context) {
489
506
  return null
490
507
  }
491
508
 
492
- let normalized = normalize(value)
509
+ let normalized = normalize(value, { property })
493
510
 
494
511
  if (!isParsableCssValue(property, normalized)) {
495
512
  return null
496
513
  }
497
514
 
498
- let sort = context.offsets.arbitraryProperty()
515
+ let sort = context.offsets.arbitraryProperty(classCandidate)
499
516
 
500
517
  return [
501
518
  [
@@ -566,7 +583,7 @@ function* recordCandidates(matches, classCandidate) {
566
583
  }
567
584
  }
568
585
 
569
- function* resolveMatches(candidate, context, original = candidate) {
586
+ function* resolveMatches(candidate, context) {
570
587
  let separator = context.tailwindConfig.separator
571
588
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
572
589
  let important = false
@@ -576,15 +593,6 @@ function* resolveMatches(candidate, context, original = candidate) {
576
593
  classCandidate = classCandidate.slice(1)
577
594
  }
578
595
 
579
- if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
580
- if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
581
- let base = variants.slice().reverse().join(separator)
582
- for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
583
- yield* resolveMatches(base + separator + part, context, original)
584
- }
585
- }
586
- }
587
-
588
596
  // TODO: Reintroduce this in ways that doesn't break on false positives
589
597
  // function sortAgainst(toSort, against) {
590
598
  // return toSort.slice().sort((a, z) => {
@@ -773,7 +781,7 @@ function* resolveMatches(candidate, context, original = candidate) {
773
781
  match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
774
782
 
775
783
  // Apply final format selector
776
- match = applyFinalFormat(match, { context, candidate, original })
784
+ match = applyFinalFormat(match, { context, candidate })
777
785
 
778
786
  // Skip rules with invalid selectors
779
787
  // This will cause the candidate to be added to the "not class"
@@ -787,7 +795,7 @@ function* resolveMatches(candidate, context, original = candidate) {
787
795
  }
788
796
  }
789
797
 
790
- function applyFinalFormat(match, { context, candidate, original }) {
798
+ function applyFinalFormat(match, { context, candidate }) {
791
799
  if (!match[0].collectedFormats) {
792
800
  return match
793
801
  }
@@ -822,10 +830,19 @@ function applyFinalFormat(match, { context, candidate, original }) {
822
830
  }
823
831
 
824
832
  try {
825
- rule.selector = finalizeSelector(rule.selector, finalFormat, {
826
- candidate: original,
833
+ let selector = finalizeSelector(rule.selector, finalFormat, {
834
+ candidate,
827
835
  context,
828
836
  })
837
+
838
+ // Finalize Selector determined that this candidate is irrelevant
839
+ // TODO: This elimination should happen earlier so this never happens
840
+ if (selector === null) {
841
+ rule.remove()
842
+ return
843
+ }
844
+
845
+ rule.selector = selector
829
846
  } catch {
830
847
  // If this selector is invalid we also want to skip it
831
848
  // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
@@ -838,6 +855,11 @@ function applyFinalFormat(match, { context, candidate, original }) {
838
855
  return null
839
856
  }
840
857
 
858
+ // If all rules have been eliminated we can skip this candidate entirely
859
+ if (container.nodes.length === 0) {
860
+ return null
861
+ }
862
+
841
863
  match[1] = container.nodes[0]
842
864
 
843
865
  return match
@@ -875,7 +897,7 @@ function getImportantStrategy(important) {
875
897
  }
876
898
  }
877
899
 
878
- function generateRules(candidates, context) {
900
+ function generateRules(candidates, context, isSorting = false) {
879
901
  let allRules = []
880
902
  let strategy = getImportantStrategy(context.tailwindConfig.important)
881
903
 
@@ -910,7 +932,9 @@ function generateRules(candidates, context) {
910
932
  rule = container.nodes[0]
911
933
  }
912
934
 
913
- let newEntry = [sort, rule]
935
+ // Note: We have to clone rules during sorting
936
+ // so we eliminate some shared mutable state
937
+ let newEntry = [sort, isSorting ? rule.clone() : rule]
914
938
  rules.add(newEntry)
915
939
  context.ruleCache.add(newEntry)
916
940
  allRules.push(newEntry)
@@ -4,6 +4,14 @@ import { transform } from 'sucrase'
4
4
  import { Config } from '../../types/config'
5
5
 
6
6
  let jiti: ReturnType<typeof jitiFactory> | null = null
7
+
8
+ // @internal
9
+ // This WILL be removed in some future release
10
+ // If you rely on this your stuff WILL break
11
+ export function useCustomJiti(_jiti: () => ReturnType<typeof jitiFactory>) {
12
+ jiti = _jiti()
13
+ }
14
+
7
15
  function lazyJiti() {
8
16
  return (
9
17
  jiti ??
@@ -23,7 +23,9 @@ import { remapBitfield } from './remap-bitfield.js'
23
23
  * @property {bigint} arbitrary 0n if false, 1n if true
24
24
  * @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants
25
25
  * @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable.
26
- * @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing.
26
+ * @property {bigint} index Index of the rule / utility in its given *parent* layer. Monotonically increasing.
27
+ * @property {bigint} propertyOffset Offset for the arbitrary property. Only valid after sorting.
28
+ * @property {string} property Name/Value of the arbitrary property.
27
29
  * @property {VariantOption[]} options Some information on how we can sort arbitrary variants
28
30
  */
29
31
 
@@ -88,17 +90,21 @@ export class Offsets {
88
90
  variants: 0n,
89
91
  parallelIndex: 0n,
90
92
  index: this.offsets[layer]++,
93
+ propertyOffset: 0n,
94
+ property: '',
91
95
  options: [],
92
96
  }
93
97
  }
94
98
 
95
99
  /**
100
+ * @param {string} name
96
101
  * @returns {RuleOffset}
97
102
  */
98
- arbitraryProperty() {
103
+ arbitraryProperty(name) {
99
104
  return {
100
105
  ...this.create('utilities'),
101
106
  arbitrary: 1n,
107
+ property: name,
102
108
  }
103
109
  }
104
110
 
@@ -262,6 +268,11 @@ export class Offsets {
262
268
  return a.arbitrary - b.arbitrary
263
269
  }
264
270
 
271
+ // Always sort arbitrary properties alphabetically
272
+ if (a.propertyOffset !== b.propertyOffset) {
273
+ return a.propertyOffset - b.propertyOffset
274
+ }
275
+
265
276
  // Sort utilities, components, etc… in the order they were registered
266
277
  return a.index - b.index
267
278
  }
@@ -320,14 +331,62 @@ export class Offsets {
320
331
  })
321
332
  }
322
333
 
334
+ /**
335
+ * @template T
336
+ * @param {[RuleOffset, T][]} list
337
+ * @returns {[RuleOffset, T][]}
338
+ */
339
+ sortArbitraryProperties(list) {
340
+ // Collect all known arbitrary properties
341
+ let known = new Set()
342
+
343
+ for (let [offset] of list) {
344
+ if (offset.arbitrary === 1n) {
345
+ known.add(offset.property)
346
+ }
347
+ }
348
+
349
+ // No arbitrary properties? Nothing to do.
350
+ if (known.size === 0) {
351
+ return list
352
+ }
353
+
354
+ // Sort the properties alphabetically
355
+ let properties = Array.from(known).sort()
356
+
357
+ // Create a map from the property name to its offset
358
+ let offsets = new Map()
359
+
360
+ let offset = 1n
361
+ for (let property of properties) {
362
+ offsets.set(property, offset++)
363
+ }
364
+
365
+ // Apply the sorted offsets to the list
366
+ return list.map((item) => {
367
+ let [offset, rule] = item
368
+
369
+ offset = {
370
+ ...offset,
371
+ propertyOffset: offsets.get(offset.property) ?? 0n,
372
+ }
373
+
374
+ return [offset, rule]
375
+ })
376
+ }
377
+
323
378
  /**
324
379
  * @template T
325
380
  * @param {[RuleOffset, T][]} list
326
381
  * @returns {[RuleOffset, T][]}
327
382
  */
328
383
  sort(list) {
384
+ // Sort arbitrary variants so they're in alphabetical order
329
385
  list = this.remapArbitraryVariantOffsets(list)
330
386
 
387
+ // Sort arbitrary properties so they're in alphabetical order
388
+ list = this.sortArbitraryProperties(list)
389
+
331
390
  return list.sort(([a], [b]) => bigSign(this.compare(a, b)))
332
391
  }
333
392
  }
@@ -104,8 +104,12 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
104
104
  // we consider them separately because merging the declarations into
105
105
  // a single rule will cause browsers that do not understand the
106
106
  // vendor prefix to throw out the whole rule
107
+ // Additionally if a selector contains `:has` we also consider
108
+ // it separately because FF only recently gained support for it
107
109
  let selectorGroupName =
108
- selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
110
+ selector.includes(':-') || selector.includes('::-') || selector.includes(':has')
111
+ ? selector
112
+ : '__DEFAULT__'
109
113
 
110
114
  let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
111
115
  selectorGroups.set(selectorGroupName, selectors)
@@ -24,6 +24,8 @@ import { Offsets } from './offsets.js'
24
24
  import { flagEnabled } from '../featureFlags.js'
25
25
  import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
26
26
 
27
+ export const INTERNAL_FEATURES = Symbol()
28
+
27
29
  const VARIANT_TYPES = {
28
30
  AddVariant: Symbol.for('ADD_VARIANT'),
29
31
  MatchVariant: Symbol.for('MATCH_VARIANT'),
@@ -146,43 +148,45 @@ function getClasses(selector, mutate) {
146
148
  return parser.transformSync(selector)
147
149
  }
148
150
 
151
+ /**
152
+ * Ignore everything inside a :not(...). This allows you to write code like
153
+ * `div:not(.foo)`. If `.foo` is never found in your code, then we used to
154
+ * not generated it. But now we will ignore everything inside a `:not`, so
155
+ * that it still gets generated.
156
+ *
157
+ * @param {selectorParser.Root} selectors
158
+ */
159
+ function ignoreNot(selectors) {
160
+ selectors.walkPseudos((pseudo) => {
161
+ if (pseudo.value === ':not') {
162
+ pseudo.remove()
163
+ }
164
+ })
165
+ }
166
+
149
167
  function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
150
168
  let classes = []
169
+ let selectors = []
151
170
 
152
- // Handle normal rules
153
171
  if (node.type === 'rule') {
154
- // Ignore everything inside a :not(...). This allows you to write code like
155
- // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
156
- // not generated it. But now we will ignore everything inside a `:not`, so
157
- // that it still gets generated.
158
- function ignoreNot(selectors) {
159
- selectors.walkPseudos((pseudo) => {
160
- if (pseudo.value === ':not') {
161
- pseudo.remove()
162
- }
163
- })
164
- }
172
+ // Handle normal rules
173
+ selectors.push(...node.selectors)
174
+ } else if (node.type === 'atrule') {
175
+ // Handle at-rules (which contains nested rules)
176
+ node.walkRules((rule) => selectors.push(...rule.selectors))
177
+ }
165
178
 
166
- for (let selector of node.selectors) {
167
- let classCandidates = getClasses(selector, ignoreNot)
168
- // At least one of the selectors contains non-"on-demandable" candidates.
169
- if (classCandidates.length === 0) {
170
- state.containsNonOnDemandable = true
171
- }
179
+ for (let selector of selectors) {
180
+ let classCandidates = getClasses(selector, ignoreNot)
172
181
 
173
- for (let classCandidate of classCandidates) {
174
- classes.push(classCandidate)
175
- }
182
+ // At least one of the selectors contains non-"on-demandable" candidates.
183
+ if (classCandidates.length === 0) {
184
+ state.containsNonOnDemandable = true
176
185
  }
177
- }
178
186
 
179
- // Handle at-rules (which contains nested rules)
180
- else if (node.type === 'atrule') {
181
- node.walkRules((rule) => {
182
- for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
183
- classes.push(classCandidate)
184
- }
185
- })
187
+ for (let classCandidate of classCandidates) {
188
+ classes.push(classCandidate)
189
+ }
186
190
  }
187
191
 
188
192
  if (depth === 0) {
@@ -230,8 +234,8 @@ export function parseVariant(variant) {
230
234
  return ({ format }) => format(str)
231
235
  }
232
236
 
233
- let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str)
234
- return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() }))
237
+ let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str)
238
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params?.trim() ?? '' }))
235
239
  })
236
240
  .reverse()
237
241
 
@@ -752,22 +756,46 @@ function resolvePlugins(context, root) {
752
756
  // TODO: This is a workaround for backwards compatibility, since custom variants
753
757
  // were historically sorted before screen/stackable variants.
754
758
  let beforeVariants = [
759
+ variantPlugins['childVariant'],
755
760
  variantPlugins['pseudoElementVariants'],
756
761
  variantPlugins['pseudoClassVariants'],
762
+ variantPlugins['hasVariants'],
757
763
  variantPlugins['ariaVariants'],
758
764
  variantPlugins['dataVariants'],
759
765
  ]
760
766
  let afterVariants = [
761
767
  variantPlugins['supportsVariants'],
762
- variantPlugins['directionVariants'],
763
768
  variantPlugins['reducedMotionVariants'],
764
769
  variantPlugins['prefersContrastVariants'],
765
- variantPlugins['darkVariants'],
766
- variantPlugins['printVariant'],
767
770
  variantPlugins['screenVariants'],
768
771
  variantPlugins['orientationVariants'],
772
+ variantPlugins['directionVariants'],
773
+ variantPlugins['darkVariants'],
774
+ variantPlugins['forcedColorsVariants'],
775
+ variantPlugins['printVariant'],
769
776
  ]
770
777
 
778
+ // This is a compatibility fix for the pre 3.4 dark mode behavior
779
+ // `class` retains the old behavior, but `selector` keeps the new behavior
780
+ let isLegacyDarkMode =
781
+ context.tailwindConfig.darkMode === 'class' ||
782
+ (Array.isArray(context.tailwindConfig.darkMode) &&
783
+ context.tailwindConfig.darkMode[0] === 'class')
784
+
785
+ if (isLegacyDarkMode) {
786
+ afterVariants = [
787
+ variantPlugins['supportsVariants'],
788
+ variantPlugins['reducedMotionVariants'],
789
+ variantPlugins['prefersContrastVariants'],
790
+ variantPlugins['darkVariants'],
791
+ variantPlugins['screenVariants'],
792
+ variantPlugins['orientationVariants'],
793
+ variantPlugins['directionVariants'],
794
+ variantPlugins['forcedColorsVariants'],
795
+ variantPlugins['printVariant'],
796
+ ]
797
+ }
798
+
771
799
  return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
772
800
  }
773
801
 
@@ -943,13 +971,17 @@ function registerPlugins(plugins, context) {
943
971
 
944
972
  // Sort all classes in order
945
973
  // Non-tailwind classes won't be generated and will be left as `null`
946
- let rules = generateRules(new Set(sorted), context)
974
+ let rules = generateRules(new Set(sorted), context, true)
947
975
  rules = context.offsets.sort(rules)
948
976
 
949
977
  let idx = BigInt(parasiteUtilities.length)
950
978
 
951
979
  for (const [, rule] of rules) {
952
- sortedClassNames.set(rule.raws.tailwind.candidate, idx++)
980
+ let candidate = rule.raws.tailwind.candidate
981
+
982
+ // When multiple rules match a candidate
983
+ // always take the position of the first one
984
+ sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++)
953
985
  }
954
986
 
955
987
  return classes.map((className) => {
@@ -1119,17 +1151,24 @@ function registerPlugins(plugins, context) {
1119
1151
  }
1120
1152
 
1121
1153
  let isArbitraryVariant = !(value in (options.values ?? {}))
1154
+ let internalFeatures = options[INTERNAL_FEATURES] ?? {}
1155
+
1156
+ let respectPrefix = (() => {
1157
+ if (isArbitraryVariant) return false
1158
+ if (internalFeatures.respectPrefix === false) return false
1159
+ return true
1160
+ })()
1122
1161
 
1123
1162
  formatStrings = formatStrings.map((format) =>
1124
1163
  format.map((str) => ({
1125
1164
  format: str,
1126
- isArbitraryVariant,
1165
+ respectPrefix,
1127
1166
  }))
1128
1167
  )
1129
1168
 
1130
1169
  manualFormatStrings = manualFormatStrings.map((format) => ({
1131
1170
  format,
1132
- isArbitraryVariant,
1171
+ respectPrefix,
1133
1172
  }))
1134
1173
 
1135
1174
  let opts = {
@@ -63,9 +63,7 @@ function getTailwindConfig(configOrPath) {
63
63
  }
64
64
 
65
65
  // It's a plain object, not a path
66
- let newConfig = resolveConfig(
67
- configOrPath.config === undefined ? configOrPath : configOrPath.config
68
- )
66
+ let newConfig = resolveConfig(configOrPath?.config ?? configOrPath ?? {})
69
67
 
70
68
  newConfig = validateConfig(newConfig)
71
69
 
@@ -210,16 +210,16 @@ let state = {
210
210
  },
211
211
 
212
212
  getContext({ createContext, cliConfigPath, root, result, content }) {
213
+ env.DEBUG && console.time('Searching for config')
214
+ let configPath = findAtConfigPath(root, result) ?? cliConfigPath
215
+ env.DEBUG && console.timeEnd('Searching for config')
216
+
213
217
  if (this.context) {
214
218
  this.context.changedContent = this.changedContent.splice(0)
215
219
 
216
220
  return this.context
217
221
  }
218
222
 
219
- env.DEBUG && console.time('Searching for config')
220
- let configPath = findAtConfigPath(root, result) ?? cliConfigPath
221
- env.DEBUG && console.timeEnd('Searching for config')
222
-
223
223
  env.DEBUG && console.time('Loading config')
224
224
  let config = this.loadConfig(configPath, content)
225
225
  env.DEBUG && console.timeEnd('Loading config')
@@ -277,9 +277,9 @@ export async function createProcessor(args, cliConfigPath) {
277
277
  let tailwindPlugin = () => {
278
278
  return {
279
279
  postcssPlugin: 'tailwindcss',
280
- Once(root, { result }) {
280
+ async Once(root, { result }) {
281
281
  env.DEBUG && console.time('Compiling CSS')
282
- tailwind(({ createContext }) => {
282
+ await tailwind(({ createContext }) => {
283
283
  console.error()
284
284
  console.error('Rebuilding...')
285
285
 
package/src/plugin.js CHANGED
@@ -13,7 +13,7 @@ module.exports = function tailwindcss(configOrPath) {
13
13
  console.time('JIT TOTAL')
14
14
  return root
15
15
  },
16
- function (root, result) {
16
+ async function (root, result) {
17
17
  // Use the path for the `@config` directive if it exists, otherwise use the
18
18
  // path for the file being processed
19
19
  configOrPath = findAtConfigPath(root, result) ?? configOrPath
@@ -25,14 +25,14 @@ module.exports = function tailwindcss(configOrPath) {
25
25
 
26
26
  for (const root of roots) {
27
27
  if (root.type === 'root') {
28
- processTailwindFeatures(context)(root, result)
28
+ await processTailwindFeatures(context)(root, result)
29
29
  }
30
30
  }
31
31
 
32
32
  return
33
33
  }
34
34
 
35
- processTailwindFeatures(context)(root, result)
35
+ await processTailwindFeatures(context)(root, result)
36
36
  },
37
37
  __OXIDE__ &&
38
38
  function lightningCssPlugin(_root, result) {