tailwindcss 0.0.0-insiders.bd970d0 → 0.0.0-insiders.bda8421

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 (137) hide show
  1. package/README.md +1 -2
  2. package/lib/cli/build/deps.js +62 -0
  3. package/lib/cli/build/index.js +12 -15
  4. package/lib/cli/build/plugin.js +107 -167
  5. package/lib/cli/build/utils.js +13 -13
  6. package/lib/cli/build/watching.js +10 -10
  7. package/lib/cli/help/index.js +11 -11
  8. package/lib/cli/index.js +60 -61
  9. package/lib/cli/init/index.js +16 -16
  10. package/lib/cli-peer-dependencies.js +36 -0
  11. package/lib/cli.js +1 -1
  12. package/lib/corePluginList.js +4 -1
  13. package/lib/corePlugins.js +2018 -1905
  14. package/lib/css/preflight.css +18 -12
  15. package/lib/featureFlags.js +17 -23
  16. package/lib/index.js +1 -1
  17. package/lib/lib/cacheInvalidation.js +3 -3
  18. package/lib/lib/collapseAdjacentRules.js +6 -6
  19. package/lib/lib/collapseDuplicateDeclarations.js +1 -1
  20. package/lib/lib/content.js +39 -32
  21. package/lib/lib/defaultExtractor.js +40 -17
  22. package/lib/lib/evaluateTailwindFunctions.js +23 -23
  23. package/lib/lib/expandApplyAtRules.js +37 -18
  24. package/lib/lib/expandTailwindAtRules.js +49 -85
  25. package/lib/lib/findAtConfigPath.js +5 -5
  26. package/lib/lib/generateRules.js +82 -55
  27. package/lib/lib/getModuleDependencies.js +23 -23
  28. package/lib/lib/load-config.js +21 -6
  29. package/lib/lib/normalizeTailwindDirectives.js +36 -21
  30. package/lib/lib/offsets.js +97 -48
  31. package/lib/lib/partitionApplyAtRules.js +2 -2
  32. package/lib/lib/regex.js +5 -5
  33. package/lib/lib/resolveDefaultsAtRules.js +27 -25
  34. package/lib/lib/setupContextUtils.js +195 -167
  35. package/lib/lib/setupTrackingContext.js +4 -5
  36. package/lib/lib/sharedState.js +10 -10
  37. package/lib/lib/substituteScreenAtRules.js +4 -4
  38. package/lib/plugin.js +8 -124
  39. package/lib/postcss-plugins/nesting/index.js +2 -2
  40. package/lib/postcss-plugins/nesting/plugin.js +11 -11
  41. package/lib/processTailwindFeatures.js +3 -5
  42. package/lib/public/colors.js +264 -264
  43. package/lib/util/applyImportantSelector.js +2 -2
  44. package/lib/util/buildMediaQuery.js +2 -2
  45. package/lib/util/cloneDeep.js +1 -1
  46. package/lib/util/cloneNodes.js +33 -13
  47. package/lib/util/color.js +19 -19
  48. package/lib/util/createUtilityPlugin.js +3 -3
  49. package/lib/util/dataTypes.js +170 -113
  50. package/lib/util/defaults.js +4 -4
  51. package/lib/util/escapeClassName.js +2 -2
  52. package/lib/util/escapeCommas.js +1 -1
  53. package/lib/util/flattenColorPalette.js +2 -2
  54. package/lib/util/formatVariantSelector.js +24 -19
  55. package/lib/util/getAllConfigs.js +4 -5
  56. package/lib/util/isKeyframeRule.js +1 -1
  57. package/lib/util/isPlainObject.js +2 -2
  58. package/lib/util/isSyntacticallyValidPropertyValue.js +10 -10
  59. package/lib/util/log.js +6 -23
  60. package/lib/util/nameClass.js +4 -4
  61. package/lib/util/negateValue.js +8 -8
  62. package/lib/util/normalizeConfig.js +63 -94
  63. package/lib/util/normalizeScreens.js +18 -18
  64. package/lib/util/parseAnimationValue.js +38 -38
  65. package/lib/util/parseBoxShadowValue.js +18 -18
  66. package/lib/util/parseDependency.js +4 -4
  67. package/lib/util/parseGlob.js +4 -4
  68. package/lib/util/parseObjectStyles.js +1 -1
  69. package/lib/util/pluginUtils.js +36 -21
  70. package/lib/util/prefixSelector.js +4 -4
  71. package/lib/util/pseudoElements.js +72 -69
  72. package/lib/util/removeAlphaVariables.js +1 -1
  73. package/lib/util/resolveConfig.js +11 -15
  74. package/lib/util/resolveConfigPath.js +6 -6
  75. package/lib/util/responsive.js +1 -1
  76. package/lib/util/splitAtTopLevelOnly.js +3 -3
  77. package/lib/util/toColorValue.js +1 -1
  78. package/lib/util/toPath.js +2 -2
  79. package/lib/util/transformThemeValue.js +24 -24
  80. package/lib/util/validateConfig.js +9 -20
  81. package/lib/util/validateFormalSyntax.js +5 -5
  82. package/lib/util/withAlphaVariable.js +5 -5
  83. package/lib/value-parser/index.js +5 -5
  84. package/lib/value-parser/parse.js +43 -43
  85. package/lib/value-parser/stringify.js +11 -11
  86. package/lib/value-parser/unit.js +5 -5
  87. package/lib/value-parser/walk.js +1 -1
  88. package/nesting/index.d.ts +4 -0
  89. package/package.json +41 -36
  90. package/peers/index.js +96503 -0
  91. package/resolveConfig.d.ts +22 -3
  92. package/scripts/generate-types.js +1 -2
  93. package/scripts/release-notes.js +1 -1
  94. package/src/cli/build/deps.js +56 -0
  95. package/src/cli/build/index.js +0 -4
  96. package/src/cli/build/plugin.js +53 -85
  97. package/src/cli/build/watching.js +1 -1
  98. package/src/cli/index.js +0 -1
  99. package/src/cli-peer-dependencies.js +15 -0
  100. package/src/corePluginList.js +1 -1
  101. package/src/corePlugins.js +118 -31
  102. package/src/css/preflight.css +18 -12
  103. package/src/featureFlags.js +3 -5
  104. package/src/lib/content.js +39 -30
  105. package/src/lib/defaultExtractor.js +29 -6
  106. package/src/lib/expandApplyAtRules.js +24 -0
  107. package/src/lib/expandTailwindAtRules.js +19 -56
  108. package/src/lib/generateRules.js +39 -8
  109. package/src/lib/load-config.ts +13 -0
  110. package/src/lib/normalizeTailwindDirectives.js +27 -0
  111. package/src/lib/offsets.js +62 -3
  112. package/src/lib/resolveDefaultsAtRules.js +5 -1
  113. package/src/lib/setupContextUtils.js +79 -39
  114. package/src/plugin.js +0 -86
  115. package/src/processTailwindFeatures.js +0 -3
  116. package/src/util/cloneNodes.js +35 -14
  117. package/src/util/dataTypes.js +72 -6
  118. package/src/util/formatVariantSelector.js +7 -0
  119. package/src/util/isPlainObject.js +1 -1
  120. package/src/util/log.js +0 -24
  121. package/src/util/normalizeConfig.js +11 -38
  122. package/src/util/pluginUtils.js +20 -0
  123. package/src/util/pseudoElements.js +4 -0
  124. package/src/util/validateConfig.js +0 -10
  125. package/stubs/config.full.js +68 -8
  126. package/stubs/config.simple.js +1 -1
  127. package/stubs/postcss.config.cjs +1 -0
  128. package/stubs/postcss.config.js +1 -0
  129. package/types/config.d.ts +12 -5
  130. package/types/generated/corePluginList.d.ts +1 -1
  131. package/types/generated/default-theme.d.ts +28 -9
  132. package/types/index.d.ts +7 -3
  133. package/index.css +0 -5
  134. package/lib/lib/detectNesting.js +0 -45
  135. package/lib/lib/handleImportAtRules.js +0 -50
  136. package/src/lib/detectNesting.js +0 -47
  137. package/src/lib/handleImportAtRules.js +0 -34
