tailwindcss 0.0.0-insiders.bd4dddd → 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 (224) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +13 -10
  3. package/colors.d.ts +3 -0
  4. package/defaultConfig.d.ts +3 -0
  5. package/defaultTheme.d.ts +4 -0
  6. package/lib/cli/build/deps.js +62 -0
  7. package/lib/cli/build/index.js +54 -0
  8. package/lib/cli/build/plugin.js +371 -0
  9. package/lib/cli/build/utils.js +88 -0
  10. package/lib/cli/build/watching.js +182 -0
  11. package/lib/cli/help/index.js +73 -0
  12. package/lib/cli/index.js +230 -0
  13. package/lib/cli/init/index.js +63 -0
  14. package/lib/cli-peer-dependencies.js +28 -7
  15. package/lib/cli.js +1 -741
  16. package/lib/corePluginList.js +17 -4
  17. package/lib/corePlugins.js +2503 -1839
  18. package/lib/css/preflight.css +32 -13
  19. package/lib/featureFlags.js +45 -28
  20. package/lib/index.js +1 -29
  21. package/lib/lib/cacheInvalidation.js +92 -0
  22. package/lib/lib/collapseAdjacentRules.js +30 -10
  23. package/lib/lib/collapseDuplicateDeclarations.js +13 -8
  24. package/lib/lib/content.js +214 -0
  25. package/lib/lib/defaultExtractor.js +240 -31
  26. package/lib/lib/evaluateTailwindFunctions.js +117 -50
  27. package/lib/lib/expandApplyAtRules.js +466 -230
  28. package/lib/lib/expandTailwindAtRules.js +156 -135
  29. package/lib/lib/findAtConfigPath.js +46 -0
  30. package/lib/lib/generateRules.js +554 -200
  31. package/lib/lib/getModuleDependencies.js +88 -37
  32. package/lib/lib/load-config.js +57 -0
  33. package/lib/lib/normalizeTailwindDirectives.js +39 -33
  34. package/lib/lib/offsets.js +355 -0
  35. package/lib/lib/partitionApplyAtRules.js +58 -0
  36. package/lib/lib/regex.js +74 -0
  37. package/lib/lib/remap-bitfield.js +89 -0
  38. package/lib/lib/resolveDefaultsAtRules.js +97 -69
  39. package/lib/lib/setupContextUtils.js +796 -341
  40. package/lib/lib/setupTrackingContext.js +66 -71
  41. package/lib/lib/sharedState.js +45 -14
  42. package/lib/lib/substituteScreenAtRules.js +14 -10
  43. package/lib/plugin.js +48 -0
  44. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  45. package/lib/postcss-plugins/nesting/index.js +21 -0
  46. package/lib/postcss-plugins/nesting/plugin.js +89 -0
  47. package/lib/processTailwindFeatures.js +37 -27
  48. package/lib/public/colors.js +272 -246
  49. package/lib/public/create-plugin.js +9 -5
  50. package/lib/public/default-config.js +10 -6
  51. package/lib/public/default-theme.js +10 -6
  52. package/lib/public/load-config.js +12 -0
  53. package/lib/public/resolve-config.js +11 -6
  54. package/lib/util/applyImportantSelector.js +36 -0
  55. package/lib/util/bigSign.js +6 -1
  56. package/lib/util/buildMediaQuery.js +13 -6
  57. package/lib/util/cloneDeep.js +9 -6
  58. package/lib/util/cloneNodes.js +44 -4
  59. package/lib/util/color.js +70 -53
  60. package/lib/util/colorNames.js +752 -0
  61. package/lib/util/configurePlugins.js +7 -2
  62. package/lib/util/createPlugin.js +8 -6
  63. package/lib/util/createUtilityPlugin.js +16 -16
  64. package/lib/util/dataTypes.js +295 -113
  65. package/lib/util/defaults.js +12 -7
  66. package/lib/util/escapeClassName.js +13 -8
  67. package/lib/util/escapeCommas.js +7 -2
  68. package/lib/util/flattenColorPalette.js +11 -12
  69. package/lib/util/formatVariantSelector.js +235 -151
  70. package/lib/util/getAllConfigs.js +33 -12
  71. package/lib/util/hashConfig.js +9 -4
  72. package/lib/util/isKeyframeRule.js +7 -2
  73. package/lib/util/isPlainObject.js +8 -3
  74. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +25 -15
  75. package/lib/util/log.js +25 -15
  76. package/lib/util/nameClass.js +27 -10
  77. package/lib/util/negateValue.js +25 -8
  78. package/lib/util/normalizeConfig.js +127 -82
  79. package/lib/util/normalizeScreens.js +128 -11
  80. package/lib/util/parseAnimationValue.js +44 -40
  81. package/lib/util/parseBoxShadowValue.js +34 -23
  82. package/lib/util/parseDependency.js +39 -55
  83. package/lib/util/parseGlob.js +36 -0
  84. package/lib/util/parseObjectStyles.js +15 -10
  85. package/lib/util/pluginUtils.js +172 -69
  86. package/lib/util/prefixSelector.js +30 -12
  87. package/lib/util/pseudoElements.js +212 -0
  88. package/lib/util/removeAlphaVariables.js +31 -0
  89. package/lib/util/resolveConfig.js +101 -87
  90. package/lib/util/resolveConfigPath.js +30 -12
  91. package/lib/util/responsive.js +11 -6
  92. package/lib/util/splitAtTopLevelOnly.js +51 -0
  93. package/lib/util/tap.js +6 -1
  94. package/lib/util/toColorValue.js +7 -3
  95. package/lib/util/toPath.js +22 -4
  96. package/lib/util/transformThemeValue.js +40 -30
  97. package/lib/util/validateConfig.js +37 -0
  98. package/lib/util/validateFormalSyntax.js +26 -0
  99. package/lib/util/withAlphaVariable.js +27 -15
  100. package/lib/value-parser/LICENSE +22 -0
  101. package/lib/value-parser/README.md +3 -0
  102. package/lib/value-parser/index.d.js +2 -0
  103. package/lib/value-parser/index.js +22 -0
  104. package/lib/value-parser/parse.js +259 -0
  105. package/lib/value-parser/stringify.js +38 -0
  106. package/lib/value-parser/unit.js +86 -0
  107. package/lib/value-parser/walk.js +16 -0
  108. package/loadConfig.d.ts +4 -0
  109. package/loadConfig.js +2 -0
  110. package/nesting/index.d.ts +4 -0
  111. package/nesting/index.js +2 -12
  112. package/package.json +60 -55
  113. package/peers/index.js +75503 -55095
  114. package/plugin.d.ts +11 -0
  115. package/resolveConfig.d.ts +31 -0
  116. package/scripts/generate-types.js +104 -0
  117. package/scripts/release-channel.js +18 -0
  118. package/scripts/release-notes.js +21 -0
  119. package/scripts/type-utils.js +27 -0
  120. package/src/cli/build/deps.js +56 -0
  121. package/src/cli/build/index.js +49 -0
  122. package/src/cli/build/plugin.js +437 -0
  123. package/src/cli/build/utils.js +76 -0
  124. package/src/cli/build/watching.js +229 -0
  125. package/src/cli/help/index.js +70 -0
  126. package/src/cli/index.js +216 -0
  127. package/src/cli/init/index.js +79 -0
  128. package/src/cli-peer-dependencies.js +7 -1
  129. package/src/cli.js +1 -811
  130. package/src/corePluginList.js +1 -1
  131. package/src/corePlugins.js +849 -243
  132. package/src/css/preflight.css +32 -13
  133. package/src/featureFlags.js +13 -9
  134. package/src/index.js +1 -28
  135. package/src/lib/cacheInvalidation.js +52 -0
  136. package/src/lib/collapseAdjacentRules.js +21 -2
  137. package/src/lib/content.js +249 -0
  138. package/src/lib/defaultExtractor.js +215 -33
  139. package/src/lib/evaluateTailwindFunctions.js +82 -8
  140. package/src/lib/expandApplyAtRules.js +512 -224
  141. package/src/lib/expandTailwindAtRules.js +102 -87
  142. package/src/lib/findAtConfigPath.js +48 -0
  143. package/src/lib/generateRules.js +541 -146
  144. package/src/lib/getModuleDependencies.js +70 -30
  145. package/src/lib/load-config.ts +44 -0
  146. package/src/lib/normalizeTailwindDirectives.js +1 -0
  147. package/src/lib/offsets.js +432 -0
  148. package/src/lib/partitionApplyAtRules.js +52 -0
  149. package/src/lib/regex.js +74 -0
  150. package/src/lib/remap-bitfield.js +82 -0
  151. package/src/lib/resolveDefaultsAtRules.js +55 -30
  152. package/src/lib/setupContextUtils.js +720 -190
  153. package/src/lib/setupTrackingContext.js +47 -61
  154. package/src/lib/sharedState.js +15 -4
  155. package/src/plugin.js +47 -0
  156. package/src/postcss-plugins/nesting/README.md +42 -0
  157. package/src/postcss-plugins/nesting/index.js +13 -0
  158. package/src/postcss-plugins/nesting/plugin.js +80 -0
  159. package/src/processTailwindFeatures.js +11 -4
  160. package/src/public/colors.js +22 -0
  161. package/src/public/default-config.js +1 -1
  162. package/src/public/default-theme.js +2 -2
  163. package/src/public/load-config.js +2 -0
  164. package/src/util/applyImportantSelector.js +27 -0
  165. package/src/util/buildMediaQuery.js +5 -3
  166. package/src/util/cloneNodes.js +40 -2
  167. package/src/util/color.js +39 -20
  168. package/src/util/colorNames.js +150 -0
  169. package/src/util/dataTypes.js +184 -26
  170. package/src/util/formatVariantSelector.js +272 -144
  171. package/src/util/getAllConfigs.js +21 -2
  172. package/src/util/isPlainObject.js +1 -1
  173. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  174. package/src/util/log.js +8 -8
  175. package/src/util/nameClass.js +4 -0
  176. package/src/util/negateValue.js +11 -3
  177. package/src/util/normalizeConfig.js +45 -6
  178. package/src/util/normalizeScreens.js +99 -4
  179. package/src/util/parseBoxShadowValue.js +4 -3
  180. package/src/util/parseDependency.js +37 -42
  181. package/src/util/parseGlob.js +24 -0
  182. package/src/util/pluginUtils.js +139 -24
  183. package/src/util/prefixSelector.js +31 -10
  184. package/src/util/pseudoElements.js +171 -0
  185. package/src/util/removeAlphaVariables.js +24 -0
  186. package/src/util/resolveConfig.js +70 -32
  187. package/src/util/resolveConfigPath.js +12 -1
  188. package/src/util/splitAtTopLevelOnly.js +52 -0
  189. package/src/util/toPath.js +1 -1
  190. package/src/util/transformThemeValue.js +13 -3
  191. package/src/util/validateConfig.js +26 -0
  192. package/src/util/validateFormalSyntax.js +34 -0
  193. package/src/util/withAlphaVariable.js +1 -1
  194. package/src/value-parser/LICENSE +22 -0
  195. package/src/value-parser/README.md +3 -0
  196. package/src/value-parser/index.d.ts +177 -0
  197. package/src/value-parser/index.js +28 -0
  198. package/src/value-parser/parse.js +303 -0
  199. package/src/value-parser/stringify.js +41 -0
  200. package/src/value-parser/unit.js +118 -0
  201. package/src/value-parser/walk.js +18 -0
  202. package/stubs/.gitignore +1 -0
  203. package/stubs/.prettierrc.json +6 -0
  204. package/stubs/{defaultConfig.stub.js → config.full.js} +290 -178
  205. package/stubs/postcss.config.js +6 -0
  206. package/stubs/tailwind.config.cjs +2 -0
  207. package/stubs/tailwind.config.js +2 -0
  208. package/stubs/tailwind.config.ts +3 -0
  209. package/types/config.d.ts +376 -0
  210. package/types/generated/.gitkeep +0 -0
  211. package/types/generated/colors.d.ts +298 -0
  212. package/types/generated/corePluginList.d.ts +1 -0
  213. package/types/generated/default-theme.d.ts +397 -0
  214. package/types/index.d.ts +11 -0
  215. package/CHANGELOG.md +0 -1959
  216. package/lib/constants.js +0 -37
  217. package/lib/lib/detectNesting.js +0 -34
  218. package/nesting/plugin.js +0 -41
  219. package/scripts/install-integrations.js +0 -27
  220. package/scripts/rebuildFixtures.js +0 -68
  221. package/src/constants.js +0 -17
  222. package/src/lib/detectNesting.js +0 -39
  223. /package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -0
  224. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -3,18 +3,27 @@ import selectorParser from 'postcss-selector-parser'
