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
@@ -11,42 +11,95 @@ import isPlainObject from '../util/isPlainObject'
11
11
  import escapeClassName from '../util/escapeClassName'
12
12
  import nameClass, { formatClass } from '../util/nameClass'
13
13
  import { coerceValue } from '../util/pluginUtils'
14
- import bigSign from '../util/bigSign'
15
14
  import { variantPlugins, corePlugins } from '../corePlugins'
16
15
  import * as sharedState from './sharedState'
17
16
  import { env } from './sharedState'
18
17
  import { toPath } from '../util/toPath'
19
18
  import log from '../util/log'
20
19
  import negateValue from '../util/negateValue'
21
- import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
-
23
- function parseVariantFormatString(input) {
24
- if (input.includes('{')) {
25
- if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
20
+ import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPropertyValue'
21
+ import { generateRules, getClassNameFromSelector } from './generateRules'
22
+ import { hasContentChanged } from './cacheInvalidation.js'
23
+ import { Offsets } from './offsets.js'
24
+ import { flagEnabled } from '../featureFlags.js'
25
+ import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
26
+
27
+ export const INTERNAL_FEATURES = Symbol()
28
+
29
+ const VARIANT_TYPES = {
30
+ AddVariant: Symbol.for('ADD_VARIANT'),
31
+ MatchVariant: Symbol.for('MATCH_VARIANT'),
32
+ }
26
33
 
27
- return input
28
- .split(/{(.*)}/gim)
29
- .flatMap((line) => parseVariantFormatString(line))
30
- .filter(Boolean)
31
- }
34
+ const VARIANT_INFO = {
35
+ Base: 1 << 0,
36
+ Dynamic: 1 << 1,
37
+ }
32
38
 
33
- return [input.trim()]
39
+ function prefix(context, selector) {
40
+ let prefix = context.tailwindConfig.prefix
41
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
34
42
  }
35
43
 
36
- function isBalanced(input) {
37
- let count = 0
44
+ function normalizeOptionTypes({ type = 'any', ...options }) {
45
+ let types = [].concat(type)
46
+
47
+ return {
48
+ ...options,
49
+ types: types.map((type) => {
50
+ if (Array.isArray(type)) {
51
+ return { type: type[0], ...type[1] }
52
+ }
53
+ return { type, preferOnConflict: false }
54
+ }),
55
+ }
56
+ }
38
57
 
39
- for (let char of input) {
40
- if (char === '{') {
41
- count++
58
+ function parseVariantFormatString(input) {
59
+ /** @type {string[]} */
60
+ let parts = []
61
+
62
+ // When parsing whitespace around special characters are insignificant
63
+ // However, _inside_ of a variant they could be
64
+ // Because the selector could look like this
65
+ // @media { &[data-name="foo bar"] }
66
+ // This is why we do not skip whitespace
67
+
68
+ let current = ''
69
+ let depth = 0
70
+
71
+ for (let idx = 0; idx < input.length; idx++) {
72
+ let char = input[idx]
73
+
74
+ if (char === '\\') {
75
+ // Escaped characters are not special
76
+ current += '\\' + input[++idx]
77
+ } else if (char === '{') {
78
+ // Nested rule: start
79
+ ++depth
80
+ parts.push(current.trim())
81
+ current = ''
42
82
  } else if (char === '}') {
43
- if (--count < 0) {
44
- return false // unbalanced
83
+ // Nested rule: end
84
+ if (--depth < 0) {
85
+ throw new Error(`Your { and } are unbalanced.`)
45
86
  }
87
+
88
+ parts.push(current.trim())
89
+ current = ''
90
+ } else {
91
+ // Normal character
92
+ current += char
46
93
  }
47
94
  }
48
95
 
49
- return count === 0
96
+ if (current.length > 0) {
97
+ parts.push(current.trim())
98
+ }
99
+
100
+ parts = parts.filter((part) => part !== '')
101
+
102
+ return parts
50
103
  }
51
104
 
52
105
  function insertInto(list, value, { before = [] } = {}) {
@@ -78,35 +131,66 @@ function parseStyles(styles) {
78
131
  })
79
132
  }
80
133
 
81
- function getClasses(selector) {
134
+ function getClasses(selector, mutate) {
82
135
  let parser = selectorParser((selectors) => {
83
136
  let allClasses = []
137
+
138
+ if (mutate) {
139
+ mutate(selectors)
140
+ }
141
+
84
142
  selectors.walkClasses((classNode) => {
85
143
  allClasses.push(classNode.value)
86
144
  })
145
+
87
146
  return allClasses
88
147
  })
89
148
  return parser.transformSync(selector)
90
149
  }
91
150
 
92
- function extractCandidates(node) {
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
+
167
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
93
168
  let classes = []
169
+ let selectors = []
94
170
 
95
171
  if (node.type === 'rule') {
96
- for (let selector of node.selectors) {
97
- let classCandidates = getClasses(selector)
98
- // At least one of the selectors contains non-"on-demandable" candidates.
99
- if (classCandidates.length === 0) return []
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
+ }
178
+
179
+ for (let selector of selectors) {
180
+ let classCandidates = getClasses(selector, ignoreNot)
100
181
 
101
- classes = [...classes, ...classCandidates]
182
+ // At least one of the selectors contains non-"on-demandable" candidates.
183
+ if (classCandidates.length === 0) {
184
+ state.containsNonOnDemandable = true
185
+ }
186
+
187
+ for (let classCandidate of classCandidates) {
188
+ classes.push(classCandidate)
102
189
  }
103
- return classes
104
190
  }
105
191
 
106
- if (node.type === 'atrule') {
107
- node.walkRules((rule) => {
108
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
109
- })
192
+ if (depth === 0) {
193
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
110
194
  }
111
195
 
112
196
  return classes
@@ -115,13 +199,16 @@ function extractCandidates(node) {
115
199
  function withIdentifiers(styles) {
116
200
  return parseStyles(styles).flatMap((node) => {
117
201
  let nodeMap = new Map()
118
- let candidates = extractCandidates(node)
202
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
119
203
 
120
- // If this isn't "on-demandable", assign it a universal candidate.
121
- if (candidates.length === 0) {
122
- return [['*', node]]
204
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
205
+ if (containsNonOnDemandableSelectors) {
206
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
123
207
  }
124
208
 
209
+ // However, it could be that it also contains "on-demandable" candidates.
210
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
211
+ // `@apply foo` for example.
125
212
  return candidates.map((c) => {
126
213
  if (!nodeMap.has(node)) {
127
214
  nodeMap.set(node, node)
@@ -131,6 +218,41 @@ function withIdentifiers(styles) {
131
218
  })
132
219
  }
133
220
 
221
+ export function isValidVariantFormatString(format) {
222
+ return format.startsWith('@') || format.includes('&')
223
+ }
224
+
225
+ export function parseVariant(variant) {
226
+ variant = variant
227
+ .replace(/\n+/g, '')
228
+ .replace(/\s{1,}/g, ' ')
229
+ .trim()
230
+
231
+ let fns = parseVariantFormatString(variant)
232
+ .map((str) => {
233
+ if (!str.startsWith('@')) {
234
+ return ({ format }) => format(str)
235
+ }
236
+
237
+ let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str)
238
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params?.trim() ?? '' }))
239
+ })
240
+ .reverse()
241
+
242
+ return (api) => {
243
+ for (let fn of fns) {
244
+ fn(api)
245
+ }
246
+ }
247
+ }
248
+
249
+ /**
250
+ *
251
+ * @param {any} tailwindConfig
252
+ * @param {any} context
253
+ * @param {object} param2
254
+ * @param {Offsets} param2.offsets
255
+ */
134
256
  function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
135
257
  function getConfigValue(path, defaultValue) {
136
258
  return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
@@ -141,8 +263,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
141
263
  }
142
264
 
143
265
  function prefixIdentifier(identifier, options) {
144
- if (identifier === '*') {
145
- return '*'
266
+ if (identifier === sharedState.NOT_ON_DEMAND) {
267
+ return sharedState.NOT_ON_DEMAND
146
268
  }
147
269
 
148
270
  if (!options.respectPrefix) {
@@ -152,51 +274,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
152
274
  return context.tailwindConfig.prefix + identifier
153
275
  }
154
276
 
155
- return {
156
- addVariant(variantName, variantFunctions, options = {}) {
157
- variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
158
- if (typeof variantFunction !== 'string') {
159
- // Safelist public API functions
160
- return ({ modifySelectors, container, separator }) => {
161
- return variantFunction({ modifySelectors, container, separator })
162
- }
163
- }
164
-
165
- variantFunction = variantFunction
166
- .replace(/\n+/g, '')
167
- .replace(/\s{1,}/g, ' ')
168
- .trim()
169
-
170
- let fns = parseVariantFormatString(variantFunction)
171
- .map((str) => {
172
- if (!str.startsWith('@')) {
173
- return ({ format }) => format(str)
174
- }
175
-
176
- let [, name, params] = /@(.*?) (.*)/g.exec(str)
177
- return ({ wrap }) => wrap(postcss.atRule({ name, params }))
178
- })
179
- .reverse()
180
-
181
- return (api) => {
182
- for (let fn of fns) {
183
- fn(api)
184
- }
185
- }
186
- })
277
+ function resolveThemeValue(path, defaultValue, opts = {}) {
278
+ let parts = toPath(path)
279
+ let value = getConfigValue(['theme', ...parts], defaultValue)
280
+ return transformThemeValue(parts[0])(value, opts)
281
+ }
187
282
 
188
- insertInto(variantList, variantName, options)
189
- variantMap.set(variantName, variantFunctions)
190
- },
283
+ let variantIdentifier = 0
284
+ let api = {
191
285
  postcss,
192
286
  prefix: applyConfiguredPrefix,
193
287
  e: escapeClassName,
194
288
  config: getConfigValue,
195
- theme(path, defaultValue) {
196
- const [pathRoot, ...subPaths] = toPath(path)
197
- const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
198
- return transformThemeValue(pathRoot)(value)
199
- },
289
+ theme: resolveThemeValue,
200
290
  corePlugins: (path) => {
201
291
  if (Array.isArray(tailwindConfig.corePlugins)) {
202
292
  return tailwindConfig.corePlugins.includes(path)
@@ -208,21 +298,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
208
298
  // Preserved for backwards compatibility but not used in v3.0+
209
299
  return []
210
300
  },
211
- addUserCss(userCss) {
212
- for (let [identifier, rule] of withIdentifiers(userCss)) {
213
- let offset = offsets.user++
214
-
215
- if (!context.candidateRuleMap.has(identifier)) {
216
- context.candidateRuleMap.set(identifier, [])
217
- }
218
-
219
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
220
- }
221
- },
222
301
  addBase(base) {
223
302
  for (let [identifier, rule] of withIdentifiers(base)) {
224
303
  let prefixedIdentifier = prefixIdentifier(identifier, {})
225
- let offset = offsets.base++
304
+ let offset = offsets.create('base')
226
305
 
227
306
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
228
307
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -244,7 +323,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
244
323
 
245
324
  for (let [identifier, rule] of withIdentifiers(groups)) {
246
325
  let prefixedIdentifier = prefixIdentifier(identifier, {})
247
- let offset = offsets.base++
248
326
 
249
327
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
250
328
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -252,11 +330,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
252
330
 
253
331
  context.candidateRuleMap
254
332
  .get(prefixedIdentifier)
255
- .push([{ sort: offset, layer: 'defaults' }, rule])
333
+ .push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule])
256
334
  }
257
335
  },
258
336
  addComponents(components, options) {
259
337
  let defaultOptions = {
338
+ preserveSource: false,
260
339
  respectPrefix: true,
261
340
  respectImportant: false,
262
341
  }
@@ -265,7 +344,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
265
344
 
266
345
  for (let [identifier, rule] of withIdentifiers(components)) {
267
346
  let prefixedIdentifier = prefixIdentifier(identifier, options)
268
- let offset = offsets.components++
269
347
 
270
348
  classList.add(prefixedIdentifier)
271
349
 
@@ -275,11 +353,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
275
353
 
276
354
  context.candidateRuleMap
277
355
  .get(prefixedIdentifier)
278
- .push([{ sort: offset, layer: 'components', options }, rule])
356
+ .push([{ sort: offsets.create('components'), layer: 'components', options }, rule])
279
357
  }
280
358
  },
281
359
  addUtilities(utilities, options) {
282
360
  let defaultOptions = {
361
+ preserveSource: false,
283
362
  respectPrefix: true,
284
363
  respectImportant: true,
285
364
  }
@@ -288,7 +367,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
288
367
 
289
368
  for (let [identifier, rule] of withIdentifiers(utilities)) {
290
369
  let prefixedIdentifier = prefixIdentifier(identifier, options)
291
- let offset = offsets.utilities++
292
370
 
293
371
  classList.add(prefixedIdentifier)
294
372
 
@@ -298,18 +376,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
298
376
 
299
377
  context.candidateRuleMap
300
378
  .get(prefixedIdentifier)
301
- .push([{ sort: offset, layer: 'utilities', options }, rule])
379
+ .push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule])
302
380
  }
303
381
  },
304
382
  matchUtilities: function (utilities, options) {
305
383
  let defaultOptions = {
306
384
  respectPrefix: true,
307
385
  respectImportant: true,
386
+ modifiers: false,
308
387
  }
309
388
 
310
- options = { ...defaultOptions, ...options }
389
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
311
390
 
312
- let offset = offsets.utilities++
391
+ let offset = offsets.create('utilities')
313
392
 
314
393
  for (let identifier in utilities) {
315
394
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -318,24 +397,51 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
318
397
  classList.add([prefixedIdentifier, options])
319
398
 
320
399
  function wrapped(modifier, { isOnlyPlugin }) {
321
- let { type = 'any' } = options
322
- type = [].concat(type)
323
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
400
+ let [value, coercedType, utilityModifier] = coerceValue(
401
+ options.types,
402
+ modifier,
403
+ options,
404
+ tailwindConfig
405
+ )
324
406
 
325
407
  if (value === undefined) {
326
408
  return []
327
409
  }
328
410
 
329
- if (!type.includes(coercedType) && !isOnlyPlugin) {
330
- return []
411
+ if (!options.types.some(({ type }) => type === coercedType)) {
412
+ if (isOnlyPlugin) {
413
+ log.warn([
414
+ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
415
+ `You can safely update it to \`${identifier}-${modifier.replace(
416
+ coercedType + ':',
417
+ ''
418
+ )}\`.`,
419
+ ])
420
+ } else {
421
+ return []
422
+ }
331
423
  }
332
424
 
333
- if (!isValidArbitraryValue(value)) {
425
+ if (!isSyntacticallyValidPropertyValue(value)) {
334
426
  return []
335
427
  }
336
428
 
429
+ let extras = {
430
+ get modifier() {
431
+ if (!options.modifiers) {
432
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
433
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
434
+ ])
435
+ }
436
+
437
+ return utilityModifier
438
+ },
439
+ }
440
+
441
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
442
+
337
443
  let ruleSets = []
338
- .concat(rule(value))
444
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
339
445
  .filter(Boolean)
340
446
  .map((declaration) => ({
341
447
  [nameClass(identifier, modifier)]: declaration,
@@ -357,11 +463,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
357
463
  let defaultOptions = {
358
464
  respectPrefix: true,
359
465
  respectImportant: false,
466
+ modifiers: false,
360
467
  }
361
468
 
362
- options = { ...defaultOptions, ...options }
469
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
363
470
 
364
- let offset = offsets.components++
471
+ let offset = offsets.create('components')
365
472
 
366
473
  for (let identifier in components) {
367
474
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -370,15 +477,18 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
370
477
  classList.add([prefixedIdentifier, options])
371
478
 
372
479
  function wrapped(modifier, { isOnlyPlugin }) {
373
- let { type = 'any' } = options
374
- type = [].concat(type)
375
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
480
+ let [value, coercedType, utilityModifier] = coerceValue(
481
+ options.types,
482
+ modifier,
483
+ options,
484
+ tailwindConfig
485
+ )
376
486
 
377
487
  if (value === undefined) {
378
488
  return []
379
489
  }
380
490
 
381
- if (!type.includes(coercedType)) {
491
+ if (!options.types.some(({ type }) => type === coercedType)) {
382
492
  if (isOnlyPlugin) {
383
493
  log.warn([
384
494
  `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
@@ -392,12 +502,26 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
392
502
  }
393
503
  }
394
504
 
395
- if (!isValidArbitraryValue(value)) {
505
+ if (!isSyntacticallyValidPropertyValue(value)) {
396
506
  return []
397
507
  }
398
508
 
509
+ let extras = {
510
+ get modifier() {
511
+ if (!options.modifiers) {
512
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
513
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
514
+ ])
515
+ }
516
+
517
+ return utilityModifier
518
+ },
519
+ }
520
+
521
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
522
+
399
523
  let ruleSets = []
400
- .concat(rule(value))
524
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
401
525
  .filter(Boolean)
402
526
  .map((declaration) => ({
403
527
  [nameClass(identifier, modifier)]: declaration,
@@ -415,7 +539,109 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
415
539
  context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
416
540
  }
417
541
  },
542
+ addVariant(variantName, variantFunctions, options = {}) {
543
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
544
+ if (typeof variantFunction !== 'string') {
545
+ // Safelist public API functions
546
+ return (api = {}) => {
547
+ let { args, modifySelectors, container, separator, wrap, format } = api
548
+ let result = variantFunction(
549
+ Object.assign(
550
+ { modifySelectors, container, separator },
551
+ options.type === VARIANT_TYPES.MatchVariant && { args, wrap, format }
552
+ )
553
+ )
554
+
555
+ if (typeof result === 'string' && !isValidVariantFormatString(result)) {
556
+ throw new Error(
557
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
558
+ )
559
+ }
560
+
561
+ if (Array.isArray(result)) {
562
+ return result
563
+ .filter((variant) => typeof variant === 'string')
564
+ .map((variant) => parseVariant(variant))
565
+ }
566
+
567
+ // result may be undefined with legacy variants that use APIs like `modifySelectors`
568
+ // result may also be a postcss node if someone was returning the result from `modifySelectors`
569
+ return result && typeof result === 'string' && parseVariant(result)(api)
570
+ }
571
+ }
572
+
573
+ if (!isValidVariantFormatString(variantFunction)) {
574
+ throw new Error(
575
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
576
+ )
577
+ }
578
+
579
+ return parseVariant(variantFunction)
580
+ })
581
+
582
+ insertInto(variantList, variantName, options)
583
+ variantMap.set(variantName, variantFunctions)
584
+ context.variantOptions.set(variantName, options)
585
+ },
586
+ matchVariant(variant, variantFn, options) {
587
+ // A unique identifier that "groups" these variants together.
588
+ // This is for internal use only which is why it is not present in the types
589
+ let id = options?.id ?? ++variantIdentifier
590
+ let isSpecial = variant === '@'
591
+
592
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
593
+
594
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
595
+ if (key === 'DEFAULT') continue
596
+
597
+ api.addVariant(
598
+ isSpecial ? `${variant}${key}` : `${variant}-${key}`,
599
+ ({ args, container }) => {
600
+ return variantFn(
601
+ value,
602
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
603
+ )
604
+ },
605
+
606
+ {
607
+ ...options,
608
+ value,
609
+ id,
610
+ type: VARIANT_TYPES.MatchVariant,
611
+ variantInfo: VARIANT_INFO.Base,
612
+ }
613
+ )
614
+ }
615
+
616
+ let hasDefault = 'DEFAULT' in (options?.values ?? {})
617
+
618
+ api.addVariant(
619
+ variant,
620
+ ({ args, container }) => {
621
+ if (args?.value === sharedState.NONE && !hasDefault) {
622
+ return null
623
+ }
624
+
625
+ return variantFn(
626
+ args?.value === sharedState.NONE
627
+ ? options.values.DEFAULT
628
+ : // Falling back to args if it is a string, otherwise '' for older intellisense
629
+ // (JetBrains) plugins.
630
+ args?.value ?? (typeof args === 'string' ? args : ''),
631
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
632
+ )
633
+ },
634
+ {
635
+ ...options,
636
+ id,
637
+ type: VARIANT_TYPES.MatchVariant,
638
+ variantInfo: VARIANT_INFO.Dynamic,
639
+ }
640
+ )
641
+ },
418
642
  }
643
+
644
+ return api
419
645
  }
420
646
 
421
647
  let fileModifiedMapCache = new WeakMap()
@@ -428,6 +654,7 @@ export function getFileModifiedMap(context) {
428
654
 
429
655
  function trackModified(files, fileModifiedMap) {
430
656
  let changed = false
657
+ let mtimesToCommit = new Map()
431
658
 
432
659
  for (let file of files) {
433
660
  if (!file) continue
@@ -448,10 +675,10 @@ function trackModified(files, fileModifiedMap) {
448
675
  changed = true
449
676
  }
450
677
 
451
- fileModifiedMap.set(file, newModified)
678
+ mtimesToCommit.set(file, newModified)
452
679
  }
453
680
 
454
- return changed
681
+ return [changed, mtimesToCommit]
455
682
  }
456
683
 
457
684
  function extractVariantAtRules(node) {
@@ -488,29 +715,20 @@ function collectLayerPlugins(root) {
488
715
  } else if (layerRule.params === 'components') {
489
716
  for (let node of layerRule.nodes) {
490
717
  layerPlugins.push(function ({ addComponents }) {
491
- addComponents(node, { respectPrefix: false })
718
+ addComponents(node, { respectPrefix: false, preserveSource: true })
492
719
  })
493
720
  }
494
721
  layerRule.remove()
495
722
  } else if (layerRule.params === 'utilities') {
496
723
  for (let node of layerRule.nodes) {
497
724
  layerPlugins.push(function ({ addUtilities }) {
498
- addUtilities(node, { respectPrefix: false })
725
+ addUtilities(node, { respectPrefix: false, preserveSource: true })
499
726
  })
500
727
  }
501
728
  layerRule.remove()
502
729
  }
503
730
  })
504
731
 
505
- root.walkRules((rule) => {
506
- // At this point it is safe to include all the left-over css from the
507
- // user's css file. This is because the `@tailwind` and `@layer` directives
508
- // will already be handled and will be removed from the css tree.
509
- layerPlugins.push(function ({ addUserCss }) {
510
- addUserCss(rule, { respectPrefix: false })
511
- })
512
- })
513
-
514
732
  return layerPlugins
515
733
  }
516
734
 
@@ -538,31 +756,56 @@ function resolvePlugins(context, root) {
538
756
  // TODO: This is a workaround for backwards compatibility, since custom variants
539
757
  // were historically sorted before screen/stackable variants.
540
758
  let beforeVariants = [
759
+ variantPlugins['childVariant'],
541
760
  variantPlugins['pseudoElementVariants'],
542
761
  variantPlugins['pseudoClassVariants'],
762
+ variantPlugins['hasVariants'],
763
+ variantPlugins['ariaVariants'],
764
+ variantPlugins['dataVariants'],
543
765
  ]
544
766
  let afterVariants = [
545
- variantPlugins['directionVariants'],
767
+ variantPlugins['supportsVariants'],
546
768
  variantPlugins['reducedMotionVariants'],
547
- variantPlugins['darkVariants'],
548
- variantPlugins['printVariant'],
769
+ variantPlugins['prefersContrastVariants'],
549
770
  variantPlugins['screenVariants'],
550
771
  variantPlugins['orientationVariants'],
772
+ variantPlugins['directionVariants'],
773
+ variantPlugins['darkVariants'],
774
+ variantPlugins['forcedColorsVariants'],
775
+ variantPlugins['printVariant'],
551
776
  ]
552
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
+
553
799
  return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
554
800
  }
555
801
 
556
802
  function registerPlugins(plugins, context) {
557
803
  let variantList = []
558
804
  let variantMap = new Map()
559
- let offsets = {
560
- defaults: 0n,
561
- base: 0n,
562
- components: 0n,
563
- utilities: 0n,
564
- user: 0n,
565
- }
805
+ context.variantMap = variantMap
806
+
807
+ let offsets = new Offsets()
808
+ context.offsets = offsets
566
809
 
567
810
  let classList = new Set()
568
811
 
@@ -583,49 +826,17 @@ function registerPlugins(plugins, context) {
583
826
  }
584
827
  }
585
828
 
586
- let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
587
- offsets.base,
588
- offsets.defaults,
589
- offsets.components,
590
- offsets.utilities,
591
- offsets.user,
592
- ])
593
- let reservedBits = BigInt(highestOffset.toString(2).length)
594
-
595
- // A number one less than the top range of the highest offset area
596
- // so arbitrary properties are always sorted at the end.
597
- context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
598
-
599
- context.layerOrder = {
600
- defaults: (1n << reservedBits) << 0n,
601
- base: (1n << reservedBits) << 1n,
602
- components: (1n << reservedBits) << 2n,
603
- utilities: (1n << reservedBits) << 3n,
604
- user: (1n << reservedBits) << 4n,
605
- }
606
-
607
- reservedBits += 5n
608
-
609
- let offset = 0
610
- context.variantOrder = new Map(
611
- variantList
612
- .map((variant, i) => {
613
- let variantFunctions = variantMap.get(variant).length
614
- let bits = (1n << BigInt(i + offset)) << reservedBits
615
- offset += variantFunctions - 1
616
- return [variant, bits]
617
- })
618
- .sort(([, a], [, z]) => bigSign(a - z))
619
- )
620
-
621
- context.minimumScreen = [...context.variantOrder.values()].shift()
829
+ // Make sure to record bit masks for every variant
830
+ offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length)
622
831
 
623
832
  // Build variantMap
624
833
  for (let [variantName, variantFunctions] of variantMap.entries()) {
625
- let sort = context.variantOrder.get(variantName)
626
834
  context.variantMap.set(
627
835
  variantName,
628
- variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction])
836
+ variantFunctions.map((variantFunction, idx) => [
837
+ offsets.forVariant(variantName, idx),
838
+ variantFunction,
839
+ ])
629
840
  )
630
841
  }
631
842
 
@@ -643,7 +854,7 @@ function registerPlugins(plugins, context) {
643
854
  log.warn('root-regex', [
644
855
  'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
645
856
  'Update your `safelist` configuration to eliminate this warning.',
646
- // TODO: Add https://tw.wtf/regex-safelist
857
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
647
858
  ])
648
859
  continue
649
860
  }
@@ -653,17 +864,46 @@ function registerPlugins(plugins, context) {
653
864
 
654
865
  if (checks.length > 0) {
655
866
  let patternMatchingCount = new Map()
867
+ let prefixLength = context.tailwindConfig.prefix.length
868
+ let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
656
869
 
657
870
  for (let util of classList) {
658
871
  let utils = Array.isArray(util)
659
872
  ? (() => {
660
873
  let [utilName, options] = util
661
- let classes = Object.keys(options?.values ?? {}).map((value) =>
662
- formatClass(utilName, value)
663
- )
874
+ let values = Object.keys(options?.values ?? {})
875
+ let classes = values.map((value) => formatClass(utilName, value))
664
876
 
665
877
  if (options?.supportsNegativeValues) {
878
+ // This is the normal negated version
879
+ // e.g. `-inset-1` or `-tw-inset-1`
666
880
  classes = [...classes, ...classes.map((cls) => '-' + cls)]
881
+
882
+ // This is the negated version *after* the prefix
883
+ // e.g. `tw--inset-1`
884
+ // The prefix is already attached to util name
885
+ // So we add the negative after the prefix
886
+ classes = [
887
+ ...classes,
888
+ ...classes.map(
889
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
890
+ ),
891
+ ]
892
+ }
893
+
894
+ if (options.types.some(({ type }) => type === 'color')) {
895
+ classes = [
896
+ ...classes,
897
+ ...classes.flatMap((cls) =>
898
+ Object.keys(context.tailwindConfig.theme.opacity).map(
899
+ (opacity) => `${cls}/${opacity}`
900
+ )
901
+ ),
902
+ ]
903
+ }
904
+
905
+ if (checkImportantUtils && options?.respectImportant) {
906
+ classes = [...classes, ...classes.map((cls) => '!' + cls)]
667
907
  }
668
908
 
669
909
  return classes
@@ -701,25 +941,95 @@ function registerPlugins(plugins, context) {
701
941
  log.warn([
702
942
  `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
703
943
  'Fix this pattern or remove it from your `safelist` configuration.',
944
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
704
945
  ])
705
946
  }
706
947
  }
707
948
  }
708
949
 
950
+ let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark'
951
+
952
+ // A list of utilities that are used by certain Tailwind CSS utilities but
953
+ // that don't exist on their own. This will result in them "not existing" and
954
+ // sorting could be weird since you still require them in order to make the
955
+ // host utilities work properly. (Thanks Biology)
956
+ let parasiteUtilities = [
957
+ prefix(context, darkClassName),
958
+ prefix(context, 'group'),
959
+ prefix(context, 'peer'),
960
+ ]
961
+ context.getClassOrder = function getClassOrder(classes) {
962
+ // Sort classes so they're ordered in a deterministic manner
963
+ let sorted = [...classes].sort((a, z) => {
964
+ if (a === z) return 0
965
+ if (a < z) return -1
966
+ return 1
967
+ })
968
+
969
+ // Non-util classes won't be generated, so we default them to null
970
+ let sortedClassNames = new Map(sorted.map((className) => [className, null]))
971
+
972
+ // Sort all classes in order
973
+ // Non-tailwind classes won't be generated and will be left as `null`
974
+ let rules = generateRules(new Set(sorted), context, true)
975
+ rules = context.offsets.sort(rules)
976
+
977
+ let idx = BigInt(parasiteUtilities.length)
978
+
979
+ for (const [, rule] of rules) {
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++)
985
+ }
986
+
987
+ return classes.map((className) => {
988
+ let order = sortedClassNames.get(className) ?? null
989
+ let parasiteIndex = parasiteUtilities.indexOf(className)
990
+
991
+ if (order === null && parasiteIndex !== -1) {
992
+ // This will make sure that it is at the very beginning of the
993
+ // `components` layer which technically means 'before any
994
+ // components'.
995
+ order = BigInt(parasiteIndex)
996
+ }
997
+
998
+ return [className, order]
999
+ })
1000
+ }
1001
+
709
1002
  // Generate a list of strings for autocompletion purposes, e.g.
710
1003
  // ['uppercase', 'lowercase', ...]
711
- context.getClassList = function () {
1004
+ context.getClassList = function getClassList(options = {}) {
712
1005
  let output = []
713
1006
 
714
1007
  for (let util of classList) {
715
1008
  if (Array.isArray(util)) {
716
- let [utilName, options] = util
1009
+ let [utilName, utilOptions] = util
717
1010
  let negativeClasses = []
718
1011
 
719
- for (let [key, value] of Object.entries(options?.values ?? {})) {
720
- output.push(formatClass(utilName, key))
721
- if (options?.supportsNegativeValues && negateValue(value)) {
722
- negativeClasses.push(formatClass(utilName, `-${key}`))
1012
+ let modifiers = Object.keys(utilOptions?.modifiers ?? {})
1013
+
1014
+ if (utilOptions?.types?.some(({ type }) => type === 'color')) {
1015
+ modifiers.push(...Object.keys(context.tailwindConfig.theme.opacity ?? {}))
1016
+ }
1017
+
1018
+ let metadata = { modifiers }
1019
+ let includeMetadata = options.includeMetadata && modifiers.length > 0
1020
+
1021
+ for (let [key, value] of Object.entries(utilOptions?.values ?? {})) {
1022
+ // Ignore undefined and null values
1023
+ if (value == null) {
1024
+ continue
1025
+ }
1026
+
1027
+ let cls = formatClass(utilName, key)
1028
+ output.push(includeMetadata ? [cls, metadata] : cls)
1029
+
1030
+ if (utilOptions?.supportsNegativeValues && negateValue(value)) {
1031
+ let cls = formatClass(utilName, `-${key}`)
1032
+ negativeClasses.push(includeMetadata ? [cls, metadata] : cls)
723
1033
  }
724
1034
  }
725
1035
 
@@ -731,21 +1041,235 @@ function registerPlugins(plugins, context) {
731
1041
 
732
1042
  return output
733
1043
  }
1044
+
1045
+ // Generate a list of available variants with meta information of the type of variant.
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
+
1052
+ let result = []
1053
+ for (let [name, options] of context.variantOptions.entries()) {
1054
+ if (options.variantInfo === VARIANT_INFO.Base) continue
1055
+
1056
+ result.push({
1057
+ name,
1058
+ isArbitrary: options.type === Symbol.for('MATCH_VARIANT'),
1059
+ values: Object.keys(options.values ?? {}),
1060
+ hasDash: name !== '@',
1061
+ selectors({ modifier, value } = {}) {
1062
+ let candidate = `TAILWINDPLACEHOLDER${id}`
1063
+
1064
+ let rule = postcss.rule({ selector: `.${candidate}` })
1065
+ let container = postcss.root({ nodes: [rule.clone()] })
1066
+
1067
+ let before = container.toString()
1068
+
1069
+ let fns = (context.variantMap.get(name) ?? []).flatMap(([_, fn]) => fn)
1070
+ let formatStrings = []
1071
+ for (let fn of fns) {
1072
+ let localFormatStrings = []
1073
+
1074
+ let api = {
1075
+ args: { modifier, value: options.values?.[value] ?? value },
1076
+ separator: context.tailwindConfig.separator,
1077
+ modifySelectors(modifierFunction) {
1078
+ // Run the modifierFunction over each rule
1079
+ container.each((rule) => {
1080
+ if (rule.type !== 'rule') {
1081
+ return
1082
+ }
1083
+
1084
+ rule.selectors = rule.selectors.map((selector) => {
1085
+ return modifierFunction({
1086
+ get className() {
1087
+ return getClassNameFromSelector(selector)
1088
+ },
1089
+ selector,
1090
+ })
1091
+ })
1092
+ })
1093
+
1094
+ return container
1095
+ },
1096
+ format(str) {
1097
+ localFormatStrings.push(str)
1098
+ },
1099
+ wrap(wrapper) {
1100
+ localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`)
1101
+ },
1102
+ container,
1103
+ }
1104
+
1105
+ let ruleWithVariant = fn(api)
1106
+ if (localFormatStrings.length > 0) {
1107
+ formatStrings.push(localFormatStrings)
1108
+ }
1109
+
1110
+ if (Array.isArray(ruleWithVariant)) {
1111
+ for (let variantFunction of ruleWithVariant) {
1112
+ localFormatStrings = []
1113
+ variantFunction(api)
1114
+ formatStrings.push(localFormatStrings)
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ // Reverse engineer the result of the `container`
1120
+ let manualFormatStrings = []
1121
+ let after = container.toString()
1122
+
1123
+ if (before !== after) {
1124
+ // Figure out all selectors
1125
+ container.walkRules((rule) => {
1126
+ let modified = rule.selector
1127
+
1128
+ // Rebuild the base selector, this is what plugin authors would do
1129
+ // as well. E.g.: `${variant}${separator}${className}`.
1130
+ // However, plugin authors probably also prepend or append certain
1131
+ // classes, pseudos, ids, ...
1132
+ let rebuiltBase = selectorParser((selectors) => {
1133
+ selectors.walkClasses((classNode) => {
1134
+ classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}`
1135
+ })
1136
+ }).processSync(modified)
1137
+
1138
+ // Now that we know the original selector, the new selector, and
1139
+ // the rebuild part in between, we can replace the part that plugin
1140
+ // authors need to rebuild with `&`, and eventually store it in the
1141
+ // collectedFormats. Similar to what `format('...')` would do.
1142
+ //
1143
+ // E.g.:
1144
+ // variant: foo
1145
+ // selector: .markdown > p
1146
+ // modified (by plugin): .foo .foo\\:markdown > p
1147
+ // rebuiltBase (internal): .foo\\:markdown > p
1148
+ // format: .foo &
1149
+ manualFormatStrings.push(modified.replace(rebuiltBase, '&').replace(candidate, '&'))
1150
+ })
1151
+
1152
+ // Figure out all atrules
1153
+ container.walkAtRules((atrule) => {
1154
+ manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`)
1155
+ })
1156
+ }
1157
+
1158
+ let isArbitraryVariant = !(value in (options.values ?? {}))
1159
+ let internalFeatures = options[INTERNAL_FEATURES] ?? {}
1160
+
1161
+ let respectPrefix = (() => {
1162
+ if (isArbitraryVariant) return false
1163
+ if (internalFeatures.respectPrefix === false) return false
1164
+ return true
1165
+ })()
1166
+
1167
+ formatStrings = formatStrings.map((format) =>
1168
+ format.map((str) => ({
1169
+ format: str,
1170
+ respectPrefix,
1171
+ }))
1172
+ )
1173
+
1174
+ manualFormatStrings = manualFormatStrings.map((format) => ({
1175
+ format,
1176
+ respectPrefix,
1177
+ }))
1178
+
1179
+ let opts = {
1180
+ candidate,
1181
+ context,
1182
+ }
1183
+
1184
+ let result = formatStrings.map((formats) =>
1185
+ finalizeSelector(`.${candidate}`, formatVariantSelector(formats, opts), opts)
1186
+ .replace(`.${candidate}`, '&')
1187
+ .replace('{ & }', '')
1188
+ .trim()
1189
+ )
1190
+
1191
+ if (manualFormatStrings.length > 0) {
1192
+ result.push(
1193
+ formatVariantSelector(manualFormatStrings, opts)
1194
+ .toString()
1195
+ .replace(`.${candidate}`, '&')
1196
+ )
1197
+ }
1198
+
1199
+ return result
1200
+ },
1201
+ })
1202
+ }
1203
+
1204
+ return result
1205
+ }
1206
+ }
1207
+
1208
+ /**
1209
+ * Mark as class as retroactively invalid
1210
+ *
1211
+ *
1212
+ * @param {string} candidate
1213
+ */
1214
+ function markInvalidUtilityCandidate(context, candidate) {
1215
+ if (!context.classCache.has(candidate)) {
1216
+ return
1217
+ }
1218
+
1219
+ // Mark this as not being a real utility
1220
+ context.notClassCache.add(candidate)
1221
+
1222
+ // Remove it from any candidate-specific caches
1223
+ context.classCache.delete(candidate)
1224
+ context.applyClassCache.delete(candidate)
1225
+ context.candidateRuleMap.delete(candidate)
1226
+ context.candidateRuleCache.delete(candidate)
1227
+
1228
+ // Ensure the stylesheet gets rebuilt
1229
+ context.stylesheetCache = null
1230
+ }
1231
+
1232
+ /**
1233
+ * Mark as class as retroactively invalid
1234
+ *
1235
+ * @param {import('postcss').Node} node
1236
+ */
1237
+ function markInvalidUtilityNode(context, node) {
1238
+ let candidate = node.raws.tailwind.candidate
1239
+
1240
+ if (!candidate) {
1241
+ return
1242
+ }
1243
+
1244
+ for (const entry of context.ruleCache) {
1245
+ if (entry[1].raws.tailwind.candidate === candidate) {
1246
+ context.ruleCache.delete(entry)
1247
+ // context.postCssNodeCache.delete(node)
1248
+ }
1249
+ }
1250
+
1251
+ markInvalidUtilityCandidate(context, candidate)
734
1252
  }
735
1253
 
736
1254
  export function createContext(tailwindConfig, changedContent = [], root = postcss.root()) {
737
1255
  let context = {
738
1256
  disposables: [],
739
1257
  ruleCache: new Set(),
1258
+ candidateRuleCache: new Map(),
740
1259
  classCache: new Map(),
741
1260
  applyClassCache: new Map(),
742
- notClassCache: new Set(),
1261
+ // Seed the not class cache with the blocklist (which is only strings)
1262
+ notClassCache: new Set(tailwindConfig.blocklist ?? []),
743
1263
  postCssNodeCache: new Map(),
744
1264
  candidateRuleMap: new Map(),
745
1265
  tailwindConfig,
746
1266
  changedContent: changedContent,
747
1267
  variantMap: new Map(),
748
1268
  stylesheetCache: null,
1269
+ variantOptions: new Map(),
1270
+
1271
+ markInvalidUtilityCandidate: (candidate) => markInvalidUtilityCandidate(context, candidate),
1272
+ markInvalidUtilityNode: (node) => markInvalidUtilityNode(context, node),
749
1273
  }
750
1274
 
751
1275
  let resolvedPlugins = resolvePlugins(context, root)
@@ -783,15 +1307,17 @@ export function getContext(
783
1307
  existingContext = context
784
1308
  }
785
1309
 
1310
+ let cssDidChange = hasContentChanged(sourcePath, root)
1311
+
786
1312
  // If there's already a context in the cache and we don't need to
787
1313
  // reset the context, return the cached context.
788
1314
  if (existingContext) {
789
- let contextDependenciesChanged = trackModified(
1315
+ let [contextDependenciesChanged, mtimesToCommit] = trackModified(
790
1316
  [...contextDependencies],
791
1317
  getFileModifiedMap(existingContext)
792
1318
  )
793
- if (!contextDependenciesChanged) {
794
- return [existingContext, false]
1319
+ if (!contextDependenciesChanged && !cssDidChange) {
1320
+ return [existingContext, false, mtimesToCommit]
795
1321
  }
796
1322
  }
797
1323
 
@@ -822,7 +1348,11 @@ export function getContext(
822
1348
 
823
1349
  let context = createContext(tailwindConfig, [], root)
824
1350
 
825
- trackModified([...contextDependencies], getFileModifiedMap(context))
1351
+ Object.assign(context, {
1352
+ userConfigPath,
1353
+ })
1354
+
1355
+ let [, mtimesToCommit] = trackModified([...contextDependencies], getFileModifiedMap(context))
826
1356
 
827
1357
  // ---
828
1358
 
@@ -837,5 +1367,5 @@ export function getContext(
837
1367
 
838
1368
  contextSourcesMap.get(context).add(sourcePath)
839
1369
 
840
- return [context, true]
1370
+ return [context, true, mtimesToCommit]
841
1371
  }