@@ -1,12 +1,10 @@
1
1
  import fs from 'fs'
2
2
  import LRU from '@alloc/quick-lru'
3
- import { parseCandidateStrings, IO, Parsing } from '@tailwindcss/oxide'
4
3
  import * as sharedState from './sharedState'
5
4
  import { generateRules } from './generateRules'
6
5
  import log from '../util/log'
7
6
  import cloneNodes from '../util/cloneNodes'
8
7
  import { defaultExtractor } from './defaultExtractor'
9
- import { flagEnabled } from '../featureFlags'
10
8
 
11
9
  let env = sharedState.env
12
10
 
@@ -26,10 +24,7 @@ function getExtractor(context, fileExtension) {
26
24
  extractors[fileExtension] ||
27
25
  extractors.DEFAULT ||
28
26
  builtInExtractors[fileExtension] ||
29
- // Because we call `DEFAULT(context)`, the returning function is always a new function without a
30
- // stable identity. Marking it with `DEFAULT_EXTRACTOR` allows us to check if it is the default
31
- // extractor without relying on the function identity.
32
- Object.assign(builtInExtractors.DEFAULT(context), { DEFAULT_EXTRACTOR: true })
27
+ builtInExtractors.DEFAULT(context)
33
28
  )
34
29
  }
35
30
 
