tailwindcss 0.0.0-insiders.e2d5f21 → 0.0.0-insiders.e302ef1

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 (91) hide show
  1. package/CHANGELOG.md +244 -3
  2. package/LICENSE +1 -2
  3. package/README.md +8 -4
  4. package/colors.js +2 -1
  5. package/defaultConfig.js +2 -1
  6. package/defaultTheme.js +2 -1
  7. package/lib/cli.js +123 -88
  8. package/lib/corePlugins.js +288 -360
  9. package/lib/css/preflight.css +4 -4
  10. package/lib/featureFlags.js +4 -5
  11. package/lib/index.js +12 -3
  12. package/lib/lib/cacheInvalidation.js +69 -0
  13. package/lib/lib/collapseAdjacentRules.js +14 -1
  14. package/lib/lib/collapseDuplicateDeclarations.js +52 -1
  15. package/lib/lib/defaultExtractor.js +44 -0
  16. package/lib/lib/evaluateTailwindFunctions.js +1 -1
  17. package/lib/lib/expandApplyAtRules.js +265 -60
  18. package/lib/lib/expandTailwindAtRules.js +63 -48
  19. package/lib/lib/generateRules.js +136 -34
  20. package/lib/lib/normalizeTailwindDirectives.js +10 -2
  21. package/lib/lib/partitionApplyAtRules.js +53 -0
  22. package/lib/lib/resolveDefaultsAtRules.js +30 -12
  23. package/lib/lib/setupContextUtils.js +137 -81
  24. package/lib/lib/setupTrackingContext.js +8 -7
  25. package/lib/lib/sharedState.js +38 -5
  26. package/{nesting → lib/postcss-plugins/nesting}/README.md +0 -0
  27. package/lib/postcss-plugins/nesting/index.js +17 -0
  28. package/lib/postcss-plugins/nesting/plugin.js +85 -0
  29. package/lib/processTailwindFeatures.js +10 -2
  30. package/lib/util/cloneNodes.js +12 -1
  31. package/lib/util/color.js +23 -8
  32. package/lib/util/createPlugin.js +1 -2
  33. package/lib/util/createUtilityPlugin.js +4 -8
  34. package/lib/util/dataTypes.js +4 -2
  35. package/lib/util/defaults.js +6 -0
  36. package/lib/util/flattenColorPalette.js +1 -3
  37. package/lib/util/log.js +10 -6
  38. package/lib/util/normalizeConfig.js +47 -15
  39. package/lib/util/normalizeScreens.js +6 -5
  40. package/lib/util/parseBoxShadowValue.js +44 -4
  41. package/lib/util/pluginUtils.js +6 -13
  42. package/lib/util/prefixSelector.js +4 -5
  43. package/lib/util/resolveConfig.js +48 -27
  44. package/lib/util/resolveConfigPath.js +1 -2
  45. package/lib/util/toColorValue.js +1 -2
  46. package/lib/util/toPath.js +6 -1
  47. package/lib/util/transformThemeValue.js +4 -8
  48. package/nesting/index.js +2 -12
  49. package/package.json +28 -33
  50. package/peers/index.js +3209 -4239
  51. package/plugin.js +2 -1
  52. package/resolveConfig.js +2 -1
  53. package/src/cli.js +62 -15
  54. package/src/corePlugins.js +212 -217
  55. package/src/css/preflight.css +4 -4
  56. package/src/featureFlags.js +3 -3
  57. package/src/index.js +14 -6
  58. package/src/lib/cacheInvalidation.js +52 -0
  59. package/src/lib/collapseAdjacentRules.js +16 -1
  60. package/src/lib/collapseDuplicateDeclarations.js +66 -1
  61. package/src/lib/defaultExtractor.js +50 -0
  62. package/src/lib/evaluateTailwindFunctions.js +1 -1
  63. package/src/lib/expandApplyAtRules.js +285 -56
  64. package/src/lib/expandTailwindAtRules.js +75 -37
  65. package/src/lib/generateRules.js +121 -27
  66. package/src/lib/normalizeTailwindDirectives.js +7 -1
  67. package/src/lib/partitionApplyAtRules.js +52 -0
  68. package/src/lib/resolveDefaultsAtRules.js +35 -10
  69. package/src/lib/setupContextUtils.js +127 -45
  70. package/src/lib/setupTrackingContext.js +8 -7
  71. package/src/lib/sharedState.js +42 -4
  72. package/src/postcss-plugins/nesting/README.md +42 -0
  73. package/src/postcss-plugins/nesting/index.js +13 -0
  74. package/src/postcss-plugins/nesting/plugin.js +80 -0
  75. package/src/processTailwindFeatures.js +12 -2
  76. package/src/util/cloneNodes.js +14 -1
  77. package/src/util/color.js +20 -7
  78. package/src/util/dataTypes.js +7 -5
  79. package/src/util/defaults.js +6 -0
  80. package/src/util/log.js +10 -6
  81. package/src/util/normalizeConfig.js +24 -3
  82. package/src/util/normalizeScreens.js +6 -3
  83. package/src/util/parseBoxShadowValue.js +51 -3
  84. package/src/util/pluginUtils.js +1 -1
  85. package/src/util/prefixSelector.js +7 -5
  86. package/src/util/resolveConfig.js +41 -1
  87. package/src/util/toPath.js +23 -1
  88. package/stubs/defaultConfig.stub.js +2 -2
  89. package/lib/lib/setupWatchingContext.js +0 -288
  90. package/nesting/plugin.js +0 -41
  91. package/src/lib/setupWatchingContext.js +0 -311
