tailwindcss 3.0.23 → 3.0.24

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 (89) hide show
  1. package/CHANGELOG.md +29 -3
  2. package/lib/cli-peer-dependencies.js +3 -3
  3. package/lib/cli.js +183 -161
  4. package/lib/constants.js +8 -8
  5. package/lib/corePlugins.js +1572 -1523
  6. package/lib/featureFlags.js +9 -9
  7. package/lib/index.js +19 -6
  8. package/lib/lib/cacheInvalidation.js +69 -0
  9. package/lib/lib/collapseAdjacentRules.js +26 -13
  10. package/lib/lib/collapseDuplicateDeclarations.js +1 -1
  11. package/lib/lib/defaultExtractor.js +6 -6
  12. package/lib/lib/detectNesting.js +9 -9
  13. package/lib/lib/evaluateTailwindFunctions.js +16 -16
  14. package/lib/lib/expandApplyAtRules.js +180 -27
  15. package/lib/lib/expandTailwindAtRules.js +132 -122
  16. package/lib/lib/generateRules.js +90 -72
  17. package/lib/lib/getModuleDependencies.js +14 -14
  18. package/lib/lib/normalizeTailwindDirectives.js +35 -35
  19. package/lib/lib/partitionApplyAtRules.js +7 -7
  20. package/lib/lib/resolveDefaultsAtRules.js +81 -77
  21. package/lib/lib/setupContextUtils.js +78 -87
  22. package/lib/lib/setupTrackingContext.js +57 -57
  23. package/lib/lib/sharedState.js +10 -8
  24. package/lib/lib/substituteScreenAtRules.js +2 -2
  25. package/lib/postcss-plugins/nesting/README.md +2 -2
  26. package/lib/postcss-plugins/nesting/index.js +1 -1
  27. package/lib/postcss-plugins/nesting/plugin.js +41 -9
  28. package/lib/processTailwindFeatures.js +7 -7
  29. package/lib/public/colors.js +241 -241
  30. package/lib/public/resolve-config.js +5 -5
  31. package/lib/util/buildMediaQuery.js +2 -2
  32. package/lib/util/cloneDeep.js +1 -1
  33. package/lib/util/cloneNodes.js +12 -1
  34. package/lib/util/color.js +21 -20
  35. package/lib/util/createUtilityPlugin.js +6 -6
  36. package/lib/util/dataTypes.js +77 -75
  37. package/lib/util/escapeClassName.js +5 -5
  38. package/lib/util/escapeCommas.js +1 -1
  39. package/lib/util/flattenColorPalette.js +2 -2
  40. package/lib/util/formatVariantSelector.js +19 -19
  41. package/lib/util/getAllConfigs.js +5 -5
  42. package/lib/util/hashConfig.js +5 -5
  43. package/lib/util/isKeyframeRule.js +1 -1
  44. package/lib/util/isPlainObject.js +1 -1
  45. package/lib/util/isValidArbitraryValue.js +27 -27
  46. package/lib/util/log.js +8 -8
  47. package/lib/util/nameClass.js +7 -7
  48. package/lib/util/negateValue.js +4 -4
  49. package/lib/util/normalizeConfig.js +39 -39
  50. package/lib/util/normalizeScreens.js +4 -4
  51. package/lib/util/parseAnimationValue.js +56 -56
  52. package/lib/util/parseBoxShadowValue.js +60 -20
  53. package/lib/util/parseDependency.js +32 -32
  54. package/lib/util/parseObjectStyles.js +6 -6
  55. package/lib/util/pluginUtils.js +9 -9
  56. package/lib/util/prefixSelector.js +1 -1
  57. package/lib/util/resolveConfig.js +28 -28
  58. package/lib/util/resolveConfigPath.js +16 -16
  59. package/lib/util/responsive.js +6 -6
  60. package/lib/util/toColorValue.js +1 -1
  61. package/lib/util/toPath.js +2 -2
  62. package/lib/util/transformThemeValue.js +27 -27
  63. package/lib/util/withAlphaVariable.js +19 -19
  64. package/package.json +24 -23
  65. package/peers/index.js +4777 -4831
  66. package/scripts/generate-types.js +52 -0
  67. package/src/cli.js +41 -11
  68. package/src/corePlugins.js +67 -5
  69. package/src/featureFlags.js +2 -2
  70. package/src/index.js +17 -1
  71. package/src/lib/cacheInvalidation.js +52 -0
  72. package/src/lib/collapseAdjacentRules.js +16 -1
  73. package/src/lib/defaultExtractor.js +4 -4
  74. package/src/lib/expandApplyAtRules.js +179 -6
  75. package/src/lib/expandTailwindAtRules.js +25 -5
  76. package/src/lib/generateRules.js +68 -46
  77. package/src/lib/resolveDefaultsAtRules.js +6 -2
  78. package/src/lib/setupContextUtils.js +25 -26
  79. package/src/lib/setupTrackingContext.js +3 -3
  80. package/src/lib/sharedState.js +1 -0
  81. package/src/postcss-plugins/nesting/README.md +2 -2
  82. package/src/postcss-plugins/nesting/plugin.js +36 -0
  83. package/src/util/cloneNodes.js +14 -1
  84. package/src/util/color.js +7 -5
  85. package/src/util/dataTypes.js +3 -1
  86. package/src/util/log.js +7 -7
  87. package/src/util/parseBoxShadowValue.js +50 -2
  88. package/src/util/resolveConfig.js +32 -0
  89. package/stubs/defaultConfig.stub.js +5 -0
