tailwindcss 0.0.0-insiders.fe08e91 → 0.0.0-oxide.6bf5e56

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 (176) hide show
  1. package/CHANGELOG.md +379 -3
  2. package/LICENSE +1 -2
  3. package/README.md +12 -8
  4. package/colors.d.ts +3 -0
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultTheme.d.ts +4 -0
  7. package/lib/cli/build/deps.js +54 -0
  8. package/lib/cli/build/index.js +48 -0
  9. package/lib/cli/build/plugin.js +367 -0
  10. package/lib/cli/build/utils.js +78 -0
  11. package/lib/cli/build/watching.js +178 -0
  12. package/lib/cli/help/index.js +71 -0
  13. package/lib/cli/index.js +18 -0
  14. package/lib/cli/init/index.js +46 -0
  15. package/lib/cli/shared.js +13 -0
  16. package/lib/cli-peer-dependencies.js +20 -7
  17. package/lib/cli.js +107 -611
  18. package/lib/constants.js +27 -20
  19. package/lib/corePluginList.js +6 -3
  20. package/lib/corePlugins.js +2064 -1811
  21. package/lib/css/preflight.css +5 -5
  22. package/lib/featureFlags.js +31 -22
  23. package/lib/index.js +28 -10
  24. package/lib/lib/cacheInvalidation.js +90 -0
  25. package/lib/lib/collapseAdjacentRules.js +27 -9
  26. package/lib/lib/collapseDuplicateDeclarations.js +12 -9
  27. package/lib/lib/content.js +176 -0
  28. package/lib/lib/defaultExtractor.js +225 -31
  29. package/lib/lib/detectNesting.js +13 -10
  30. package/lib/lib/evaluateTailwindFunctions.js +118 -55
  31. package/lib/lib/expandApplyAtRules.js +439 -190
  32. package/lib/lib/expandTailwindAtRules.js +151 -134
  33. package/lib/lib/findAtConfigPath.js +44 -0
  34. package/lib/lib/generateRules.js +454 -187
  35. package/lib/lib/getModuleDependencies.js +11 -8
  36. package/lib/lib/normalizeTailwindDirectives.js +36 -32
  37. package/lib/lib/offsets.js +217 -0
  38. package/lib/lib/partitionApplyAtRules.js +56 -0
  39. package/lib/lib/regex.js +60 -0
  40. package/lib/lib/resolveDefaultsAtRules.js +89 -67
  41. package/lib/lib/setupContextUtils.js +667 -376
  42. package/lib/lib/setupTrackingContext.js +38 -67
  43. package/lib/lib/sharedState.js +27 -14
  44. package/lib/lib/substituteScreenAtRules.js +11 -9
  45. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  46. package/lib/postcss-plugins/nesting/index.js +19 -0
  47. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  48. package/lib/processTailwindFeatures.js +35 -25
  49. package/lib/public/colors.js +247 -245
  50. package/lib/public/create-plugin.js +6 -4
  51. package/lib/public/default-config.js +7 -5
  52. package/lib/public/default-theme.js +7 -5
  53. package/lib/public/resolve-config.js +8 -5
  54. package/lib/util/bigSign.js +4 -1
  55. package/lib/util/buildMediaQuery.js +11 -6
  56. package/lib/util/cloneDeep.js +7 -6
  57. package/lib/util/cloneNodes.js +21 -3
  58. package/lib/util/color.js +53 -54
  59. package/lib/util/configurePlugins.js +5 -2
  60. package/lib/util/createPlugin.js +6 -6
  61. package/lib/util/createUtilityPlugin.js +12 -14
  62. package/lib/util/dataTypes.js +119 -110
  63. package/lib/util/defaults.js +4 -1
  64. package/lib/util/escapeClassName.js +7 -4
  65. package/lib/util/escapeCommas.js +5 -2
  66. package/lib/util/flattenColorPalette.js +9 -12
  67. package/lib/util/formatVariantSelector.js +184 -85
  68. package/lib/util/getAllConfigs.js +27 -8
  69. package/lib/util/hashConfig.js +6 -3
  70. package/lib/util/isKeyframeRule.js +5 -2
  71. package/lib/util/isPlainObject.js +5 -2
  72. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +23 -15
  73. package/lib/util/log.js +20 -14
  74. package/lib/util/nameClass.js +20 -9
  75. package/lib/util/negateValue.js +23 -8
  76. package/lib/util/normalizeConfig.js +116 -72
  77. package/lib/util/normalizeScreens.js +120 -11
  78. package/lib/util/parseAnimationValue.js +42 -40
  79. package/lib/util/parseBoxShadowValue.js +30 -23
  80. package/lib/util/parseDependency.js +38 -56
  81. package/lib/util/parseGlob.js +34 -0
  82. package/lib/util/parseObjectStyles.js +11 -8
  83. package/lib/util/pluginUtils.js +147 -50
  84. package/lib/util/prefixSelector.js +10 -8
  85. package/lib/util/removeAlphaVariables.js +29 -0
  86. package/lib/util/resolveConfig.js +97 -85
  87. package/lib/util/resolveConfigPath.js +11 -9
  88. package/lib/util/responsive.js +8 -5
  89. package/lib/util/splitAtTopLevelOnly.js +43 -0
  90. package/lib/util/tap.js +4 -1
  91. package/lib/util/toColorValue.js +5 -3
  92. package/lib/util/toPath.js +20 -4
  93. package/lib/util/transformThemeValue.js +37 -29
  94. package/lib/util/validateConfig.js +24 -0
  95. package/lib/util/validateFormalSyntax.js +24 -0
  96. package/lib/util/withAlphaVariable.js +23 -15
  97. package/nesting/index.js +2 -12
  98. package/package.json +47 -42
  99. package/peers/index.js +11381 -7950
  100. package/plugin.d.ts +11 -0
  101. package/resolveConfig.d.ts +12 -0
  102. package/scripts/generate-types.js +105 -0
  103. package/scripts/release-channel.js +18 -0
  104. package/scripts/release-notes.js +21 -0
  105. package/scripts/type-utils.js +27 -0
  106. package/src/cli/build/deps.js +56 -0
  107. package/src/cli/build/index.js +49 -0
  108. package/src/cli/build/plugin.js +439 -0
  109. package/src/cli/build/utils.js +76 -0
  110. package/src/cli/build/watching.js +227 -0
  111. package/src/cli/help/index.js +70 -0
  112. package/src/cli/index.js +3 -0
  113. package/src/cli/init/index.js +50 -0
  114. package/src/cli/shared.js +6 -0
  115. package/src/cli-peer-dependencies.js +7 -1
  116. package/src/cli.js +50 -629
  117. package/src/corePluginList.js +1 -1
  118. package/src/corePlugins.js +532 -217
  119. package/src/css/preflight.css +5 -5
  120. package/src/featureFlags.js +15 -9
  121. package/src/index.js +20 -1
  122. package/src/lib/cacheInvalidation.js +52 -0
  123. package/src/lib/collapseAdjacentRules.js +21 -2
  124. package/src/lib/content.js +212 -0
  125. package/src/lib/defaultExtractor.js +196 -33
  126. package/src/lib/evaluateTailwindFunctions.js +78 -7
  127. package/src/lib/expandApplyAtRules.js +482 -183
  128. package/src/lib/expandTailwindAtRules.js +106 -85
  129. package/src/lib/findAtConfigPath.js +48 -0
  130. package/src/lib/generateRules.js +418 -129
  131. package/src/lib/normalizeTailwindDirectives.js +1 -0
  132. package/src/lib/offsets.js +270 -0
  133. package/src/lib/partitionApplyAtRules.js +52 -0
  134. package/src/lib/regex.js +74 -0
  135. package/src/lib/resolveDefaultsAtRules.js +51 -30
  136. package/src/lib/setupContextUtils.js +556 -208
  137. package/src/lib/setupTrackingContext.js +11 -48
  138. package/src/lib/sharedState.js +5 -0
  139. package/src/postcss-plugins/nesting/README.md +42 -0
  140. package/src/postcss-plugins/nesting/index.js +13 -0
  141. package/src/postcss-plugins/nesting/plugin.js +80 -0
  142. package/src/processTailwindFeatures.js +8 -0
  143. package/src/util/buildMediaQuery.js +5 -3
  144. package/src/util/cloneNodes.js +19 -2
  145. package/src/util/color.js +25 -21
  146. package/src/util/dataTypes.js +29 -21
  147. package/src/util/formatVariantSelector.js +184 -61
  148. package/src/util/getAllConfigs.js +19 -0
  149. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  150. package/src/util/log.js +8 -8
  151. package/src/util/nameClass.js +4 -0
  152. package/src/util/negateValue.js +11 -3
  153. package/src/util/normalizeConfig.js +44 -6
  154. package/src/util/normalizeScreens.js +99 -4
  155. package/src/util/parseBoxShadowValue.js +4 -3
  156. package/src/util/parseDependency.js +37 -42
  157. package/src/util/parseGlob.js +24 -0
  158. package/src/util/pluginUtils.js +132 -10
  159. package/src/util/prefixSelector.js +7 -5
  160. package/src/util/removeAlphaVariables.js +24 -0
  161. package/src/util/resolveConfig.js +70 -32
  162. package/src/util/splitAtTopLevelOnly.js +45 -0
  163. package/src/util/toPath.js +1 -1
  164. package/src/util/transformThemeValue.js +13 -3
  165. package/src/util/validateConfig.js +13 -0
  166. package/src/util/validateFormalSyntax.js +34 -0
  167. package/src/util/withAlphaVariable.js +1 -1
  168. package/stubs/defaultConfig.stub.js +23 -20
  169. package/stubs/simpleConfig.stub.js +1 -0
  170. package/types/config.d.ts +362 -0
  171. package/types/generated/.gitkeep +0 -0
  172. package/types/generated/colors.d.ts +276 -0
  173. package/types/generated/corePluginList.d.ts +1 -0
  174. package/types/generated/default-theme.d.ts +342 -0
  175. package/types/index.d.ts +7 -0
  176. package/nesting/plugin.js +0 -41