@@ -1,13 +1,167 @@
1
1
  import postcss from 'postcss'
2
+ import parser from 'postcss-selector-parser'
3
+
2
4
  import { resolveMatches } from './generateRules'
3
5
  import bigSign from '../util/bigSign'
4
6
  import escapeClassName from '../util/escapeClassName'
5
7
 
8
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
9
+
10
+ function extractClasses(node) {
11
+ let classes = new Set()
12
+ let container = postcss.root({ nodes: [node.clone()] })
13
+
14
+ container.walkRules((rule) => {
15
+ parser((selectors) => {
16
+ selectors.walkClasses((classSelector) => {
17
+ classes.add(classSelector.value)
18
+ })
19
+ }).processSync(rule.selector)
20
+ })
21
+
22
+ return Array.from(classes)
23
+ }
24
+
25
+ function extractBaseCandidates(candidates, separator) {
26
+ let baseClasses = new Set()
27
+
28
+ for (let candidate of candidates) {
29
+ baseClasses.add(candidate.split(separator).pop())
30
+ }
31
+
32
+ return Array.from(baseClasses)
33
+ }
34
+
6
35
  function prefix(context, selector) {
7
36
  let prefix = context.tailwindConfig.prefix
8
37
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
9
38
  }
10
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
+ */
11
165
  function buildApplyCache(applyCandidates, context) {
12
166
  for (let candidate of applyCandidates) {
13
167
  if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
@@ -35,6 +189,43 @@ function buildApplyCache(applyCandidates, context) {
35
189
  return context.applyClassCache
36
190
  }
37
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
+
38
229
  function extractApplyCandidates(params) {
39
230
  let candidates = params.split(/[\s\t\n]+/g)
40
231
 
@@ -45,48 +236,7 @@ function extractApplyCandidates(params) {
45
236
  return [candidates, false]
46
237
  }
47
238
 
48
- function partitionApplyParents(root) {
49
- let applyParents = new Set()
50
-
51
- root.walkAtRules('apply', (rule) => {
52
- applyParents.add(rule.parent)
53
- })
54
-
55
- for (let rule of applyParents) {
56
- let nodeGroups = []
57
- let lastGroup = []
58
-
59
- for (let node of rule.nodes) {
60
- if (node.type === 'atrule' && node.name === 'apply') {
61
- if (lastGroup.length > 0) {
62
- nodeGroups.push(lastGroup)
63
- lastGroup = []
64
- }
65
- nodeGroups.push([node])
66
- } else {
67
- lastGroup.push(node)
68
- }
69
- }
70
-
71
- if (lastGroup.length > 0) {
72
- nodeGroups.push(lastGroup)
73
- }
74
-
75
- if (nodeGroups.length === 1) {
76
- continue
77
- }
78
-
79
- for (let group of [...nodeGroups].reverse()) {
80
- let newParent = rule.clone({ nodes: [] })
81
- newParent.append(group)
82
- rule.after(newParent)
83
- }
84
-
85
- rule.remove()
86
- }
87
- }
88
-
89
- function processApply(root, context) {
239
+ function processApply(root, context, localCache) {
90
240
  let applyCandidates = new Set()
91
241
 
92
242
  // Collect all @apply rules and candidates
@@ -104,7 +254,7 @@ function processApply(root, context) {
104
254
  // Start the @apply process if we have rules with @apply in them
105
255
  if (applies.length > 0) {
106
256
  // Fill up some caches!
107
- let applyClassCache = buildApplyCache(applyCandidates, context)
257
+ let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
108
258
 
109
259
  /**
110
260
  * When we have an apply like this:
@@ -129,10 +279,10 @@ function processApply(root, context) {
129
279
  // TODO: Should we use postcss-selector-parser for this instead?
130
280
  function replaceSelector(selector, utilitySelectors, candidate) {
131
281
  let needle = `.${escapeClassName(candidate)}`
132
- let utilitySelectorsList = utilitySelectors.split(/\s*,\s*/g)
282
+ let utilitySelectorsList = utilitySelectors.split(/\s*\,(?![^(]*\))\s*/g)
133
283
 
134
284
  return selector
135
- .split(/\s*,\s*/g)
285
+ .split(/\s*\,(?![^(]*\))\s*/g)
136
286
  .map((s) => {
137
287
  let replaced = []
138
288
 
@@ -154,7 +304,7 @@ function processApply(root, context) {
154
304
  for (let apply of applies) {
155
305
  let candidates = perParentApplies.get(apply.parent) || []
156
306
 
157
- perParentApplies.set(apply.parent, candidates)
307
+ perParentApplies.set(apply.parent, [candidates, apply.source])
158
308
 
159
309
  let [applyCandidates, important] = extractApplyCandidates(apply.params)
160
310
 
@@ -175,12 +325,12 @@ function processApply(root, context) {
175
325
  }
176
326
 
177
327
  for (let applyCandidate of applyCandidates) {
178
- if (!applyClassCache.has(applyCandidate)) {
179
- if (applyCandidate === prefix(context, 'group')) {
180
- // TODO: Link to specific documentation page with error code.
181
- throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
182
- }
328
+ if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
329
+ // TODO: Link to specific documentation page with error code.
330
+ throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
331
+ }
183
332
 
333
+ if (!applyClassCache.has(applyCandidate)) {
184
334
  throw apply.error(
185
335
  `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
186
336
  )
@@ -192,17 +342,95 @@ function processApply(root, context) {
192
342
  }
193
343
  }
194
344
 
195
- for (const [parent, candidates] of perParentApplies) {
345
+ for (const [parent, [candidates, atApplySource]] of perParentApplies) {
196
346
  let siblings = []
197
347
 
198
348
  for (let [applyCandidate, important, rules] of candidates) {
199
349
  for (let [meta, node] of rules) {
350
+ let parentClasses = extractClasses(parent)
351
+ let nodeClasses = extractClasses(node)
352
+
353
+ // Add base utility classes from the @apply node to the list of
354
+ // classes to check whether it intersects and therefore results in a
355
+ // circular dependency or not.
356
+ //
357
+ // E.g.:
358
+ // .foo {
359
+ // @apply hover:a; // This applies "a" but with a modifier
360
+ // }
361
+ //
362
+ // We only have to do that with base classes of the `node`, not of the `parent`
363
+ // E.g.:
364
+ // .hover\:foo {
365
+ // @apply bar;
366
+ // }
367
+ // .bar {
368
+ // @apply foo;
369
+ // }
370
+ //
371
+ // This should not result in a circular dependency because we are
372
+ // just applying `.foo` and the rule above is `.hover\:foo` which is
373
+ // unrelated. However, if we were to apply `hover:foo` then we _did_
374
+ // have to include this one.
375
+ nodeClasses = nodeClasses.concat(
376
+ extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
377
+ )
378
+
379
+ let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
380
+ if (intersects) {
381
+ throw node.error(
382
+ `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
383
+ )
384
+ }
385
+
200
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
+
201
393
  let canRewriteSelector =
202
394
  node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
203
395
 
204
396
  if (canRewriteSelector) {
205
397
  root.walkRules((rule) => {
398
+ // Let's imagine you have the following structure:
399
+ //
400
+ // .foo {
401
+ // @apply bar;
402
+ // }
403
+ //
404
+ // @supports (a: b) {
405
+ // .bar {
406
+ // color: blue
407
+ // }
408
+ //
409
+ // .something-unrelated {}
410
+ // }
411
+ //
412
+ // In this case we want to apply `.bar` but it happens to be in
413
+ // an atrule node. We clone that node instead of the nested one
414
+ // because we still want that @supports rule to be there once we
415
+ // applied everything.
416
+ //
417
+ // However it happens to be that the `.something-unrelated` is
418
+ // also in that same shared @supports atrule. This is not good,
419
+ // and this should not be there. The good part is that this is
420
+ // a clone already and it can be safely removed. The question is
421
+ // how do we know we can remove it. Basically what we can do is
422
+ // match it against the applyCandidate that you want to apply. If
423
+ // it doesn't match the we can safely delete it.
424
+ //
425
+ // If we didn't do this, then the `replaceSelector` function
426
+ // would have replaced this with something that didn't exist and
427
+ // therefore it removed the selector altogether. In this specific
428
+ // case it would result in `{}` instead of `.something-unrelated {}`
429
+ if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
430
+ rule.remove()
431
+ return
432
+ }
433
+
206
434
  rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)
207
435
 
208
436
  rule.walkDecls((d) => {
@@ -223,7 +451,6 @@ function processApply(root, context) {
223
451
  // Inject the rules, sorted, correctly
224
452
  let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
225
453
 
226
- // console.log(parent)
227
454
  // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
228
455
  parent.after(nodes)
229
456
  }
@@ -239,13 +466,15 @@ function processApply(root, context) {
239
466
  }
240
467
 
241
468
  // Do it again, in case we have other `@apply` rules
242
- processApply(root, context)
469
+ processApply(root, context, localCache)
243
470
  }
244
471
  }
245
472
 
246
473
  export default function expandApplyAtRules(context) {
247
474
  return (root) => {
248
- partitionApplyParents(root)
249
- 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)
250
479
  }
251
480
  }
@@ -2,34 +2,14 @@ import LRU from 'quick-lru'
2
2
  import * as sharedState from './sharedState'
3
3
  import { generateRules } from './generateRules'
4
4
  import bigSign from '../util/bigSign'
5
+ import log from '../util/log'
5
6
  import cloneNodes from '../util/cloneNodes'
7
+ import { defaultExtractor } from './defaultExtractor'
6
8
 
7
9
  let env = sharedState.env
8
10
 
9
- const PATTERNS = [
10
- /([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
11
- /([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
12
- /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
13
- /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
14
- /([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
15
- /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
16
- /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
17
- /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
18
- /([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
19
- /([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
20
- /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
21
- /([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
22
- ].join('|')
23
- const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
24
- const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
25
-
26
11
  const builtInExtractors = {
27
- DEFAULT: (content) => {
28
- let broadMatches = content.match(BROAD_MATCH_GLOBAL_REGEXP) || []
29
- let innerMatches = content.match(INNER_MATCH_GLOBAL_REGEXP) || []
30
-
31
- return [...broadMatches, ...innerMatches]
32
- },
12
+ DEFAULT: defaultExtractor,
33
13
  }
34
14
 
35
15
  const builtInTransformers = {
@@ -99,6 +79,7 @@ function buildStylesheet(rules, context) {
99
79
 
100
80
  let returnValue = {
101
81
  base: new Set(),
82
+ defaults: new Set(),
102
83
  components: new Set(),
103
84
  utilities: new Set(),
104
85
  variants: new Set(),
@@ -125,6 +106,11 @@ function buildStylesheet(rules, context) {
125
106
  continue
126
107
  }
127
108
 
109
+ if (sort & context.layerOrder.defaults) {
110
+ returnValue.defaults.add(rule)
111
+ continue
112
+ }
113
+
128
114
  if (sort & context.layerOrder.components) {
129
115
  returnValue.components.add(rule)
130
116
  continue
@@ -153,13 +139,15 @@ export default function expandTailwindAtRules(context) {
153
139
  variants: null,
154
140
  }
155
141
 
156
- // Make sure this file contains Tailwind directives. If not, we can save
157
- // a lot of work and bail early. Also we don't have to register our touch
158
- // file as a dependency since the output of this CSS does not depend on
159
- // the source of any templates. Think Vue <style> blocks for example.
160
- root.walkAtRules('tailwind', (rule) => {
161
- if (Object.keys(layerNodes).includes(rule.params)) {
162
- layerNodes[rule.params] = rule
142
+ root.walkAtRules((rule) => {
143
+ // Make sure this file contains Tailwind directives. If not, we can save
144
+ // a lot of work and bail early. Also we don't have to register our touch
145
+ // file as a dependency since the output of this CSS does not depend on
146
+ // the source of any templates. Think Vue <style> blocks for example.
147
+ if (rule.name === 'tailwind') {
148
+ if (Object.keys(layerNodes).includes(rule.params)) {
149
+ layerNodes[rule.params] = rule
150
+ }
163
151
  }
164
152
  })
165
153
 
@@ -170,7 +158,7 @@ export default function expandTailwindAtRules(context) {
170
158
  // ---
171
159
 
172
160
  // Find potential rules in changed files
173
- let candidates = new Set(['*'])
161
+ let candidates = new Set([sharedState.NOT_ON_DEMAND])
174
162
  let seen = new Set()
175
163
 
176
164
  env.DEBUG && console.time('Reading changed files')
@@ -181,6 +169,8 @@ export default function expandTailwindAtRules(context) {
181
169
  getClassCandidates(transformer(content), extractor, candidates, seen)
182
170
  }
183
171
 
172
+ env.DEBUG && console.timeEnd('Reading changed files')
173
+
184
174
  // ---
185
175
 
186
176
  // Generate the actual CSS
@@ -202,6 +192,7 @@ export default function expandTailwindAtRules(context) {
202
192
  env.DEBUG && console.timeEnd('Build stylesheet')
203
193
 
204
194
  let {
195
+ defaults: defaultNodes,
205
196
  base: baseNodes,
206
197
  components: componentNodes,
207
198
  utilities: utilityNodes,
@@ -213,25 +204,72 @@ export default function expandTailwindAtRules(context) {
213
204
  // Replace any Tailwind directives with generated CSS
214
205
 
215
206
  if (layerNodes.base) {
216
- layerNodes.base.before(cloneNodes([...baseNodes], layerNodes.base.source))
207
+ layerNodes.base.before(
208
+ cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
209
+ layer: 'base',
210
+ })
211
+ )
217
212
  layerNodes.base.remove()
218
213
  }
219
214
 
220
215
  if (layerNodes.components) {
221
- layerNodes.components.before(cloneNodes([...componentNodes], layerNodes.components.source))
216
+ layerNodes.components.before(
217
+ cloneNodes([...componentNodes], layerNodes.components.source, {
218
+ layer: 'components',
219
+ })
220
+ )
222
221
  layerNodes.components.remove()
223
222
  }
224
223
 
225
224
  if (layerNodes.utilities) {
226
- layerNodes.utilities.before(cloneNodes([...utilityNodes], layerNodes.utilities.source))
225
+ layerNodes.utilities.before(
226
+ cloneNodes([...utilityNodes], layerNodes.utilities.source, {
227
+ layer: 'utilities',
228
+ })
229
+ )
227
230
  layerNodes.utilities.remove()
228
231
  }
229
232
 
233
+ // We do post-filtering to not alter the emitted order of the variants
234
+ const variantNodes = Array.from(screenNodes).filter((node) => {
235
+ const parentLayer = node.raws.tailwind?.parentLayer
236
+
237
+ if (parentLayer === 'components') {
238
+ return layerNodes.components !== null
239
+ }
240
+
241
+ if (parentLayer === 'utilities') {
242
+ return layerNodes.utilities !== null
243
+ }
244
+
245
+ return true
246
+ })
247
+
230
248
  if (layerNodes.variants) {
231
- layerNodes.variants.before(cloneNodes([...screenNodes], layerNodes.variants.source))
249
+ layerNodes.variants.before(
250
+ cloneNodes(variantNodes, layerNodes.variants.source, {
251
+ layer: 'variants',
252
+ })
253
+ )
232
254
  layerNodes.variants.remove()
233
- } else {
234
- root.append(cloneNodes([...screenNodes], root.source))
255
+ } else if (variantNodes.length > 0) {
256
+ root.append(
257
+ cloneNodes(variantNodes, root.source, {
258
+ layer: 'variants',
259
+ })
260
+ )
261
+ }
262
+
263
+ // If we've got a utility layer and no utilities are generated there's likely something wrong
264
+ const hasUtilityVariants = variantNodes.some(
265
+ (node) => node.raws.tailwind?.parentLayer === 'utilities'
266
+ )
267
+
268
+ if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) {
269
+ log.warn('content-problems', [
270
+ 'No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.',
271
+ 'https://tailwindcss.com/docs/content-configuration',
272
+ ])
235
273
  }
236
274
 
237
275
  // ---