@@ -5,6 +5,8 @@ import { resolveMatches } from './generateRules'
5
5
  import bigSign from '../util/bigSign'
6
6
  import escapeClassName from '../util/escapeClassName'
7
7
 
8
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
9
+
8
10
  function extractClasses(node) {
9
11
  let classes = new Set()
10
12
  let container = postcss.root({ nodes: [node.clone()] })
@@ -35,6 +37,131 @@ function prefix(context, selector) {
35
37
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
36
38
  }
37
39
 
40
+ function* pathToRoot(node) {
41
+ yield node
42
+ while (node.parent) {
43
+ yield node.parent
44
+ node = node.parent
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Only clone the node itself and not its children
50
+ *
51
+ * @param {*} node
52
+ * @param {*} overrides
53
+ * @returns
54
+ */
55
+ function shallowClone(node, overrides = {}) {
56
+ let children = node.nodes
57
+ node.nodes = []
58
+
59
+ let tmp = node.clone(overrides)
60
+
61
+ node.nodes = children
62
+
63
+ return tmp
64
+ }
65
+
66
+ /**
67
+ * Clone just the nodes all the way to the top that are required to represent
68
+ * this singular rule in the tree.
69
+ *
70
+ * For example, if we have CSS like this:
71
+ * ```css
72
+ * @media (min-width: 768px) {
73
+ * @supports (display: grid) {
74
+ * .foo {
75
+ * display: grid;
76
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
77
+ * }
78
+ * }
79
+ *
80
+ * @supports (backdrop-filter: blur(1px)) {
81
+ * .bar {
82
+ * backdrop-filter: blur(1px);
83
+ * }
84
+ * }
85
+ *
86
+ * .baz {
87
+ * color: orange;
88
+ * }
89
+ * }
90
+ * ```
91
+ *
92
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
93
+ *
94
+ * ```css
95
+ * @media (min-width: 768px) {
96
+ * @supports (backdrop-filter: blur(1px)) {
97
+ * .bar {
98
+ * backdrop-filter: blur(1px);
99
+ * }
100
+ * }
101
+ * }
102
+ * ```
103
+ *
104
+ * @param {import('postcss').Node} node
105
+ */
106
+ function nestedClone(node) {
107
+ for (let parent of pathToRoot(node)) {
108
+ if (node === parent) {
109
+ continue
110
+ }
111
+
112
+ if (parent.type === 'root') {
113
+ break
114
+ }
115
+
116
+ node = shallowClone(parent, {
117
+ nodes: [node],
118
+ })
119
+ }
120
+
121
+ return node
122
+ }
123
+
124
+ /**
125
+ * @param {import('postcss').Root} root
126
+ */
127
+ function buildLocalApplyCache(root, context) {
128
+ /** @type {ApplyCache} */
129
+ let cache = new Map()
130
+
131
+ let highestOffset = context.layerOrder.user >> 4n
132
+
133
+ root.walkRules((rule, idx) => {
134
+ // Ignore rules generated by Tailwind
135
+ for (let node of pathToRoot(rule)) {
136
+ if (node.raws.tailwind?.layer !== undefined) {
137
+ return
138
+ }
139
+ }
140
+
141
+ // Clone what's required to represent this singular rule in the tree
142
+ let container = nestedClone(rule)
143
+
144
+ for (let className of extractClasses(rule)) {
145
+ let list = cache.get(className) || []
146
+ cache.set(className, list)
147
+
148
+ list.push([
149
+ {
150
+ layer: 'user',
151
+ sort: BigInt(idx) + highestOffset,
152
+ important: false,
153
+ },
154
+ container,
155
+ ])
156
+ }
157
+ })
158
+
159
+ return cache
160
+ }
161
+
162
+ /**
163
+ * @returns {ApplyCache}
164
+ */
38
165
  function buildApplyCache(applyCandidates, context) {
39
166
  for (let candidate of applyCandidates) {
40
167
  if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
@@ -62,6 +189,43 @@ function buildApplyCache(applyCandidates, context) {
62
189
  return context.applyClassCache
63
190
  }
64
191
 
192
+ /**
193
+ * Build a cache only when it's first used
194
+ *
195
+ * @param {() => ApplyCache} buildCacheFn
196
+ * @returns {ApplyCache}
197
+ */
198
+ function lazyCache(buildCacheFn) {
199
+ let cache = null
200
+
201
+ return {
202
+ get: (name) => {
203
+ cache = cache || buildCacheFn()
204
+
205
+ return cache.get(name)
206
+ },
207
+ has: (name) => {
208
+ cache = cache || buildCacheFn()
209
+
210
+ return cache.has(name)
211
+ },
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Take a series of multiple caches and merge
217
+ * them so they act like one large cache
218
+ *
219
+ * @param {ApplyCache[]} caches
220
+ * @returns {ApplyCache}
221
+ */
222
+ function combineCaches(caches) {
223
+ return {
224
+ get: (name) => caches.flatMap((cache) => cache.get(name) || []),
225
+ has: (name) => caches.some((cache) => cache.has(name)),
226
+ }
227
+ }
228
+
65
229
  function extractApplyCandidates(params) {
66
230
  let candidates = params.split(/[\s\t\n]+/g)
67
231
 
@@ -72,7 +236,7 @@ function extractApplyCandidates(params) {
72
236
  return [candidates, false]
73
237
  }
74
238
 
75
- function processApply(root, context) {
239
+ function processApply(root, context, localCache) {
76
240
  let applyCandidates = new Set()
77
241
 
78
242
  // Collect all @apply rules and candidates
@@ -90,7 +254,7 @@ function processApply(root, context) {
90
254
  // Start the @apply process if we have rules with @apply in them
91
255
  if (applies.length > 0) {
92
256
  // Fill up some caches!
93
- let applyClassCache = buildApplyCache(applyCandidates, context)
257
+ let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
94
258
 
95
259
  /**
96
260
  * When we have an apply like this:
@@ -140,7 +304,7 @@ function processApply(root, context) {
140
304
  for (let apply of applies) {
141
305
  let candidates = perParentApplies.get(apply.parent) || []
142
306
 
143
- perParentApplies.set(apply.parent, candidates)
307
+ perParentApplies.set(apply.parent, [candidates, apply.source])
144
308
 
145
309
  let [applyCandidates, important] = extractApplyCandidates(apply.params)
146
310
 
@@ -178,7 +342,7 @@ function processApply(root, context) {
178
342
  }
179
343
  }
180
344
 
181
- for (const [parent, candidates] of perParentApplies) {
345
+ for (const [parent, [candidates, atApplySource]] of perParentApplies) {
182
346
  let siblings = []
183
347
 
184
348
  for (let [applyCandidate, important, rules] of candidates) {
@@ -220,6 +384,12 @@ function processApply(root, context) {
220
384
  }
221
385
 
222
386
  let root = postcss.root({ nodes: [node.clone()] })
387
+
388
+ // Make sure every node in the entire tree points back at the @apply rule that generated it
389
+ root.walk((node) => {
390
+ node.source = atApplySource
391
+ })
392
+
223
393
  let canRewriteSelector =
224
394
  node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
225
395
 
@@ -296,12 +466,15 @@ function processApply(root, context) {
296
466
  }
297
467
 
298
468
  // Do it again, in case we have other `@apply` rules
299
- processApply(root, context)
469
+ processApply(root, context, localCache)
300
470
  }
301
471
  }
302
472
 
303
473
  export default function expandApplyAtRules(context) {
304
474
  return (root) => {
305
- processApply(root, context)
475
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
476
+ let localCache = lazyCache(() => buildLocalApplyCache(root, context))
477
+
478
+ processApply(root, context, localCache)
306
479
  }
307
480
  }
@@ -204,17 +204,29 @@ export default function expandTailwindAtRules(context) {
204
204
  // Replace any Tailwind directives with generated CSS
205
205
 
206
206
  if (layerNodes.base) {
207
- layerNodes.base.before(cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source))
207
+ layerNodes.base.before(
208
+ cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
209
+ layer: 'base',
210
+ })
211
+ )
208
212
  layerNodes.base.remove()
209
213
  }
210
214
 
211
215
  if (layerNodes.components) {
212
- layerNodes.components.before(cloneNodes([...componentNodes], layerNodes.components.source))
216
+ layerNodes.components.before(
217
+ cloneNodes([...componentNodes], layerNodes.components.source, {
218
+ layer: 'components',
219
+ })
220
+ )
213
221
  layerNodes.components.remove()
214
222
  }
215
223
 
216
224
  if (layerNodes.utilities) {
217
- layerNodes.utilities.before(cloneNodes([...utilityNodes], layerNodes.utilities.source))
225
+ layerNodes.utilities.before(
226
+ cloneNodes([...utilityNodes], layerNodes.utilities.source, {
227
+ layer: 'utilities',
228
+ })
229
+ )
218
230
  layerNodes.utilities.remove()
219
231
  }
220
232
 
@@ -234,10 +246,18 @@ export default function expandTailwindAtRules(context) {
234
246
  })
235
247
 
236
248
  if (layerNodes.variants) {
237
- layerNodes.variants.before(cloneNodes(variantNodes, layerNodes.variants.source))
249
+ layerNodes.variants.before(
250
+ cloneNodes(variantNodes, layerNodes.variants.source, {
251
+ layer: 'variants',
252
+ })
253
+ )
238
254
  layerNodes.variants.remove()
239
255
  } else if (variantNodes.length > 0) {
240
- root.append(cloneNodes(variantNodes, root.source))
256
+ root.append(
257
+ cloneNodes(variantNodes, root.source, {
258
+ layer: 'variants',
259
+ })
260
+ )
241
261
  }
242
262
 
243
263
  // If we've got a utility layer and no utilities are generated there's likely something wrong
@@ -88,7 +88,7 @@ function applyPrefix(matches, context) {
88
88
  return matches
89
89
  }
90
90
 
91
- function applyImportant(matches) {
91
+ function applyImportant(matches, classCandidate) {
92
92
  if (matches.length === 0) {
93
93
  return matches
94
94
  }
@@ -98,7 +98,10 @@ function applyImportant(matches) {
98
98
  let container = postcss.root({ nodes: [rule.clone()] })
99
99
  container.walkRules((r) => {
100
100
  r.selector = updateAllClasses(r.selector, (className) => {
101
- return `!${className}`
101
+ if (className === classCandidate) {
102
+ return `!${className}`
103
+ }
104
+ return className
102
105
  })
103
106
  r.walkDecls((d) => (d.important = true))
104
107
  })
@@ -300,6 +303,19 @@ function looksLikeUri(declaration) {
300
303
  }
301
304
  }
302
305
 
306
+ function isParsableNode(node) {
307
+ let isParsable = true
308
+
309
+ node.walkDecls((decl) => {
310
+ if (!isParsableCssValue(decl.name, decl.value)) {
311
+ isParsable = false
312
+ return false
313
+ }
314
+ })
315
+
316
+ return isParsable
317
+ }
318
+
303
319
  function isParsableCssValue(property, value) {
304
320
  // We don't want to to treat [https://example.com] as a custom property
305
321
  // Even though, according to the CSS grammar, it's a totally valid CSS declaration
@@ -453,60 +469,66 @@ function* resolveMatches(candidate, context) {
453
469
  }
454
470
  }
455
471
 
456
- // Only keep the result of the very first plugin if we are dealing with
457
- // arbitrary values, to protect against ambiguity.
458
- if (isArbitraryValue(modifier) && matches.length > 1) {
459
- let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
472
+ if (isArbitraryValue(modifier)) {
473
+ // When generated arbitrary values are ambiguous, we can't know
474
+ // which to pick so don't generate any utilities for them
475
+ if (matches.length > 1) {
476
+ let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
460
477
 
461
- // Remove duplicates, so that we can detect proper unique types for each plugin.
462
- for (let pluginTypes of typesPerPlugin) {
463
- for (let type of pluginTypes) {
464
- let removeFromOwnGroup = false
478
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
479
+ for (let pluginTypes of typesPerPlugin) {
480
+ for (let type of pluginTypes) {
481
+ let removeFromOwnGroup = false
465
482
 
466
- for (let otherGroup of typesPerPlugin) {
467
- if (pluginTypes === otherGroup) continue
483
+ for (let otherGroup of typesPerPlugin) {
484
+ if (pluginTypes === otherGroup) continue
468
485
 
469
- if (otherGroup.has(type)) {
470
- otherGroup.delete(type)
471
- removeFromOwnGroup = true
486
+ if (otherGroup.has(type)) {
487
+ otherGroup.delete(type)
488
+ removeFromOwnGroup = true
489
+ }
472
490
  }
473
- }
474
491
 
475
- if (removeFromOwnGroup) pluginTypes.delete(type)
492
+ if (removeFromOwnGroup) pluginTypes.delete(type)
493
+ }
476
494
  }
477
- }
478
495
 
479
- let messages = []
480
-
481
- for (let [idx, group] of typesPerPlugin.entries()) {
482
- for (let type of group) {
483
- let rules = matches[idx]
484
- .map(([, rule]) => rule)
485
- .flat()
486
- .map((rule) =>
487
- rule
488
- .toString()
489
- .split('\n')
490
- .slice(1, -1) // Remove selector and closing '}'
491
- .map((line) => line.trim())
492
- .map((x) => ` ${x}`) // Re-indent
493
- .join('\n')
496
+ let messages = []
497
+
498
+ for (let [idx, group] of typesPerPlugin.entries()) {
499
+ for (let type of group) {
500
+ let rules = matches[idx]
501
+ .map(([, rule]) => rule)
502
+ .flat()
503
+ .map((rule) =>
504
+ rule
505
+ .toString()
506
+ .split('\n')
507
+ .slice(1, -1) // Remove selector and closing '}'
508
+ .map((line) => line.trim())
509
+ .map((x) => ` ${x}`) // Re-indent
510
+ .join('\n')
511
+ )
512
+ .join('\n\n')
513
+
514
+ messages.push(
515
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
494
516
  )
495
- .join('\n\n')
496
-
497
- messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
498
- break
517
+ break
518
+ }
499
519
  }
520
+
521
+ log.warn([
522
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
523
+ ...messages,
524
+ `If this is content and not a class, replace it with \`${candidate
525
+ .replace('[', '&lsqb;')
526
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
527
+ ])
528
+ continue
500
529
  }
501
530
 
502
- log.warn([
503
- `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
504
- ...messages,
505
- `If this is content and not a class, replace it with \`${candidate
506
- .replace('[', '&lsqb;')
507
- .replace(']', '&rsqb;')}\` to silence this warning.`,
508
- ])
509
- continue
531
+ matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
510
532
  }
511
533
 
512
534
  matches = matches.flat()
@@ -514,7 +536,7 @@ function* resolveMatches(candidate, context) {
514
536
  matches = applyPrefix(matches, context)
515
537
 
516
538
  if (important) {
517
- matches = applyImportant(matches, context)
539
+ matches = applyImportant(matches, classCandidate)
518
540
  }
519
541
 
520
542
  for (let variant of variants) {
@@ -120,7 +120,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
120
120
  }
121
121
 
122
122
  for (let [, selectors] of selectorGroups) {
123
- let universalRule = postcss.rule()
123
+ let universalRule = postcss.rule({
124
+ source: universal.source,
125
+ })
124
126
 
125
127
  universalRule.selectors = [...selectors]
126
128
 
@@ -128,7 +130,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
128
130
  universal.before(universalRule)
129
131
  }
130
132
  } else {
131
- let universalRule = postcss.rule()
133
+ let universalRule = postcss.rule({
134
+ source: universal.source,
135
+ })
132
136
 
133
137
  universalRule.selectors = ['*', '::before', '::after']
134
138
 
@@ -20,6 +20,7 @@ import log from '../util/log'
20
20
  import negateValue from '../util/negateValue'
21
21
  import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
22
  import { generateRules } from './generateRules'
23
+ import { hasContentChanged } from './cacheInvalidation.js'
23
24
 
24
25
  function prefix(context, selector) {
25
26
  let prefix = context.tailwindConfig.prefix
@@ -84,12 +85,18 @@ function parseStyles(styles) {
84
85
  })
85
86
  }
86
87
 
87
- function getClasses(selector) {
88
+ function getClasses(selector, mutate) {
88
89
  let parser = selectorParser((selectors) => {
89
90
  let allClasses = []
91
+
92
+ if (mutate) {
93
+ mutate(selectors)
94
+ }
95
+
90
96
  selectors.walkClasses((classNode) => {
91
97
  allClasses.push(classNode.value)
92
98
  })
99
+
93
100
  return allClasses
94
101
  })
95
102
  return parser.transformSync(selector)
@@ -100,8 +107,20 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
100
107
 
101
108
  // Handle normal rules
102
109
  if (node.type === 'rule') {
110
+ // Ignore everything inside a :not(...). This allows you to write code like
111
+ // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
112
+ // not generated it. But now we will ignore everything inside a `:not`, so
113
+ // that it still gets generated.
114
+ function ignoreNot(selectors) {
115
+ selectors.walkPseudos((pseudo) => {
116
+ if (pseudo.value === ':not') {
117
+ pseudo.remove()
118
+ }
119
+ })
120
+ }
121
+
103
122
  for (let selector of node.selectors) {
104
- let classCandidates = getClasses(selector)
123
+ let classCandidates = getClasses(selector, ignoreNot)
105
124
  // At least one of the selectors contains non-"on-demandable" candidates.
106
125
  if (classCandidates.length === 0) {
107
126
  state.containsNonOnDemandable = true
@@ -116,9 +135,7 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
116
135
  // Handle at-rules (which contains nested rules)
117
136
  else if (node.type === 'atrule') {
118
137
  node.walkRules((rule) => {
119
- for (let classCandidate of rule.selectors.flatMap((selector) =>
120
- getClasses(selector, state, depth + 1)
121
- )) {
138
+ for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
122
139
  classes.push(classCandidate)
123
140
  }
124
141
  })
@@ -230,17 +247,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
247
  // Preserved for backwards compatibility but not used in v3.0+
231
248
  return []
232
249
  },
233
- addUserCss(userCss) {
234
- for (let [identifier, rule] of withIdentifiers(userCss)) {
235
- let offset = offsets.user++
236
-
237
- if (!context.candidateRuleMap.has(identifier)) {
238
- context.candidateRuleMap.set(identifier, [])
239
- }
240
-
241
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
242
- }
243
- },
244
250
  addBase(base) {
245
251
  for (let [identifier, rule] of withIdentifiers(base)) {
246
252
  let prefixedIdentifier = prefixIdentifier(identifier, {})
@@ -521,15 +527,6 @@ function collectLayerPlugins(root) {
521
527
  }
522
528
  })
523
529
 
524
- root.walkRules((rule) => {
525
- // At this point it is safe to include all the left-over css from the
526
- // user's css file. This is because the `@tailwind` and `@layer` directives
527
- // will already be handled and will be removed from the css tree.
528
- layerPlugins.push(function ({ addUserCss }) {
529
- addUserCss(rule, { respectPrefix: false })
530
- })
531
- })
532
-
533
530
  return layerPlugins
534
531
  }
535
532
 
@@ -842,6 +839,8 @@ export function getContext(
842
839
  existingContext = context
843
840
  }
844
841
 
842
+ let cssDidChange = hasContentChanged(sourcePath, root)
843
+
845
844
  // If there's already a context in the cache and we don't need to
846
845
  // reset the context, return the cached context.
847
846
  if (existingContext) {
@@ -849,7 +848,7 @@ export function getContext(
849
848
  [...contextDependencies],
850
849
  getFileModifiedMap(existingContext)
851
850
  )
852
- if (!contextDependenciesChanged) {
851
+ if (!contextDependenciesChanged && !cssDidChange) {
853
852
  return [existingContext, false]
854
853
  }
855
854
  }
@@ -112,7 +112,7 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
112
112
  // source path), or set up a new one (including setting up watchers and registering
113
113
  // plugins) then return it
114
114
  export default function setupTrackingContext(configOrPath) {
115
- return ({ tailwindDirectives, registerDependency, applyDirectives }) => {
115
+ return ({ tailwindDirectives, registerDependency }) => {
116
116
  return (root, result) => {
117
117
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
118
118
  getTailwindConfig(configOrPath)
@@ -125,7 +125,7 @@ export default function setupTrackingContext(configOrPath) {
125
125
  // being part of this trigger too, but it's tough because it's impossible
126
126
  // for a layer in one file to end up in the actual @tailwind rule in
127
127
  // another file since independent sources are effectively isolated.
128
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
128
+ if (tailwindDirectives.size > 0) {
129
129
  // Add current css file as a context dependencies.
130
130
  contextDependencies.add(result.opts.from)
131
131
 
@@ -153,7 +153,7 @@ export default function setupTrackingContext(configOrPath) {
153
153
  // We may want to think about `@layer` being part of this trigger too, but it's tough
154
154
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
155
155
  // in another file since independent sources are effectively isolated.
156
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
156
+ if (tailwindDirectives.size > 0) {
157
157
  let fileModifiedMap = getFileModifiedMap(context)
158
158
 
159
159
  // Add template paths as postcss dependencies.
@@ -5,6 +5,7 @@ export const env = {
5
5
  export const contextMap = new Map()
6
6
  export const configContextMap = new Map()
7
7
  export const contextSourcesMap = new Map()
8
+ export const sourceHashMap = new Map()
8
9
  export const NOT_ON_DEMAND = new String('*')
9
10
 
10
11
  export function resolveDebug(debug) {
@@ -1,6 +1,6 @@
1
1
  # tailwindcss/nesting
2
2
 
3
- This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`.
3
+ This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`.
4
4
 
5
5
  Add it to your PostCSS configuration, somewhere before Tailwind itself:
6
6
 
@@ -18,7 +18,7 @@ module.exports = {
18
18
 
19
19
  By default, it uses the [postcss-nested](https://github.com/postcss/postcss-nested) plugin under the hood, which uses a Sass-like syntax and is the plugin that powers nesting support in the [Tailwind CSS plugin API](https://tailwindcss.com/docs/plugins#css-in-js-syntax).
20
20
 
21
- If you'd rather use [postcss-nesting](https://github.com/jonathantneal/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside:
21
+ If you'd rather use [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside:
22
22
 
23
23
  ```shell
24
24
  npm install postcss-nesting
@@ -39,6 +39,42 @@ export function nesting(opts = postcssNested) {
39
39
  decl.remove()
40
40
  })
41
41
 
42
+ /**
43
+ * Use a private PostCSS API to remove the "clean" flag from the entire AST.
44
+ * This is done because running process() on the AST will set the "clean"
45
+ * flag on all nodes, which we don't want.
46
+ *
47
+ * This causes downstream plugins using the visitor API to be skipped.
48
+ *
49
+ * This is guarded because the PostCSS API is not public
50
+ * and may change in future versions of PostCSS.
51
+ *
52
+ * See https://github.com/postcss/postcss/issues/1712 for more details
53
+ *
54
+ * @param {import('postcss').Node} node
55
+ */
56
+ function markDirty(node) {
57
+ if (!('markDirty' in node)) {
58
+ return
59
+ }
60
+
61
+ // Traverse the tree down to the leaf nodes
62
+ if (node.nodes) {
63
+ node.nodes.forEach((n) => markDirty(n))
64
+ }
65
+
66
+ // If it's a leaf node mark it as dirty
67
+ // We do this here because marking a node as dirty
68
+ // will walk up the tree and mark all parents as dirty
69
+ // resulting in a lot of unnecessary work if we did this
70
+ // for every single node
71
+ if (!node.nodes) {
72
+ node.markDirty()
73
+ }
74
+ }
75
+
76
+ markDirty(root)
77
+
42
78
  return root
43
79
  }
44
80
  }