@@ -11,65 +11,46 @@ 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 partitionRules(root) {
24
- if (!root.walkAtRules) return [root]
25
-
26
- let applyParents = new Set()
27
- let rules = []
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
+ const VARIANT_TYPES = {
28
+ AddVariant: Symbol.for('ADD_VARIANT'),
29
+ MatchVariant: Symbol.for('MATCH_VARIANT'),
30
+ }
28
31
 
29
- root.walkAtRules('apply', (rule) => {
30
- applyParents.add(rule.parent)
31
- })
32
+ const VARIANT_INFO = {
33
+ Base: 1 << 0,
34
+ Dynamic: 1 << 1,
35
+ }
32
36
 
33
- if (applyParents.size === 0) {
34
- rules.push(root)
35
- }
37
+ function prefix(context, selector) {
38
+ let prefix = context.tailwindConfig.prefix
39
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
40
+ }
36
41
 
37
- for (let rule of applyParents) {
38
- let nodeGroups = []
39
- let lastGroup = []
42
+ function normalizeOptionTypes({ type = 'any', ...options }) {
43
+ let types = [].concat(type)
40
44
 
41
- for (let node of rule.nodes) {
42
- if (node.type === 'atrule' && node.name === 'apply') {
43
- if (lastGroup.length > 0) {
44
- nodeGroups.push(lastGroup)
45
- lastGroup = []
46
- }
47
- nodeGroups.push([node])
48
- } else {
49
- lastGroup.push(node)
45
+ return {
46
+ ...options,
47
+ types: types.map((type) => {
48
+ if (Array.isArray(type)) {
49
+ return { type: type[0], ...type[1] }
50
50
  }
51
- }
52
-
53
- if (lastGroup.length > 0) {
54
- nodeGroups.push(lastGroup)
55
- }
56
-
57
- if (nodeGroups.length === 1) {
58
- rules.push(rule)
59
- continue
60
- }
61
-
62
- for (let group of [...nodeGroups].reverse()) {
63
- let clone = rule.clone({ nodes: [] })
64
- clone.append(group)
65
- rules.unshift(clone)
66
- rule.after(clone)
67
- }
68
-
69
- rule.remove()
51
+ return { type, preferOnConflict: false }
52
+ }),
70
53
  }
71
-
72
- return rules
73
54
  }
74
55
 
75
56
  function parseVariantFormatString(input) {
@@ -130,12 +111,18 @@ function parseStyles(styles) {
130
111
  })
131
112
  }
132
113
 
133
- function getClasses(selector) {
114
+ function getClasses(selector, mutate) {
134
115
  let parser = selectorParser((selectors) => {
135
116
  let allClasses = []
117
+
118
+ if (mutate) {
119
+ mutate(selectors)
120
+ }
121
+
136
122
  selectors.walkClasses((classNode) => {
137
123
  allClasses.push(classNode.value)
138
124
  })
125
+
139
126
  return allClasses
140
127
  })
141
128
  return parser.transformSync(selector)
@@ -146,8 +133,20 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
146
133
 
147
134
  // Handle normal rules
148
135
  if (node.type === 'rule') {
136
+ // Ignore everything inside a :not(...). This allows you to write code like
137
+ // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
138
+ // not generated it. But now we will ignore everything inside a `:not`, so
139
+ // that it still gets generated.
140
+ function ignoreNot(selectors) {
141
+ selectors.walkPseudos((pseudo) => {
142
+ if (pseudo.value === ':not') {
143
+ pseudo.remove()
144
+ }
145
+ })
146
+ }
147
+
149
148
  for (let selector of node.selectors) {
150
- let classCandidates = getClasses(selector)
149
+ let classCandidates = getClasses(selector, ignoreNot)
151
150
  // At least one of the selectors contains non-"on-demandable" candidates.
152
151
  if (classCandidates.length === 0) {
153
152
  state.containsNonOnDemandable = true
@@ -162,9 +161,7 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
162
161
  // Handle at-rules (which contains nested rules)
163
162
  else if (node.type === 'atrule') {
164
163
  node.walkRules((rule) => {
165
- for (let classCandidate of rule.selectors.flatMap((selector) =>
166
- getClasses(selector, state, depth + 1)
167
- )) {
164
+ for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
168
165
  classes.push(classCandidate)
169
166
  }
170
167
  })
@@ -184,7 +181,7 @@ function withIdentifiers(styles) {
184
181
 
185
182
  // If this isn't "on-demandable", assign it a universal candidate to always include it.
186
183
  if (containsNonOnDemandableSelectors) {
187
- candidates.unshift('*')
184
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
188
185
  }
189
186
 
190
187
  // However, it could be that it also contains "on-demandable" candidates.
@@ -199,6 +196,41 @@ function withIdentifiers(styles) {
199
196
  })
200
197
  }
201
198
 
199
+ export function isValidVariantFormatString(format) {
200
+ return format.startsWith('@') || format.includes('&')
201
+ }
202
+
203
+ export function parseVariant(variant) {
204
+ variant = variant
205
+ .replace(/\n+/g, '')
206
+ .replace(/\s{1,}/g, ' ')
207
+ .trim()
208
+
209
+ let fns = parseVariantFormatString(variant)
210
+ .map((str) => {
211
+ if (!str.startsWith('@')) {
212
+ return ({ format }) => format(str)
213
+ }
214
+
215
+ let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str)
216
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() }))
217
+ })
218
+ .reverse()
219
+
220
+ return (api) => {
221
+ for (let fn of fns) {
222
+ fn(api)
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ *
229
+ * @param {any} tailwindConfig
230
+ * @param {any} context
231
+ * @param {object} param2
232
+ * @param {Offsets} param2.offsets
233
+ */
202
234
  function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
203
235
  function getConfigValue(path, defaultValue) {
204
236
  return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
@@ -209,8 +241,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
209
241
  }
210
242
 
211
243
  function prefixIdentifier(identifier, options) {
212
- if (identifier === '*') {
213
- return '*'
244
+ if (identifier === sharedState.NOT_ON_DEMAND) {
245
+ return sharedState.NOT_ON_DEMAND
214
246
  }
215
247
 
216
248
  if (!options.respectPrefix) {
@@ -220,51 +252,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
220
252
  return context.tailwindConfig.prefix + identifier
221
253
  }
222
254
 
223
- return {
224
- addVariant(variantName, variantFunctions, options = {}) {
225
- variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
226
- if (typeof variantFunction !== 'string') {
227
- // Safelist public API functions
228
- return ({ modifySelectors, container, separator }) => {
229
- return variantFunction({ modifySelectors, container, separator })
230
- }
231
- }
232
-
233
- variantFunction = variantFunction
234
- .replace(/\n+/g, '')
235
- .replace(/\s{1,}/g, ' ')
236
- .trim()
237
-
238
- let fns = parseVariantFormatString(variantFunction)
239
- .map((str) => {
240
- if (!str.startsWith('@')) {
241
- return ({ format }) => format(str)
242
- }
243
-
244
- let [, name, params] = /@(.*?) (.*)/g.exec(str)
245
- return ({ wrap }) => wrap(postcss.atRule({ name, params }))
246
- })
247
- .reverse()
248
-
249
- return (api) => {
250
- for (let fn of fns) {
251
- fn(api)
252
- }
253
- }
254
- })
255
+ function resolveThemeValue(path, defaultValue, opts = {}) {
256
+ let parts = toPath(path)
257
+ let value = getConfigValue(['theme', ...parts], defaultValue)
258
+ return transformThemeValue(parts[0])(value, opts)
259
+ }
255
260
 
256
- insertInto(variantList, variantName, options)
257
- variantMap.set(variantName, variantFunctions)
258
- },
261
+ let variantIdentifier = 0
262
+ let api = {
259
263
  postcss,
260
264
  prefix: applyConfiguredPrefix,
261
265
  e: escapeClassName,
262
266
  config: getConfigValue,
263
- theme(path, defaultValue) {
264
- const [pathRoot, ...subPaths] = toPath(path)
265
- const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
266
- return transformThemeValue(pathRoot)(value)
267
- },
267
+ theme: resolveThemeValue,
268
268
  corePlugins: (path) => {
269
269
  if (Array.isArray(tailwindConfig.corePlugins)) {
270
270
  return tailwindConfig.corePlugins.includes(path)
@@ -276,23 +276,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
276
276
  // Preserved for backwards compatibility but not used in v3.0+
277
277
  return []
278
278
  },
279
- addUserCss(userCss) {
280
- for (let [identifier, rule] of withIdentifiers(userCss)) {
281
- let offset = offsets.user++
282
-
283
- if (!context.candidateRuleMap.has(identifier)) {
284
- context.candidateRuleMap.set(identifier, [])
285
- }
286
-
287
- context.candidateRuleMap
288
- .get(identifier)
289
- .push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'user' }, rule]))
290
- }
291
- },
292
279
  addBase(base) {
293
280
  for (let [identifier, rule] of withIdentifiers(base)) {
294
281
  let prefixedIdentifier = prefixIdentifier(identifier, {})
295
- let offset = offsets.base++
282
+ let offset = offsets.create('base')
296
283
 
297
284
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
298
285
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -300,7 +287,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
300
287
 
301
288
  context.candidateRuleMap
302
289
  .get(prefixedIdentifier)
303
- .push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'base' }, rule]))
290
+ .push([{ sort: offset, layer: 'base' }, rule])
304
291
  }
305
292
  },
306
293
  /**
@@ -321,16 +308,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
321
308
 
322
309
  context.candidateRuleMap
323
310
  .get(prefixedIdentifier)
324
- .push(
325
- ...partitionRules(rule).map((rule) => [
326
- { sort: offsets.base++, layer: 'defaults' },
327
- rule,
328
- ])
329
- )
311
+ .push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule])
330
312
  }
331
313
  },
332
314
  addComponents(components, options) {
333
315
  let defaultOptions = {
316
+ preserveSource: false,
334
317
  respectPrefix: true,
335
318
  respectImportant: false,
336
319
  }
@@ -348,16 +331,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
348
331
 
349
332
  context.candidateRuleMap
350
333
  .get(prefixedIdentifier)
351
- .push(
352
- ...partitionRules(rule).map((rule) => [
353
- { sort: offsets.components++, layer: 'components', options },
354
- rule,
355
- ])
356
- )
334
+ .push([{ sort: offsets.create('components'), layer: 'components', options }, rule])
357
335
  }
358
336
  },
359
337
  addUtilities(utilities, options) {
360
338
  let defaultOptions = {
339
+ preserveSource: false,
361
340
  respectPrefix: true,
362
341
  respectImportant: true,
363
342
  }
@@ -375,23 +354,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
375
354
 
376
355
  context.candidateRuleMap
377
356
  .get(prefixedIdentifier)
378
- .push(
379
- ...partitionRules(rule).map((rule) => [
380
- { sort: offsets.utilities++, layer: 'utilities', options },
381
- rule,
382
- ])
383
- )
357
+ .push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule])
384
358
  }
385
359
  },
386
360
  matchUtilities: function (utilities, options) {
387
361
  let defaultOptions = {
388
362
  respectPrefix: true,
389
363
  respectImportant: true,
364
+ modifiers: false,
390
365
  }
391
366
 
392
- options = { ...defaultOptions, ...options }
367
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
393
368
 
394
- let offset = offsets.utilities++
369
+ let offset = offsets.create('utilities')
395
370
 
396
371
  for (let identifier in utilities) {
397
372
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -400,24 +375,51 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
400
375
  classList.add([prefixedIdentifier, options])
401
376
 
402
377
  function wrapped(modifier, { isOnlyPlugin }) {
403
- let { type = 'any' } = options
404
- type = [].concat(type)
405
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
378
+ let [value, coercedType, utilityModifier] = coerceValue(
379
+ options.types,
380
+ modifier,
381
+ options,
382
+ tailwindConfig
383
+ )
406
384
 
407
385
  if (value === undefined) {
408
386
  return []
409
387
  }
410
388
 
411
- if (!type.includes(coercedType) && !isOnlyPlugin) {
412
- return []
389
+ if (!options.types.some(({ type }) => type === coercedType)) {
390
+ if (isOnlyPlugin) {
391
+ log.warn([
392
+ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
393
+ `You can safely update it to \`${identifier}-${modifier.replace(
394
+ coercedType + ':',
395
+ ''
396
+ )}\`.`,
397
+ ])
398
+ } else {
399
+ return []
400
+ }
413
401
  }
414
402
 
415
- if (!isValidArbitraryValue(value)) {
403
+ if (!isSyntacticallyValidPropertyValue(value)) {
416
404
  return []
417
405
  }
418
406
 
407
+ let extras = {
408
+ get modifier() {
409
+ if (!options.modifiers) {
410
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
411
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
412
+ ])
413
+ }
414
+
415
+ return utilityModifier
416
+ },
417
+ }
418
+
419
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
420
+
419
421
  let ruleSets = []
420
- .concat(rule(value))
422
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
421
423
  .filter(Boolean)
422
424
  .map((declaration) => ({
423
425
  [nameClass(identifier, modifier)]: declaration,
@@ -439,11 +441,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
439
441
  let defaultOptions = {
440
442
  respectPrefix: true,
441
443
  respectImportant: false,
444
+ modifiers: false,
442
445
  }
443
446
 
444
- options = { ...defaultOptions, ...options }
447
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
445
448
 
446
- let offset = offsets.components++
449
+ let offset = offsets.create('components')
447
450
 
448
451
  for (let identifier in components) {
449
452
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -452,15 +455,18 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
452
455
  classList.add([prefixedIdentifier, options])
453
456
 
454
457
  function wrapped(modifier, { isOnlyPlugin }) {
455
- let { type = 'any' } = options
456
- type = [].concat(type)
457
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
458
+ let [value, coercedType, utilityModifier] = coerceValue(
459
+ options.types,
460
+ modifier,
461
+ options,
462
+ tailwindConfig
463
+ )
458
464
 
459
465
  if (value === undefined) {
460
466
  return []
461
467
  }
462
468
 
463
- if (!type.includes(coercedType)) {
469
+ if (!options.types.some(({ type }) => type === coercedType)) {
464
470
  if (isOnlyPlugin) {
465
471
  log.warn([
466
472
  `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
@@ -474,12 +480,26 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
474
480
  }
475
481
  }
476
482
 
477
- if (!isValidArbitraryValue(value)) {
483
+ if (!isSyntacticallyValidPropertyValue(value)) {
478
484
  return []
479
485
  }
480
486
 
487
+ let extras = {
488
+ get modifier() {
489
+ if (!options.modifiers) {
490
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
491
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
492
+ ])
493
+ }
494
+
495
+ return utilityModifier
496
+ },
497
+ }
498
+
499
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
500
+
481
501
  let ruleSets = []
482
- .concat(rule(value))
502
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
483
503
  .filter(Boolean)
484
504
  .map((declaration) => ({
485
505
  [nameClass(identifier, modifier)]: declaration,
@@ -497,7 +517,109 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
497
517
  context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
498
518
  }
499
519
  },
520
+ addVariant(variantName, variantFunctions, options = {}) {
521
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
522
+ if (typeof variantFunction !== 'string') {
523
+ // Safelist public API functions
524
+ return (api = {}) => {
525
+ let { args, modifySelectors, container, separator, wrap, format } = api
526
+ let result = variantFunction(
527
+ Object.assign(
528
+ { modifySelectors, container, separator },
529
+ options.type === VARIANT_TYPES.MatchVariant && { args, wrap, format }
530
+ )
531
+ )
532
+
533
+ if (typeof result === 'string' && !isValidVariantFormatString(result)) {
534
+ throw new Error(
535
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
536
+ )
537
+ }
538
+
539
+ if (Array.isArray(result)) {
540
+ return result
541
+ .filter((variant) => typeof variant === 'string')
542
+ .map((variant) => parseVariant(variant))
543
+ }
544
+
545
+ // result may be undefined with legacy variants that use APIs like `modifySelectors`
546
+ // result may also be a postcss node if someone was returning the result from `modifySelectors`
547
+ return result && typeof result === 'string' && parseVariant(result)(api)
548
+ }
549
+ }
550
+
551
+ if (!isValidVariantFormatString(variantFunction)) {
552
+ throw new Error(
553
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
554
+ )
555
+ }
556
+
557
+ return parseVariant(variantFunction)
558
+ })
559
+
560
+ insertInto(variantList, variantName, options)
561
+ variantMap.set(variantName, variantFunctions)
562
+ context.variantOptions.set(variantName, options)
563
+ },
564
+ matchVariant(variant, variantFn, options) {
565
+ // A unique identifier that "groups" these variants together.
566
+ // This is for internal use only which is why it is not present in the types
567
+ let id = options?.id ?? ++variantIdentifier
568
+ let isSpecial = variant === '@'
569
+
570
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
571
+
572
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
573
+ if (key === 'DEFAULT') continue
574
+
575
+ api.addVariant(
576
+ isSpecial ? `${variant}${key}` : `${variant}-${key}`,
577
+ ({ args, container }) => {
578
+ return variantFn(
579
+ value,
580
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
581
+ )
582
+ },
583
+
584
+ {
585
+ ...options,
586
+ value,
587
+ id,
588
+ type: VARIANT_TYPES.MatchVariant,
589
+ variantInfo: VARIANT_INFO.Base,
590
+ }
591
+ )
592
+ }
593
+
594
+ let hasDefault = 'DEFAULT' in (options?.values ?? {})
595
+
596
+ api.addVariant(
597
+ variant,
598
+ ({ args, container }) => {
599
+ if (args?.value === sharedState.NONE && !hasDefault) {
600
+ return null
601
+ }
602
+
603
+ return variantFn(
604
+ args?.value === sharedState.NONE
605
+ ? options.values.DEFAULT
606
+ : // Falling back to args if it is a string, otherwise '' for older intellisense
607
+ // (JetBrains) plugins.
608
+ args?.value ?? (typeof args === 'string' ? args : ''),
609
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
610
+ )
611
+ },
612
+ {
613
+ ...options,
614
+ id,
615
+ type: VARIANT_TYPES.MatchVariant,
616
+ variantInfo: VARIANT_INFO.Dynamic,
617
+ }
618
+ )
619
+ },
500
620
  }
621
+
622
+ return api
501
623
  }
502
624
 
503
625
  let fileModifiedMapCache = new WeakMap()
@@ -570,29 +692,20 @@ function collectLayerPlugins(root) {
570
692
  } else if (layerRule.params === 'components') {
571
693
  for (let node of layerRule.nodes) {
572
694
  layerPlugins.push(function ({ addComponents }) {
573
- addComponents(node, { respectPrefix: false })
695
+ addComponents(node, { respectPrefix: false, preserveSource: true })
574
696
  })
575
697
  }
576
698
  layerRule.remove()
577
699
  } else if (layerRule.params === 'utilities') {
578
700
  for (let node of layerRule.nodes) {
579
701
  layerPlugins.push(function ({ addUtilities }) {
580
- addUtilities(node, { respectPrefix: false })
702
+ addUtilities(node, { respectPrefix: false, preserveSource: true })
581
703
  })
582
704
  }
583
705
  layerRule.remove()
584
706
  }
585
707
  })
586
708
 
587
- root.walkRules((rule) => {
588
- // At this point it is safe to include all the left-over css from the
589
- // user's css file. This is because the `@tailwind` and `@layer` directives
590
- // will already be handled and will be removed from the css tree.
591
- layerPlugins.push(function ({ addUserCss }) {
592
- addUserCss(rule, { respectPrefix: false })
593
- })
594
- })
595
-
596
709
  return layerPlugins
597
710
  }
598
711
 
@@ -622,10 +735,14 @@ function resolvePlugins(context, root) {
622
735
  let beforeVariants = [
623
736
  variantPlugins['pseudoElementVariants'],
624
737
  variantPlugins['pseudoClassVariants'],
738
+ variantPlugins['ariaVariants'],
739
+ variantPlugins['dataVariants'],
625
740
  ]
626
741
  let afterVariants = [
742
+ variantPlugins['supportsVariants'],
627
743
  variantPlugins['directionVariants'],
628
744
  variantPlugins['reducedMotionVariants'],
745
+ variantPlugins['prefersContrastVariants'],
629
746
  variantPlugins['darkVariants'],
630
747
  variantPlugins['printVariant'],
631
748
  variantPlugins['screenVariants'],
@@ -638,13 +755,10 @@ function resolvePlugins(context, root) {
638
755
  function registerPlugins(plugins, context) {
639
756
  let variantList = []
640
757
  let variantMap = new Map()
641
- let offsets = {
642
- defaults: 0n,
643
- base: 0n,
644
- components: 0n,
645
- utilities: 0n,
646
- user: 0n,
647
- }
758
+ context.variantMap = variantMap
759
+
760
+ let offsets = new Offsets()
761
+ context.offsets = offsets
648
762
 
649
763
  let classList = new Set()
650
764
 
@@ -665,49 +779,17 @@ function registerPlugins(plugins, context) {
665
779
  }
666
780
  }
667
781
 
668
- let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
669
- offsets.base,
670
- offsets.defaults,
671
- offsets.components,
672
- offsets.utilities,
673
- offsets.user,
674
- ])
675
- let reservedBits = BigInt(highestOffset.toString(2).length)
676
-
677
- // A number one less than the top range of the highest offset area
678
- // so arbitrary properties are always sorted at the end.
679
- context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
680
-
681
- context.layerOrder = {
682
- defaults: (1n << reservedBits) << 0n,
683
- base: (1n << reservedBits) << 1n,
684
- components: (1n << reservedBits) << 2n,
685
- utilities: (1n << reservedBits) << 3n,
686
- user: (1n << reservedBits) << 4n,
687
- }
688
-
689
- reservedBits += 5n
690
-
691
- let offset = 0
692
- context.variantOrder = new Map(
693
- variantList
694
- .map((variant, i) => {
695
- let variantFunctions = variantMap.get(variant).length
696
- let bits = (1n << BigInt(i + offset)) << reservedBits
697
- offset += variantFunctions - 1
698
- return [variant, bits]
699
- })
700
- .sort(([, a], [, z]) => bigSign(a - z))
701
- )
702
-
703
- context.minimumScreen = [...context.variantOrder.values()].shift()
782
+ // Make sure to record bit masks for every variant
783
+ offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length)
704
784
 
705
785
  // Build variantMap
706
786
  for (let [variantName, variantFunctions] of variantMap.entries()) {
707
- let sort = context.variantOrder.get(variantName)
708
787
  context.variantMap.set(
709
788
  variantName,
710
- variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction])
789
+ variantFunctions.map((variantFunction, idx) => [
790
+ offsets.forVariant(variantName, idx),
791
+ variantFunction,
792
+ ])
711
793
  )
712
794
  }
713
795
 
@@ -725,7 +807,7 @@ function registerPlugins(plugins, context) {
725
807
  log.warn('root-regex', [
726
808
  'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
727
809
  'Update your `safelist` configuration to eliminate this warning.',
728
- // TODO: Add https://tw.wtf/regex-safelist
810
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
729
811
  ])
730
812
  continue
731
813
  }
@@ -735,17 +817,46 @@ function registerPlugins(plugins, context) {
735
817
 
736
818
  if (checks.length > 0) {
737
819
  let patternMatchingCount = new Map()
820
+ let prefixLength = context.tailwindConfig.prefix.length
821
+ let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
738
822
 
739
823
  for (let util of classList) {
740
824
  let utils = Array.isArray(util)
741
825
  ? (() => {
742
826
  let [utilName, options] = util
743
- let classes = Object.keys(options?.values ?? {}).map((value) =>
744
- formatClass(utilName, value)
745
- )
827
+ let values = Object.keys(options?.values ?? {})
828
+ let classes = values.map((value) => formatClass(utilName, value))
746
829
 
747
830
  if (options?.supportsNegativeValues) {
831
+ // This is the normal negated version
832
+ // e.g. `-inset-1` or `-tw-inset-1`
748
833
  classes = [...classes, ...classes.map((cls) => '-' + cls)]
834
+
835
+ // This is the negated version *after* the prefix
836
+ // e.g. `tw--inset-1`
837
+ // The prefix is already attached to util name
838
+ // So we add the negative after the prefix
839
+ classes = [
840
+ ...classes,
841
+ ...classes.map(
842
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
843
+ ),
844
+ ]
845
+ }
846
+
847
+ if (options.types.some(({ type }) => type === 'color')) {
848
+ classes = [
849
+ ...classes,
850
+ ...classes.flatMap((cls) =>
851
+ Object.keys(context.tailwindConfig.theme.opacity).map(
852
+ (opacity) => `${cls}/${opacity}`
853
+ )
854
+ ),
855
+ ]
856
+ }
857
+
858
+ if (checkImportantUtils && options?.respectImportant) {
859
+ classes = [...classes, ...classes.map((cls) => '!' + cls)]
749
860
  }
750
861
 
751
862
  return classes
@@ -783,14 +894,56 @@ function registerPlugins(plugins, context) {
783
894
  log.warn([
784
895
  `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
785
896
  'Fix this pattern or remove it from your `safelist` configuration.',
897
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
786
898
  ])
787
899
  }
788
900
  }
789
901
  }
790
902
 
903
+ let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark'
904
+
905
+ // A list of utilities that are used by certain Tailwind CSS utilities but
906
+ // that don't exist on their own. This will result in them "not existing" and
907
+ // sorting could be weird since you still require them in order to make the
908
+ // host utilities work properly. (Thanks Biology)
909
+ let parasiteUtilities = [
910
+ prefix(context, darkClassName),
911
+ prefix(context, 'group'),
912
+ prefix(context, 'peer'),
913
+ ]
914
+ context.getClassOrder = function getClassOrder(classes) {
915
+ // Non-util classes won't be generated, so we default them to null
916
+ let sortedClassNames = new Map(classes.map((className) => [className, null]))
917
+
918
+ // Sort all classes in order
919
+ // Non-tailwind classes won't be generated and will be left as `null`
920
+ let rules = generateRules(new Set(classes), context)
921
+ rules = context.offsets.sort(rules)
922
+
923
+ let idx = BigInt(parasiteUtilities.length)
924
+
925
+ for (const [, rule] of rules) {
926
+ sortedClassNames.set(rule.raws.tailwind.candidate, idx++)
927
+ }
928
+
929
+ return classes.map((className) => {
930
+ let order = sortedClassNames.get(className) ?? null
931
+ let parasiteIndex = parasiteUtilities.indexOf(className)
932
+
933
+ if (order === null && parasiteIndex !== -1) {
934
+ // This will make sure that it is at the very beginning of the
935
+ // `components` layer which technically means 'before any
936
+ // components'.
937
+ order = BigInt(parasiteIndex)
938
+ }
939
+
940
+ return [className, order]
941
+ })
942
+ }
943
+
791
944
  // Generate a list of strings for autocompletion purposes, e.g.
792
945
  // ['uppercase', 'lowercase', ...]
793
- context.getClassList = function () {
946
+ context.getClassList = function getClassList() {
794
947
  let output = []
795
948
 
796
949
  for (let util of classList) {
@@ -799,6 +952,11 @@ function registerPlugins(plugins, context) {
799
952
  let negativeClasses = []
800
953
 
801
954
  for (let [key, value] of Object.entries(options?.values ?? {})) {
955
+ // Ignore undefined and null values
956
+ if (value == null) {
957
+ continue
958
+ }
959
+
802
960
  output.push(formatClass(utilName, key))
803
961
  if (options?.supportsNegativeValues && negateValue(value)) {
804
962
  negativeClasses.push(formatClass(utilName, `-${key}`))
@@ -813,21 +971,205 @@ function registerPlugins(plugins, context) {
813
971
 
814
972
  return output
815
973
  }
974
+
975
+ // Generate a list of available variants with meta information of the type of variant.
976
+ context.getVariants = function getVariants() {
977
+ let result = []
978
+ for (let [name, options] of context.variantOptions.entries()) {
979
+ if (options.variantInfo === VARIANT_INFO.Base) continue
980
+
981
+ result.push({
982
+ name,
983
+ isArbitrary: options.type === Symbol.for('MATCH_VARIANT'),
984
+ values: Object.keys(options.values ?? {}),
985
+ hasDash: name !== '@',
986
+ selectors({ modifier, value } = {}) {
987
+ let candidate = '__TAILWIND_PLACEHOLDER__'
988
+
989
+ let rule = postcss.rule({ selector: `.${candidate}` })
990
+ let container = postcss.root({ nodes: [rule.clone()] })
991
+
992
+ let before = container.toString()
993
+
994
+ let fns = (context.variantMap.get(name) ?? []).flatMap(([_, fn]) => fn)
995
+ let formatStrings = []
996
+ for (let fn of fns) {
997
+ let localFormatStrings = []
998
+
999
+ let api = {
1000
+ args: { modifier, value: options.values?.[value] ?? value },
1001
+ separator: context.tailwindConfig.separator,
1002
+ modifySelectors(modifierFunction) {
1003
+ // Run the modifierFunction over each rule
1004
+ container.each((rule) => {
1005
+ if (rule.type !== 'rule') {
1006
+ return
1007
+ }
1008
+
1009
+ rule.selectors = rule.selectors.map((selector) => {
1010
+ return modifierFunction({
1011
+ get className() {
1012
+ return getClassNameFromSelector(selector)
1013
+ },
1014
+ selector,
1015
+ })
1016
+ })
1017
+ })
1018
+
1019
+ return container
1020
+ },
1021
+ format(str) {
1022
+ localFormatStrings.push(str)
1023
+ },
1024
+ wrap(wrapper) {
1025
+ localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`)
1026
+ },
1027
+ container,
1028
+ }
1029
+
1030
+ let ruleWithVariant = fn(api)
1031
+ if (localFormatStrings.length > 0) {
1032
+ formatStrings.push(localFormatStrings)
1033
+ }
1034
+
1035
+ if (Array.isArray(ruleWithVariant)) {
1036
+ for (let variantFunction of ruleWithVariant) {
1037
+ localFormatStrings = []
1038
+ variantFunction(api)
1039
+ formatStrings.push(localFormatStrings)
1040
+ }
1041
+ }
1042
+ }
1043
+
1044
+ // Reverse engineer the result of the `container`
1045
+ let manualFormatStrings = []
1046
+ let after = container.toString()
1047
+
1048
+ if (before !== after) {
1049
+ // Figure out all selectors
1050
+ container.walkRules((rule) => {
1051
+ let modified = rule.selector
1052
+
1053
+ // Rebuild the base selector, this is what plugin authors would do
1054
+ // as well. E.g.: `${variant}${separator}${className}`.
1055
+ // However, plugin authors probably also prepend or append certain
1056
+ // classes, pseudos, ids, ...
1057
+ let rebuiltBase = selectorParser((selectors) => {
1058
+ selectors.walkClasses((classNode) => {
1059
+ classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}`
1060
+ })
1061
+ }).processSync(modified)
1062
+
1063
+ // Now that we know the original selector, the new selector, and
1064
+ // the rebuild part in between, we can replace the part that plugin
1065
+ // authors need to rebuild with `&`, and eventually store it in the
1066
+ // collectedFormats. Similar to what `format('...')` would do.
1067
+ //
1068
+ // E.g.:
1069
+ // variant: foo
1070
+ // selector: .markdown > p
1071
+ // modified (by plugin): .foo .foo\\:markdown > p
1072
+ // rebuiltBase (internal): .foo\\:markdown > p
1073
+ // format: .foo &
1074
+ manualFormatStrings.push(modified.replace(rebuiltBase, '&').replace(candidate, '&'))
1075
+ })
1076
+
1077
+ // Figure out all atrules
1078
+ container.walkAtRules((atrule) => {
1079
+ manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`)
1080
+ })
1081
+ }
1082
+
1083
+ let result = formatStrings.map((formatString) =>
1084
+ finalizeSelector(formatVariantSelector('&', ...formatString), {
1085
+ selector: `.${candidate}`,
1086
+ candidate,
1087
+ context,
1088
+ isArbitraryVariant: !(value in (options.values ?? {})),
1089
+ })
1090
+ .replace(`.${candidate}`, '&')
1091
+ .replace('{ & }', '')
1092
+ .trim()
1093
+ )
1094
+
1095
+ if (manualFormatStrings.length > 0) {
1096
+ result.push(formatVariantSelector('&', ...manualFormatStrings))
1097
+ }
1098
+
1099
+ return result
1100
+ },
1101
+ })
1102
+ }
1103
+
1104
+ return result
1105
+ }
1106
+ }
1107
+
1108
+ /**
1109
+ * Mark as class as retroactively invalid
1110
+ *
1111
+ *
1112
+ * @param {string} candidate
1113
+ */
1114
+ function markInvalidUtilityCandidate(context, candidate) {
1115
+ if (!context.classCache.has(candidate)) {
1116
+ return
1117
+ }
1118
+
1119
+ // Mark this as not being a real utility
1120
+ context.notClassCache.add(candidate)
1121
+
1122
+ // Remove it from any candidate-specific caches
1123
+ context.classCache.delete(candidate)
1124
+ context.applyClassCache.delete(candidate)
1125
+ context.candidateRuleMap.delete(candidate)
1126
+ context.candidateRuleCache.delete(candidate)
1127
+
1128
+ // Ensure the stylesheet gets rebuilt
1129
+ context.stylesheetCache = null
1130
+ }
1131
+
1132
+ /**
1133
+ * Mark as class as retroactively invalid
1134
+ *
1135
+ * @param {import('postcss').Node} node
1136
+ */
1137
+ function markInvalidUtilityNode(context, node) {
1138
+ let candidate = node.raws.tailwind.candidate
1139
+
1140
+ if (!candidate) {
1141
+ return
1142
+ }
1143
+
1144
+ for (const entry of context.ruleCache) {
1145
+ if (entry[1].raws.tailwind.candidate === candidate) {
1146
+ context.ruleCache.delete(entry)
1147
+ // context.postCssNodeCache.delete(node)
1148
+ }
1149
+ }
1150
+
1151
+ markInvalidUtilityCandidate(context, candidate)
816
1152
  }
817
1153
 
818
1154
  export function createContext(tailwindConfig, changedContent = [], root = postcss.root()) {
819
1155
  let context = {
820
1156
  disposables: [],
821
1157
  ruleCache: new Set(),
1158
+ candidateRuleCache: new Map(),
822
1159
  classCache: new Map(),
823
1160
  applyClassCache: new Map(),
824
- notClassCache: new Set(),
1161
+ // Seed the not class cache with the blocklist (which is only strings)
1162
+ notClassCache: new Set(tailwindConfig.blocklist ?? []),
825
1163
  postCssNodeCache: new Map(),
826
1164
  candidateRuleMap: new Map(),
827
1165
  tailwindConfig,
828
1166
  changedContent: changedContent,
829
1167
  variantMap: new Map(),
830
1168
  stylesheetCache: null,
1169
+ variantOptions: new Map(),
1170
+
1171
+ markInvalidUtilityCandidate: (candidate) => markInvalidUtilityCandidate(context, candidate),
1172
+ markInvalidUtilityNode: (node) => markInvalidUtilityNode(context, node),
831
1173
  }
832
1174
 
833
1175
  let resolvedPlugins = resolvePlugins(context, root)
@@ -865,6 +1207,8 @@ export function getContext(
865
1207
  existingContext = context
866
1208
  }
867
1209
 
1210
+ let cssDidChange = hasContentChanged(sourcePath, root)
1211
+
868
1212
  // If there's already a context in the cache and we don't need to
869
1213
  // reset the context, return the cached context.
870
1214
  if (existingContext) {
@@ -872,7 +1216,7 @@ export function getContext(
872
1216
  [...contextDependencies],
873
1217
  getFileModifiedMap(existingContext)
874
1218
  )
875
- if (!contextDependenciesChanged) {
1219
+ if (!contextDependenciesChanged && !cssDidChange) {
876
1220
  return [existingContext, false]
877
1221
  }
878
1222
  }
@@ -904,6 +1248,10 @@ export function getContext(
904
1248
 
905
1249
  let context = createContext(tailwindConfig, [], root)
906
1250
 
1251
+ Object.assign(context, {
1252
+ userConfigPath,
1253
+ })
1254
+
907
1255
  trackModified([...contextDependencies], getFileModifiedMap(context))
908
1256
 
909
1257
  // ---