@@ -135,43 +130,21 @@ export default function expandTailwindAtRules(context) {
135
130
 
136
131
  env.DEBUG && console.time('Reading changed files')
137
132
 
138
- if (flagEnabled(context.tailwindConfig, 'oxideParser')) {
139
- let rustParserContent = []
140
- let regexParserContent = []
133
+ /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
134
+ let regexParserContent = []
141
135
 
142
- for (let item of context.changedContent) {
143
- let transformer = getTransformer(context.tailwindConfig, item.extension)
144
- let extractor = getExtractor(context, item.extension)
145
-
146
- if (transformer === builtInTransformers.DEFAULT && extractor?.DEFAULT_EXTRACTOR === true) {
147
- rustParserContent.push(item)
148
- } else {
149
- regexParserContent.push([item, { transformer, extractor }])
150
- }
151
- }
136
+ for (let item of context.changedContent) {
137
+ let transformer = getTransformer(context.tailwindConfig, item.extension)
138
+ let extractor = getExtractor(context, item.extension)
139
+ regexParserContent.push([item, { transformer, extractor }])
140
+ }
152
141
 
153
- if (rustParserContent.length > 0) {
154
- for (let candidate of parseCandidateStrings(
155
- rustParserContent,
156
- IO.Parallel | Parsing.Parallel
157
- )) {
158
- candidates.add(candidate)
159
- }
160
- }
142
+ const BATCH_SIZE = 500
161
143
 
162
- if (regexParserContent.length > 0) {
163
- await Promise.all(
164
- regexParserContent.map(async ([{ file, content }, { transformer, extractor }]) => {
165
- content = file ? await fs.promises.readFile(file, 'utf8') : content
166
- getClassCandidates(transformer(content), extractor, candidates, seen)
167
- })
168
- )
169
- }
170
- } else {
144
+ for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
145
+ let batch = regexParserContent.slice(i, i + BATCH_SIZE)
171
146
  await Promise.all(
172
- context.changedContent.map(async ({ file, content, extension }) => {
173
- let transformer = getTransformer(context.tailwindConfig, extension)
174
- let extractor = getExtractor(context, extension)
147
+ batch.map(async ([{ file, content }, { transformer, extractor }]) => {
175
148
  content = file ? await fs.promises.readFile(file, 'utf8') : content
176
149
  getClassCandidates(transformer(content), extractor, candidates, seen)
177
150
  })
@@ -187,9 +160,6 @@ export default function expandTailwindAtRules(context) {
187
160
 
188
161
  env.DEBUG && console.time('Generate rules')
189
162
  env.DEBUG && console.time('Sorting candidates')
190
- // TODO: only sort if we are not using the oxide parser (flagEnabled(context.tailwindConfig,
191
- // 'oxideParser')) AND if we got all the candidates form the oxideParser alone. This will not
192
- // be the case currently if you have custom transformers / extractors.
193
163
  let sortedCandidates = new Set(
194
164
  [...candidates].sort((a, z) => {
195
165
  if (a === z) return 0
@@ -270,23 +240,16 @@ export default function expandTailwindAtRules(context) {
270
240
  )
271
241
  layerNodes.variants.remove()
272
242
  } else if (variantNodes.length > 0) {
273
- let cloned = cloneNodes(variantNodes, undefined, {
274
- layer: 'variants',
275
- })
276
-
277
- cloned.forEach((node) => {
278
- let parentLayer = node.raws.tailwind?.parentLayer ?? null
279
-
280
- node.walk((n) => {
281
- if (!n.source) {
282
- n.source = layerNodes[parentLayer].source
283
- }
243
+ root.append(
244
+ cloneNodes(variantNodes, root.source, {
245
+ layer: 'variants',
284
246
  })
285
- })
286
-
287
- root.append(cloned)
247
+ )
288
248
  }
289
249
 
250
+ // TODO: Why is the root node having no source location for `end` possible?
251
+ root.source.end = root.source.end ?? root.source.start
252
+
290
253
  // If we've got a utility layer and no utilities are generated there's likely something wrong
291
254
  const hasUtilityVariants = variantNodes.some(
292
255
  (node) => node.raws.tailwind?.parentLayer === 'utilities'
@@ -16,6 +16,7 @@ import { normalize } from '../util/dataTypes'
16
16
  import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
17
17
  import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
18
18
  import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
19
+ import { flagEnabled } from '../featureFlags'
19
20
  import { applyImportantSelector } from '../util/applyImportantSelector'
20
21
 
21
22
  let classNameParser = selectorParser((selectors) => {
@@ -44,7 +45,7 @@ function* candidatePermutations(candidate) {
44
45
  let bracketIdx = candidate.indexOf('[')
45
46
 
46
47
  // If character before `[` isn't a dash or a slash, this isn't a dynamic class
47
- // e.g. string[]
48
+ // eg. string[]
48
49
  if (candidate[bracketIdx - 1] === '-') {
49
50
  dashIdx = bracketIdx - 1
50
51
  } else if (candidate[bracketIdx - 1] === '/') {
@@ -118,10 +119,20 @@ function applyImportant(matches, classCandidate) {
118
119
 
119
120
  let result = []
120
121
 
122
+ function isInKeyframes(rule) {
123
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
124
+ }
125
+
121
126
  for (let [meta, rule] of matches) {
122
127
  let container = postcss.root({ nodes: [rule.clone()] })
123
128
 
124
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
+
125
136
  let ast = selectorParser().astSync(r.selector)
126
137
 
127
138
  // Remove extraneous selectors that do not include the base candidate
@@ -174,6 +185,10 @@ function applyVariant(variant, matches, context) {
174
185
  if (modifiers.length && !context.variantMap.has(variant)) {
175
186
  variant = baseVariant
176
187
  args.modifier = modifiers[0]
188
+
189
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
190
+ return []
191
+ }
177
192
  }
178
193
  }
179
194
 
@@ -461,9 +476,9 @@ function isParsableNode(node) {
461
476
  }
462
477
 
463
478
  function isParsableCssValue(property, value) {
464
- // We don't want to treat [https://example.com] as a custom property
479
+ // We don't want to to treat [https://example.com] as a custom property
465
480
  // Even though, according to the CSS grammar, it's a totally valid CSS declaration
466
- // So we short-circuit here by checking if the custom property looks like a URL
481
+ // So we short-circuit here by checking if the custom property looks like a url
467
482
  if (looksLikeUri(`${property}:${value}`)) {
468
483
  return false
469
484
  }
@@ -491,13 +506,13 @@ function extractArbitraryProperty(classCandidate, context) {
491
506
  return null
492
507
  }
493
508
 
494
- let normalized = normalize(value)
509
+ let normalized = normalize(value, { property })
495
510
 
496
511
  if (!isParsableCssValue(property, normalized)) {
497
512
  return null
498
513
  }
499
514
 
500
- let sort = context.offsets.arbitraryProperty()
515
+ let sort = context.offsets.arbitraryProperty(classCandidate)
501
516
 
502
517
  return [
503
518
  [
@@ -815,10 +830,19 @@ function applyFinalFormat(match, { context, candidate }) {
815
830
  }
816
831
 
817
832
  try {
818
- rule.selector = finalizeSelector(rule.selector, finalFormat, {
833
+ let selector = finalizeSelector(rule.selector, finalFormat, {
819
834
  candidate,
820
835
  context,
821
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
822
846
  } catch {
823
847
  // If this selector is invalid we also want to skip it
824
848
  // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
@@ -831,6 +855,11 @@ function applyFinalFormat(match, { context, candidate }) {
831
855
  return null
832
856
  }
833
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
+
834
863
  match[1] = container.nodes[0]
835
864
 
836
865
  return match
@@ -868,7 +897,7 @@ function getImportantStrategy(important) {
868
897
  }
869
898
  }
870
899
 
871
- function generateRules(candidates, context) {
900
+ function generateRules(candidates, context, isSorting = false) {
872
901
  let allRules = []
873
902
  let strategy = getImportantStrategy(context.tailwindConfig.important)
874
903
 
@@ -903,7 +932,9 @@ function generateRules(candidates, context) {
903
932
  rule = container.nodes[0]
904
933
  }
905
934
 
906
- 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]
907
938
  rules.add(newEntry)
908
939
  context.ruleCache.add(newEntry)
909
940
  allRules.push(newEntry)
@@ -4,12 +4,25 @@ 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 ??
10
18
  (jiti = jitiFactory(__filename, {
11
19
  interopDefault: true,
12
20
  transform: (opts) => {
21
+ // Sucrase can't transform import.meta so we have to use Babel
22
+ if (opts.source.includes('import.meta')) {
23
+ return require('jiti/dist/babel.js')(opts)
24
+ }
25
+
13
26
  return transform(opts.source, {
14
27
  transforms: ['typescript', 'imports'],
15
28
  })
@@ -10,6 +10,33 @@ export default function normalizeTailwindDirectives(root) {
10
10
  applyDirectives.add(atRule)
11
11
  }
12
12
 
13
+ if (atRule.name === 'import') {
14
+ if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
15
+ atRule.name = 'tailwind'
16
+ atRule.params = 'base'
17
+ } else if (
18
+ atRule.params === '"tailwindcss/components"' ||
19
+ atRule.params === "'tailwindcss/components'"
20
+ ) {
21
+ atRule.name = 'tailwind'
22
+ atRule.params = 'components'
23
+ } else if (
24
+ atRule.params === '"tailwindcss/utilities"' ||
25
+ atRule.params === "'tailwindcss/utilities'"
26
+ ) {
27
+ atRule.name = 'tailwind'
28
+ atRule.params = 'utilities'
29
+ } else if (
30
+ atRule.params === '"tailwindcss/screens"' ||
31
+ atRule.params === "'tailwindcss/screens'" ||
32
+ atRule.params === '"tailwindcss/variants"' ||
33
+ atRule.params === "'tailwindcss/variants'"
34
+ ) {
35
+ atRule.name = 'tailwind'
36
+ atRule.params = 'variants'
37
+ }
38
+ }
39
+
13
40
  if (atRule.name === 'tailwind') {
14
41
  if (atRule.params === 'screens') {
15
42
  atRule.params = 'variants'
@@ -24,6 +24,8 @@ import { remapBitfield } from './remap-bitfield.js'
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
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
  }
@@ -302,7 +313,7 @@ export class Offsets {
302
313
  let mapping = this.recalculateVariantOffsets()
303
314
 
304
315
  // No arbitrary variants? Nothing to do.
305
- // Everything already in order? Nothing to do.
316
+ // Everyhing already in order? Nothing to do.
306
317
  if (mapping.length === 0) {
307
318
  return list
308
319
  }
@@ -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
  }
@@ -354,7 +413,7 @@ function max(nums) {
354
413
  * Using `.sort()` without a custom compare function is faster
355
414
  * But you can only use that if you're sorting an array of
356
415
  * only strings. If you're sorting strings inside objects
357
- * or arrays, you need to use a custom compare function.
416
+ * or arrays, you need must use a custom compare function.
358
417
  *
359
418
  * @param {string} a
360
419
  * @param {string} b
@@ -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)
@@ -21,6 +21,7 @@ import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPrope
21
21
  import { generateRules, getClassNameFromSelector } from './generateRules'
22
22
  import { hasContentChanged } from './cacheInvalidation.js'
23
23
  import { Offsets } from './offsets.js'
24
+ import { flagEnabled } from '../featureFlags.js'
24
25
  import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
25
26
 
26
27
  export const INTERNAL_FEATURES = Symbol()
@@ -147,43 +148,45 @@ function getClasses(selector, mutate) {
147
148
  return parser.transformSync(selector)
148
149
  }
149
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
+
150
167
  function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
151
168
  let classes = []
169
+ let selectors = []
152
170
 
153
- // Handle normal rules
154
171
  if (node.type === 'rule') {
155
- // Ignore everything inside a :not(...). This allows you to write code like
156
- // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
157
- // not generated it. But now we will ignore everything inside a `:not`, so
158
- // that it still gets generated.
159
- function ignoreNot(selectors) {
160
- selectors.walkPseudos((pseudo) => {
161
- if (pseudo.value === ':not') {
162
- pseudo.remove()
163
- }
164
- })
165
- }
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
+ }
166
178
 
167
- for (let selector of node.selectors) {
168
- let classCandidates = getClasses(selector, ignoreNot)
169
- // At least one of the selectors contains non-"on-demandable" candidates.
170
- if (classCandidates.length === 0) {
171
- state.containsNonOnDemandable = true
172
- }
179
+ for (let selector of selectors) {
180
+ let classCandidates = getClasses(selector, ignoreNot)
173
181
 
174
- for (let classCandidate of classCandidates) {
175
- classes.push(classCandidate)
176
- }
182
+ // At least one of the selectors contains non-"on-demandable" candidates.
183
+ if (classCandidates.length === 0) {
184
+ state.containsNonOnDemandable = true
177
185
  }
178
- }
179
186
 
180
- // Handle at-rules (which contains nested rules)
181
- else if (node.type === 'atrule') {
182
- node.walkRules((rule) => {
183
- for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
184
- classes.push(classCandidate)
185
- }
186
- })
187
+ for (let classCandidate of classCandidates) {
188
+ classes.push(classCandidate)
189
+ }
187
190
  }
188
191
 
189
192
  if (depth === 0) {
@@ -435,8 +438,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
435
438
  },
436
439
  }
437
440
 
441
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
442
+
438
443
  let ruleSets = []
439
- .concat(rule(value, extras))
444
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
440
445
  .filter(Boolean)
441
446
  .map((declaration) => ({
442
447
  [nameClass(identifier, modifier)]: declaration,
@@ -513,8 +518,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
513
518
  },
514
519
  }
515
520
 
521
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
522
+
516
523
  let ruleSets = []
517
- .concat(rule(value, extras))
524
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
518
525
  .filter(Boolean)
519
526
  .map((declaration) => ({
520
527
  [nameClass(identifier, modifier)]: declaration,
@@ -582,13 +589,18 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
582
589
  let id = options?.id ?? ++variantIdentifier
583
590
  let isSpecial = variant === '@'
584
591
 
592
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
593
+
585
594
  for (let [key, value] of Object.entries(options?.values ?? {})) {
586
595
  if (key === 'DEFAULT') continue
587
596
 
588
597
  api.addVariant(
589
598
  isSpecial ? `${variant}${key}` : `${variant}-${key}`,
590
599
  ({ args, container }) => {
591
- return variantFn(value, { modifier: args?.modifier, container })
600
+ return variantFn(
601
+ value,
602
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
603
+ )
592
604
  },
593
605
 
594
606
  {
@@ -616,7 +628,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
616
628
  : // Falling back to args if it is a string, otherwise '' for older intellisense
617
629
  // (JetBrains) plugins.
618
630
  args?.value ?? (typeof args === 'string' ? args : ''),
619
- { modifier: args?.modifier, container }
631
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
620
632
  )
621
633
  },
622
634
  {
@@ -721,7 +733,7 @@ function collectLayerPlugins(root) {
721
733
  }
722
734
 
723
735
  function resolvePlugins(context, root) {
724
- let corePluginList = Object.entries(corePlugins)
736
+ let corePluginList = Object.entries({ ...variantPlugins, ...corePlugins })
725
737
  .map(([name, plugin]) => {
726
738
  if (!context.tailwindConfig.corePlugins.includes(name)) {
727
739
  return null
@@ -744,6 +756,7 @@ function resolvePlugins(context, root) {
744
756
  // TODO: This is a workaround for backwards compatibility, since custom variants
745
757
  // were historically sorted before screen/stackable variants.
746
758
  let beforeVariants = [
759
+ variantPlugins['childVariant'],
747
760
  variantPlugins['pseudoElementVariants'],
748
761
  variantPlugins['pseudoClassVariants'],
749
762
  variantPlugins['hasVariants'],
@@ -752,15 +765,37 @@ function resolvePlugins(context, root) {
752
765
  ]
753
766
  let afterVariants = [
754
767
  variantPlugins['supportsVariants'],
755
- variantPlugins['directionVariants'],
756
768
  variantPlugins['reducedMotionVariants'],
757
769
  variantPlugins['prefersContrastVariants'],
758
- variantPlugins['darkVariants'],
759
- variantPlugins['printVariant'],
760
770
  variantPlugins['screenVariants'],
761
771
  variantPlugins['orientationVariants'],
772
+ variantPlugins['directionVariants'],
773
+ variantPlugins['darkVariants'],
774
+ variantPlugins['forcedColorsVariants'],
775
+ variantPlugins['printVariant'],
762
776
  ]
763
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
+
764
799
  return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
765
800
  }
766
801
 
@@ -936,7 +971,7 @@ function registerPlugins(plugins, context) {
936
971
 
937
972
  // Sort all classes in order
938
973
  // Non-tailwind classes won't be generated and will be left as `null`
939
- let rules = generateRules(new Set(sorted), context)
974
+ let rules = generateRules(new Set(sorted), context, true)
940
975
  rules = context.offsets.sort(rules)
941
976
 
942
977
  let idx = BigInt(parasiteUtilities.length)
@@ -1009,6 +1044,11 @@ function registerPlugins(plugins, context) {
1009
1044
 
1010
1045
  // Generate a list of available variants with meta information of the type of variant.
1011
1046
  context.getVariants = function getVariants() {
1047
+ // We use a unique, random ID for candidate names to avoid conflicts
1048
+ // We can't use characters like `_`, `:`, `@` or `.` because they might
1049
+ // be used as a separator
1050
+ let id = Math.random().toString(36).substring(7).toUpperCase()
1051
+
1012
1052
  let result = []
1013
1053
  for (let [name, options] of context.variantOptions.entries()) {
1014
1054
  if (options.variantInfo === VARIANT_INFO.Base) continue
@@ -1019,7 +1059,7 @@ function registerPlugins(plugins, context) {
1019
1059
  values: Object.keys(options.values ?? {}),
1020
1060
  hasDash: name !== '@',
1021
1061
  selectors({ modifier, value } = {}) {
1022
- let candidate = '__TAILWIND_PLACEHOLDER__'
1062
+ let candidate = `TAILWINDPLACEHOLDER${id}`
1023
1063
 
1024
1064
  let rule = postcss.rule({ selector: `.${candidate}` })
1025
1065
  let container = postcss.root({ nodes: [rule.clone()] })