3
3
  import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses } from '../util/pluginUtils'
6
+ import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
- import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
8
+ import * as sharedState from './sharedState'
9
+ import {
10
+ formatVariantSelector,
11
+ finalizeSelector,
12
+ eliminateIrrelevantSelectors,
13
+ } from '../util/formatVariantSelector'
9
14
  import { asClass } from '../util/nameClass'
10
15
  import { normalize } from '../util/dataTypes'
11
- import isValidArbitraryValue from '../util/isValidArbitraryValue'
16
+ import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
17
+ import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
18
+ import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
19
+ import { flagEnabled } from '../featureFlags'
20
+ import { applyImportantSelector } from '../util/applyImportantSelector'
12
21
 
13
22
  let classNameParser = selectorParser((selectors) => {
14
23
  return selectors.first.filter(({ type }) => type === 'class').pop().value
15
24
  })
16
25
 
17
- function getClassNameFromSelector(selector) {
26
+ export function getClassNameFromSelector(selector) {
18
27
  return classNameParser.transformSync(selector)
19
28
  }
20
29
 
@@ -25,33 +34,49 @@ function getClassNameFromSelector(selector) {
25
34
  // Example with dynamic classes:
26
35
  // ['grid-cols', '[[linename],1fr,auto]']
27
36
  // ['grid', 'cols-[[linename],1fr,auto]']
28
- function* candidatePermutations(candidate, lastIndex = Infinity) {
29
- if (lastIndex < 0) {
30
- return
31
- }
32
-
33
- let dashIdx
34
-
35
- if (lastIndex === Infinity && candidate.endsWith(']')) {
36
- let bracketIdx = candidate.indexOf('[')
37
+ function* candidatePermutations(candidate) {
38
+ let lastIndex = Infinity
39
+
40
+ while (lastIndex >= 0) {
41
+ let dashIdx
42
+ let wasSlash = false
43
+
44
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
45
+ let bracketIdx = candidate.indexOf('[')
46
+
47
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
48
+ // eg. string[]
49
+ if (candidate[bracketIdx - 1] === '-') {
50
+ dashIdx = bracketIdx - 1
51
+ } else if (candidate[bracketIdx - 1] === '/') {
52
+ dashIdx = bracketIdx - 1
53
+ wasSlash = true
54
+ } else {
55
+ dashIdx = -1
56
+ }
57
+ } else if (lastIndex === Infinity && candidate.includes('/')) {
58
+ dashIdx = candidate.lastIndexOf('/')
59
+ wasSlash = true
60
+ } else {
61
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
62
+ }
37
63
 
38
- // If character before `[` isn't a dash or a slash, this isn't a dynamic class
39
- // eg. string[]
40
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
41
- } else {
42
- dashIdx = candidate.lastIndexOf('-', lastIndex)
43
- }
64
+ if (dashIdx < 0) {
65
+ break
66
+ }
44
67
 
45
- if (dashIdx < 0) {
46
- return
47
- }
68
+ let prefix = candidate.slice(0, dashIdx)
69
+ let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
48
70
 
49
- let prefix = candidate.slice(0, dashIdx)
50
- let modifier = candidate.slice(dashIdx + 1)
71
+ lastIndex = dashIdx - 1
51
72
 
52
- yield [prefix, modifier]
73
+ // TODO: This feels a bit hacky
74
+ if (prefix === '' || modifier === '/') {
75
+ continue
76
+ }
53
77
 
54
- yield* candidatePermutations(candidate, dashIdx - 1)
78
+ yield [prefix, modifier]
79
+ }
55
80
  }
56
81
 
57
82
  function applyPrefix(matches, context) {
@@ -63,9 +88,23 @@ function applyPrefix(matches, context) {
63
88
  let [meta] = match
64
89
  if (meta.options.respectPrefix) {
65
90
  let container = postcss.root({ nodes: [match[1].clone()] })
91
+ let classCandidate = match[1].raws.tailwind.classCandidate
92
+
66
93
  container.walkRules((r) => {
67
- r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
94
+ // If this is a negative utility with a dash *before* the prefix we
95
+ // have to ensure that the generated selector matches the candidate
96
+
97
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
98
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
99
+ let shouldPrependNegative = classCandidate.startsWith('-')
100
+
101
+ r.selector = prefixSelector(
102
+ context.tailwindConfig.prefix,
103
+ r.selector,
104
+ shouldPrependNegative
105
+ )
68
106
  })
107
+
69
108
  match[1] = container.nodes[0]
70
109
  }
71
110
  }
@@ -73,20 +112,42 @@ function applyPrefix(matches, context) {
73
112
  return matches
74
113
  }
75
114
 
76
- function applyImportant(matches) {
115
+ function applyImportant(matches, classCandidate) {
77
116
  if (matches.length === 0) {
78
117
  return matches
79
118
  }
119
+
80
120
  let result = []
81
121
 
122
+ function isInKeyframes(rule) {
123
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
124
+ }
125
+
82
126
  for (let [meta, rule] of matches) {
83
127
  let container = postcss.root({ nodes: [rule.clone()] })
128
+
84
129
  container.walkRules((r) => {
85
- r.selector = updateAllClasses(r.selector, (className) => {
86
- return `!${className}`
87
- })
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
+
136
+ let ast = selectorParser().astSync(r.selector)
137
+
138
+ // Remove extraneous selectors that do not include the base candidate
139
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
140
+
141
+ // Update all instances of the base candidate to include the important marker
142
+ updateAllClasses(ast, (className) =>
143
+ className === classCandidate ? `!${className}` : className
144
+ )
145
+
146
+ r.selector = ast.toString()
147
+
88
148
  r.walkDecls((d) => (d.important = true))
89
149
  })
150
+
90
151
  result.push([{ ...meta, important: true }, container.nodes[0]])
91
152
  }
92
153
 
@@ -107,10 +168,88 @@ function applyVariant(variant, matches, context) {
107
168
  return matches
108
169
  }
109
170
 
171
+ /** @type {{modifier: string | null, value: string | null}} */
172
+ let args = { modifier: null, value: sharedState.NONE }
173
+
174
+ // Retrieve "modifier"
175
+ {
176
+ let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
177
+
178
+ // This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
179
+ // In this case 1/10 is a value but /20 is a modifier
180
+ if (modifiers.length > 1) {
181
+ baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
182
+ modifiers = modifiers.slice(-1)
183
+ }
184
+
185
+ if (modifiers.length && !context.variantMap.has(variant)) {
186
+ variant = baseVariant
187
+ args.modifier = modifiers[0]
188
+
189
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
190
+ return []
191
+ }
192
+ }
193
+ }
194
+
195
+ // Retrieve "arbitrary value"
196
+ if (variant.endsWith(']') && !variant.startsWith('[')) {
197
+ // We either have:
198
+ // @[200px]
199
+ // group-[:hover]
200
+ //
201
+ // But we don't want:
202
+ // @-[200px] (`-` is incorrect)
203
+ // group[:hover] (`-` is missing)
204
+ let match = /(.)(-?)\[(.*)\]/g.exec(variant)
205
+ if (match) {
206
+ let [, char, separator, value] = match
207
+ // @-[200px] case
208
+ if (char === '@' && separator === '-') return []
209
+ // group[:hover] case
210
+ if (char !== '@' && separator === '') return []
211
+
212
+ variant = variant.replace(`${separator}[${value}]`, '')
213
+ args.value = value
214
+ }
215
+ }
216
+
217
+ // Register arbitrary variants
218
+ if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
219
+ let sort = context.offsets.recordVariant(variant)
220
+
221
+ let selector = normalize(variant.slice(1, -1))
222
+ let selectors = splitAtTopLevelOnly(selector, ',')
223
+
224
+ // We do not support multiple selectors for arbitrary variants
225
+ if (selectors.length > 1) {
226
+ return []
227
+ }
228
+
229
+ if (!selectors.every(isValidVariantFormatString)) {
230
+ return []
231
+ }
232
+
233
+ let records = selectors.map((sel, idx) => [
234
+ context.offsets.applyParallelOffset(sort, idx),
235
+ parseVariant(sel.trim()),
236
+ ])
237
+
238
+ context.variantMap.set(variant, records)
239
+ }
240
+
110
241
  if (context.variantMap.has(variant)) {
111
- let variantFunctionTuples = context.variantMap.get(variant)
242
+ let isArbitraryVariant = isArbitraryValue(variant)
243
+ let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {}
244
+ let variantFunctionTuples = context.variantMap.get(variant).slice()
112
245
  let result = []
113
246
 
247
+ let respectPrefix = (() => {
248
+ if (isArbitraryVariant) return false
249
+ if (internalFeatures.respectPrefix === false) return false
250
+ return true
251
+ })()
252
+
114
253
  for (let [meta, rule] of matches) {
115
254
  // Don't generate variants for user css
116
255
  if (meta.layer === 'user') {
@@ -119,15 +258,17 @@ function applyVariant(variant, matches, context) {
119
258
 
120
259
  let container = postcss.root({ nodes: [rule.clone()] })
121
260
 
122
- for (let [variantSort, variantFunction] of variantFunctionTuples) {
123
- let clone = container.clone()
261
+ for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
262
+ let clone = (containerFromArray ?? container).clone()
124
263
  let collectedFormats = []
125
264
 
126
- let originals = new Map()
127
-
128
265
  function prepareBackup() {
129
- if (originals.size > 0) return // Already prepared, chicken out
130
- clone.walkRules((rule) => originals.set(rule, rule.selector))
266
+ // Already prepared, chicken out
267
+ if (clone.raws.neededBackup) {
268
+ return
269
+ }
270
+ clone.raws.neededBackup = true
271
+ clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
131
272
  }
132
273
 
133
274
  function modifySelectors(modifierFunction) {
@@ -167,25 +308,54 @@ function applyVariant(variant, matches, context) {
167
308
  clone.append(wrapper)
168
309
  },
169
310
  format(selectorFormat) {
170
- collectedFormats.push(selectorFormat)
311
+ collectedFormats.push({
312
+ format: selectorFormat,
313
+ respectPrefix,
314
+ })
171
315
  },
316
+ args,
172
317
  })
173
318
 
319
+ // It can happen that a list of format strings is returned from within the function. In that
320
+ // case, we have to process them as well. We can use the existing `variantSort`.
321
+ if (Array.isArray(ruleWithVariant)) {
322
+ for (let [idx, variantFunction] of ruleWithVariant.entries()) {
323
+ // This is a little bit scary since we are pushing to an array of items that we are
324
+ // currently looping over. However, you can also think of it like a processing queue
325
+ // where you keep handling jobs until everything is done and each job can queue more
326
+ // jobs if needed.
327
+ variantFunctionTuples.push([
328
+ context.offsets.applyParallelOffset(variantSort, idx),
329
+ variantFunction,
330
+
331
+ // If the clone has been modified we have to pass that back
332
+ // though so each rule can use the modified container
333
+ clone.clone(),
334
+ ])
335
+ }
336
+ continue
337
+ }
338
+
174
339
  if (typeof ruleWithVariant === 'string') {
175
- collectedFormats.push(ruleWithVariant)
340
+ collectedFormats.push({
341
+ format: ruleWithVariant,
342
+ respectPrefix,
343
+ })
176
344
  }
177
345
 
178
346
  if (ruleWithVariant === null) {
179
347
  continue
180
348
  }
181
349
 
182
- // We filled the `originals`, therefore we assume that somebody touched
350
+ // We had to backup selectors, therefore we assume that somebody touched
183
351
  // `container` or `modifySelectors`. Let's see if they did, so that we
184
352
  // can restore the selectors, and collect the format strings.
185
- if (originals.size > 0) {
353
+ if (clone.raws.neededBackup) {
354
+ delete clone.raws.neededBackup
186
355
  clone.walkRules((rule) => {
187
- if (!originals.has(rule)) return
188
- let before = originals.get(rule)
356
+ let before = rule.raws.originalSelector
357
+ if (!before) return
358
+ delete rule.raws.originalSelector
189
359
  if (before === rule.selector) return // No mutation happened
190
360
 
191
361
  let modified = rule.selector
@@ -211,15 +381,28 @@ function applyVariant(variant, matches, context) {
211
381
  // modified (by plugin): .foo .foo\\:markdown > p
212
382
  // rebuiltBase (internal): .foo\\:markdown > p
213
383
  // format: .foo &
214
- collectedFormats.push(modified.replace(rebuiltBase, '&'))
384
+ collectedFormats.push({
385
+ format: modified.replace(rebuiltBase, '&'),
386
+ respectPrefix,
387
+ })
215
388
  rule.selector = before
216
389
  })
217
390
  }
218
391
 
392
+ // This tracks the originating layer for the variant
393
+ // For example:
394
+ // .sm:underline {} is a variant of something in the utilities layer
395
+ // .sm:container {} is a variant of the container component
396
+ clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
397
+
219
398
  let withOffset = [
220
399
  {
221
400
  ...meta,
222
- sort: variantSort | meta.sort,
401
+ sort: context.offsets.applyVariantOffset(
402
+ meta.sort,
403
+ variantSort,
404
+ Object.assign(args, context.variantOptions.get(variant))
405
+ ),
223
406
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
224
407
  },
225
408
  clone.nodes[0],
@@ -259,7 +442,47 @@ function isValidPropName(name) {
259
442
  return IS_VALID_PROPERTY_NAME.test(name)
260
443
  }
261
444
 
445
+ /**
446
+ * @param {string} declaration
447
+ * @returns {boolean}
448
+ */
449
+ function looksLikeUri(declaration) {
450
+ // Quick bailout for obvious non-urls
451
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
452
+ if (!declaration.includes('://')) {
453
+ return false
454
+ }
455
+
456
+ try {
457
+ const url = new URL(declaration)
458
+ return url.scheme !== '' && url.host !== ''
459
+ } catch (err) {
460
+ // Definitely not a valid url
461
+ return false
462
+ }
463
+ }
464
+
465
+ function isParsableNode(node) {
466
+ let isParsable = true
467
+
468
+ node.walkDecls((decl) => {
469
+ if (!isParsableCssValue(decl.prop, decl.value)) {
470
+ isParsable = false
471
+ return false
472
+ }
473
+ })
474
+
475
+ return isParsable
476
+ }
477
+
262
478
  function isParsableCssValue(property, value) {
479
+ // We don't want to to treat [https://example.com] as a custom property
480
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
481
+ // So we short-circuit here by checking if the custom property looks like a url
482
+ if (looksLikeUri(`${property}:${value}`)) {
483
+ return false
484
+ }
485
+
263
486
  try {
264
487
  postcss.parse(`a{${property}:${value}}`).toResult()
265
488
  return true
@@ -283,15 +506,17 @@ function extractArbitraryProperty(classCandidate, context) {
283
506
  return null
284
507
  }
285
508
 
286
- let normalized = normalize(value)
509
+ let normalized = normalize(value, { property })
287
510
 
288
511
  if (!isParsableCssValue(property, normalized)) {
289
512
  return null
290
513
  }
291
514
 
515
+ let sort = context.offsets.arbitraryProperty(classCandidate)
516
+
292
517
  return [
293
518
  [
294
- { sort: context.arbitraryPropertiesSort, layer: 'utilities' },
519
+ { sort, layer: 'utilities' },
295
520
  () => ({
296
521
  [asClass(classCandidate)]: {
297
522
  [property]: normalized,
@@ -318,7 +543,11 @@ function* resolveMatchedPlugins(classCandidate, context) {
318
543
  const twConfigPrefix = context.tailwindConfig.prefix
319
544
 
320
545
  const twConfigPrefixLen = twConfigPrefix.length
321
- if (candidatePrefix[twConfigPrefixLen] === '-') {
546
+
547
+ const hasMatchingPrefix =
548
+ candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`)
549
+
550
+ if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) {
322
551
  negative = true
323
552
  candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
324
553
  }
@@ -335,7 +564,23 @@ function* resolveMatchedPlugins(classCandidate, context) {
335
564
  }
336
565
 
337
566
  function splitWithSeparator(input, separator) {
338
- return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
567
+ if (input === sharedState.NOT_ON_DEMAND) {
568
+ return [sharedState.NOT_ON_DEMAND]
569
+ }
570
+
571
+ return splitAtTopLevelOnly(input, separator)
572
+ }
573
+
574
+ function* recordCandidates(matches, classCandidate) {
575
+ for (const match of matches) {
576
+ match[1].raws.tailwind = {
577
+ ...match[1].raws.tailwind,
578
+ classCandidate,
579
+ preserveSource: match[0].options?.preserveSource ?? false,
580
+ }
581
+
582
+ yield match
583
+ }
339
584
  }
340
585
 
341
586
  function* resolveMatches(candidate, context) {
@@ -388,71 +633,144 @@ function* resolveMatches(candidate, context) {
388
633
  }
389
634
 
390
635
  if (matchesPerPlugin.length > 0) {
391
- typesByMatches.set(matchesPerPlugin, sort.options?.type)
636
+ let matchingTypes = Array.from(
637
+ getMatchingTypes(
638
+ sort.options?.types ?? [],
639
+ modifier,
640
+ sort.options ?? {},
641
+ context.tailwindConfig
642
+ )
643
+ ).map(([_, type]) => type)
644
+
645
+ if (matchingTypes.length > 0) {
646
+ typesByMatches.set(matchesPerPlugin, matchingTypes)
647
+ }
648
+
392
649
  matches.push(matchesPerPlugin)
393
650
  }
394
651
  }
395
652
 
396
- // Only keep the result of the very first plugin if we are dealing with
397
- // arbitrary values, to protect against ambiguity.
398
- if (isArbitraryValue(modifier) && matches.length > 1) {
399
- let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
400
-
401
- // Remove duplicates, so that we can detect proper unique types for each plugin.
402
- for (let pluginTypes of typesPerPlugin) {
403
- for (let type of pluginTypes) {
404
- let removeFromOwnGroup = false
405
-
406
- for (let otherGroup of typesPerPlugin) {
407
- if (pluginTypes === otherGroup) continue
653
+ if (isArbitraryValue(modifier)) {
654
+ if (matches.length > 1) {
655
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
656
+ // don't have `any` as a type first.
657
+ let [withAny, withoutAny] = matches.reduce(
658
+ (group, plugin) => {
659
+ let hasAnyType = plugin.some(([{ options }]) =>
660
+ options.types.some(({ type }) => type === 'any')
661
+ )
408
662
 
409
- if (otherGroup.has(type)) {
410
- otherGroup.delete(type)
411
- removeFromOwnGroup = true
663
+ if (hasAnyType) {
664
+ group[0].push(plugin)
665
+ } else {
666
+ group[1].push(plugin)
412
667
  }
668
+ return group
669
+ },
670
+ [[], []]
671
+ )
672
+
673
+ function findFallback(matches) {
674
+ // If only a single plugin matches, let's take that one
675
+ if (matches.length === 1) {
676
+ return matches[0]
413
677
  }
414
678
 
415
- if (removeFromOwnGroup) pluginTypes.delete(type)
679
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
680
+ // also has the correct type which preferOnConflicts the plugin in case of clashes.
681
+ return matches.find((rules) => {
682
+ let matchingTypes = typesByMatches.get(rules)
683
+ return rules.some(([{ options }, rule]) => {
684
+ if (!isParsableNode(rule)) {
685
+ return false
686
+ }
687
+
688
+ return options.types.some(
689
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
690
+ )
691
+ })
692
+ })
416
693
  }
417
- }
418
694
 
419
- let messages = []
420
-
421
- for (let [idx, group] of typesPerPlugin.entries()) {
422
- for (let type of group) {
423
- let rules = matches[idx]
424
- .map(([, rule]) => rule)
425
- .flat()
426
- .map((rule) =>
427
- rule
428
- .toString()
429
- .split('\n')
430
- .slice(1, -1) // Remove selector and closing '}'
431
- .map((line) => line.trim())
432
- .map((x) => ` ${x}`) // Re-indent
433
- .join('\n')
434
- )
435
- .join('\n\n')
695
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
696
+ // the given arbitrary value.
697
+ let fallback = findFallback(withoutAny) ?? findFallback(withAny)
698
+ if (fallback) {
699
+ matches = [fallback]
700
+ }
701
+
702
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
703
+ // generated css for the current candidate. This means that the result is ambiguous and this
704
+ // should not happen. We won't generate anything right now, so let's report this to the user
705
+ // by logging some options about what they can do.
706
+ else {
707
+ let typesPerPlugin = matches.map(
708
+ (match) => new Set([...(typesByMatches.get(match) ?? [])])
709
+ )
710
+
711
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
712
+ for (let pluginTypes of typesPerPlugin) {
713
+ for (let type of pluginTypes) {
714
+ let removeFromOwnGroup = false
715
+
716
+ for (let otherGroup of typesPerPlugin) {
717
+ if (pluginTypes === otherGroup) continue
718
+
719
+ if (otherGroup.has(type)) {
720
+ otherGroup.delete(type)
721
+ removeFromOwnGroup = true
722
+ }
723
+ }
724
+
725
+ if (removeFromOwnGroup) pluginTypes.delete(type)
726
+ }
727
+ }
436
728
 
437
- messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
438
- break
729
+ let messages = []
730
+
731
+ for (let [idx, group] of typesPerPlugin.entries()) {
732
+ for (let type of group) {
733
+ let rules = matches[idx]
734
+ .map(([, rule]) => rule)
735
+ .flat()
736
+ .map((rule) =>
737
+ rule
738
+ .toString()
739
+ .split('\n')
740
+ .slice(1, -1) // Remove selector and closing '}'
741
+ .map((line) => line.trim())
742
+ .map((x) => ` ${x}`) // Re-indent
743
+ .join('\n')
744
+ )
745
+ .join('\n\n')
746
+
747
+ messages.push(
748
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
749
+ )
750
+ break
751
+ }
752
+ }
753
+
754
+ log.warn([
755
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
756
+ ...messages,
757
+ `If this is content and not a class, replace it with \`${candidate
758
+ .replace('[', '&lsqb;')
759
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
760
+ ])
761
+ continue
439
762
  }
440
763
  }
441
764
 
442
- log.warn([
443
- `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
444
- ...messages,
445
- `If this is content and not a class, replace it with \`${candidate
446
- .replace('[', '&lsqb;')
447
- .replace(']', '&rsqb;')}\` to silence this warning.`,
448
- ])
449
- continue
765
+ matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
450
766
  }
451
767
 
452
- matches = applyPrefix(matches.flat(), context)
768
+ matches = matches.flat()
769
+ matches = Array.from(recordCandidates(matches, classCandidate))
770
+ matches = applyPrefix(matches, context)
453
771
 
454
772
  if (important) {
455
- matches = applyImportant(matches, context)
773
+ matches = applyImportant(matches, classCandidate)
456
774
  }
457
775
 
458
776
  for (let variant of variants) {
@@ -460,20 +778,16 @@ function* resolveMatches(candidate, context) {
460
778
  }
461
779
 
462
780
  for (let match of matches) {
781
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
782
+
463
783
  // Apply final format selector
464
- if (match[0].collectedFormats) {
465
- let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
466
- let container = postcss.root({ nodes: [match[1].clone()] })
467
- container.walkRules((rule) => {
468
- if (inKeyframes(rule)) return
469
-
470
- rule.selector = finalizeSelector(finalFormat, {
471
- selector: rule.selector,
472
- candidate,
473
- context,
474
- })
475
- })
476
- match[1] = container.nodes[0]
784
+ match = applyFinalFormat(match, { context, candidate })
785
+
786
+ // Skip rules with invalid selectors
787
+ // This will cause the candidate to be added to the "not class"
788
+ // cache skipping it entirely for future builds
789
+ if (match === null) {
790
+ continue
477
791
  }
478
792
 
479
793
  yield match
@@ -481,20 +795,119 @@ function* resolveMatches(candidate, context) {
481
795
  }
482
796
  }
483
797
 
798
+ function applyFinalFormat(match, { context, candidate }) {
799
+ if (!match[0].collectedFormats) {
800
+ return match
801
+ }
802
+
803
+ let isValid = true
804
+ let finalFormat
805
+
806
+ try {
807
+ finalFormat = formatVariantSelector(match[0].collectedFormats, {
808
+ context,
809
+ candidate,
810
+ })
811
+ } catch {
812
+ // The format selector we produced is invalid
813
+ // This could be because:
814
+ // - A bug exists
815
+ // - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
816
+ // - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
817
+ // Either way the build will fail because of this
818
+ // We would rather that the build pass "silently" given that this could
819
+ // happen because of picking up invalid things when scanning content
820
+ // So we'll throw out the candidate instead
821
+
822
+ return null
823
+ }
824
+
825
+ let container = postcss.root({ nodes: [match[1].clone()] })
826
+
827
+ container.walkRules((rule) => {
828
+ if (inKeyframes(rule)) {
829
+ return
830
+ }
831
+
832
+ try {
833
+ let selector = finalizeSelector(rule.selector, finalFormat, {
834
+ candidate,
835
+ context,
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
846
+ } catch {
847
+ // If this selector is invalid we also want to skip it
848
+ // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
849
+ isValid = false
850
+ return false
851
+ }
852
+ })
853
+
854
+ if (!isValid) {
855
+ return null
856
+ }
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
+
863
+ match[1] = container.nodes[0]
864
+
865
+ return match
866
+ }
867
+
484
868
  function inKeyframes(rule) {
485
869
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
486
870
  }
487
871
 
488
- function generateRules(candidates, context) {
872
+ function getImportantStrategy(important) {
873
+ if (important === true) {
874
+ return (rule) => {
875
+ if (inKeyframes(rule)) {
876
+ return
877
+ }
878
+
879
+ rule.walkDecls((d) => {
880
+ if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
881
+ d.important = true
882
+ }
883
+ })
884
+ }
885
+ }
886
+
887
+ if (typeof important === 'string') {
888
+ return (rule) => {
889
+ if (inKeyframes(rule)) {
890
+ return
891
+ }
892
+
893
+ rule.selectors = rule.selectors.map((selector) => {
894
+ return applyImportantSelector(selector, important)
895
+ })
896
+ }
897
+ }
898
+ }
899
+
900
+ function generateRules(candidates, context, isSorting = false) {
489
901
  let allRules = []
902
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
490
903
 
491
904
  for (let candidate of candidates) {
492
905
  if (context.notClassCache.has(candidate)) {
493
906
  continue
494
907
  }
495
908
 
496
- if (context.classCache.has(candidate)) {
497
- allRules.push(context.classCache.get(candidate))
909
+ if (context.candidateRuleCache.has(candidate)) {
910
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
498
911
  continue
499
912
  }
500
913
 
@@ -506,47 +919,29 @@ function generateRules(candidates, context) {
506
919
  }
507
920
 
508
921
  context.classCache.set(candidate, matches)
509
- allRules.push(matches)
510
- }
511
922
 
512
- // Strategy based on `tailwindConfig.important`
513
- let strategy = ((important) => {
514
- if (important === true) {
515
- return (rule) => {
516
- rule.walkDecls((d) => {
517
- if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
518
- d.important = true
519
- }
520
- })
521
- }
522
- }
923
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
924
+ context.candidateRuleCache.set(candidate, rules)
523
925
 
524
- if (typeof important === 'string') {
525
- return (rule) => {
526
- rule.selectors = rule.selectors.map((selector) => {
527
- return `${important} ${selector}`
528
- })
529
- }
530
- }
531
- })(context.tailwindConfig.important)
926
+ for (const match of matches) {
927
+ let [{ sort, options }, rule] = match
532
928
 
533
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
534
- if (options.respectImportant) {
535
- if (strategy) {
929
+ if (options.respectImportant && strategy) {
536
930
  let container = postcss.root({ nodes: [rule.clone()] })
537
- container.walkRules((r) => {
538
- if (inKeyframes(r)) {
539
- return
540
- }
541
-
542
- strategy(r)
543
- })
931
+ container.walkRules(strategy)
544
932
  rule = container.nodes[0]
545
933
  }
934
+
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]
938
+ rules.add(newEntry)
939
+ context.ruleCache.add(newEntry)
940
+ allRules.push(newEntry)
546
941
  }
942
+ }
547
943
 
548
- return [sort | context.layerOrder[layer], rule]
549
- })
944
+ return allRules
550
945
  }
551
946
 
552
947
  function isArbitraryValue(input) {