tailwindcss 3.0.8 → 3.0.12

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 (37) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/lib/cli.js +49 -12
  3. package/lib/corePlugins.js +114 -142
  4. package/lib/css/preflight.css +1 -1
  5. package/lib/featureFlags.js +1 -3
  6. package/lib/index.js +1 -3
  7. package/lib/lib/collapseDuplicateDeclarations.js +52 -1
  8. package/lib/lib/defaultExtractor.js +2 -0
  9. package/lib/lib/expandApplyAtRules.js +0 -40
  10. package/lib/lib/expandTailwindAtRules.js +12 -25
  11. package/lib/lib/normalizeTailwindDirectives.js +8 -1
  12. package/lib/lib/resolveDefaultsAtRules.js +4 -4
  13. package/lib/lib/setupContextUtils.js +131 -69
  14. package/lib/lib/setupTrackingContext.js +11 -10
  15. package/lib/lib/sharedState.js +33 -4
  16. package/lib/processTailwindFeatures.js +3 -2
  17. package/lib/util/pluginUtils.js +1 -1
  18. package/package.json +7 -8
  19. package/peers/index.js +606 -606
  20. package/src/cli.js +57 -12
  21. package/src/corePlugins.js +121 -155
  22. package/src/css/preflight.css +1 -1
  23. package/src/featureFlags.js +1 -5
  24. package/src/index.js +1 -7
  25. package/src/lib/collapseDuplicateDeclarations.js +66 -1
  26. package/src/lib/defaultExtractor.js +2 -0
  27. package/src/lib/expandApplyAtRules.js +0 -42
  28. package/src/lib/expandTailwindAtRules.js +12 -21
  29. package/src/lib/normalizeTailwindDirectives.js +6 -1
  30. package/src/lib/resolveDefaultsAtRules.js +5 -5
  31. package/src/lib/setupContextUtils.js +108 -19
  32. package/src/lib/setupTrackingContext.js +11 -10
  33. package/src/lib/sharedState.js +40 -4
  34. package/src/processTailwindFeatures.js +4 -2
  35. package/src/util/pluginUtils.js +1 -1
  36. package/lib/lib/setupWatchingContext.js +0 -288
  37. package/src/lib/setupWatchingContext.js +0 -311
@@ -9,7 +9,7 @@
9
9
  box-sizing: border-box; /* 1 */
10
10
  border-width: 0; /* 2 */
11
11
  border-style: solid; /* 2 */
12
- border-color: currentColor; /* 2 */
12
+ border-color: theme('borderColor.DEFAULT', 'currentColor'); /* 2 */
13
13
  }
14
14
 
15
15
  ::before,
@@ -2,11 +2,7 @@ import chalk from 'chalk'
2
2
  import log from './util/log'
3
3
 
4
4
  let defaults = {
5
- // TODO: Drop this once we can safely rely on optimizeUniversalDefaults being
6
- // the default.
7
- optimizeUniversalDefaults: process.env.NODE_ENV === 'test' ? true : false,
8
-
9
- // optimizeUniversalDefaults: true
5
+ optimizeUniversalDefaults: false,
10
6
  }
11
7
 
12
8
  let featureFlags = {
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import setupTrackingContext from './lib/setupTrackingContext'
2
- import setupWatchingContext from './lib/setupWatchingContext'
3
2
  import processTailwindFeatures from './processTailwindFeatures'
4
3
  import { env } from './lib/sharedState'
5
4
 
@@ -14,12 +13,7 @@ module.exports = function tailwindcss(configOrPath) {
14
13
  return root
15
14
  },
16
15
  function (root, result) {
17
- let setupContext =
18
- env.TAILWIND_MODE === 'watch'
19
- ? setupWatchingContext(configOrPath)
20
- : setupTrackingContext(configOrPath)
21
-
22
- processTailwindFeatures(setupContext)(root, result)
16
+ processTailwindFeatures(setupTrackingContext(configOrPath))(root, result)
23
17
  },
24
18
  env.DEBUG &&
25
19
  function (root) {
@@ -3,6 +3,7 @@ export default function collapseDuplicateDeclarations() {
3
3
  root.walkRules((node) => {
4
4
  let seen = new Map()
5
5
  let droppable = new Set([])
6
+ let byProperty = new Map()
6
7
 
7
8
  node.walkDecls((decl) => {
8
9
  // This could happen if we have nested selectors. In that case the
@@ -14,15 +15,79 @@ export default function collapseDuplicateDeclarations() {
14
15
  }
15
16
 
16
17
  if (seen.has(decl.prop)) {
17
- droppable.add(seen.get(decl.prop))
18
+ // Exact same value as what we have seen so far
19
+ if (seen.get(decl.prop).value === decl.value) {
20
+ // Keep the last one, drop the one we've seen so far
21
+ droppable.add(seen.get(decl.prop))
22
+ // Override the existing one with the new value. This is necessary
23
+ // so that if we happen to have more than one declaration with the
24
+ // same value, that we keep removing the previous one. Otherwise we
25
+ // will only remove the *first* one.
26
+ seen.set(decl.prop, decl)
27
+ return
28
+ }
29
+
30
+ // Not the same value, so we need to check if we can merge it so
31
+ // let's collect it first.
32
+ if (!byProperty.has(decl.prop)) {
33
+ byProperty.set(decl.prop, new Set())
34
+ }
35
+
36
+ byProperty.get(decl.prop).add(seen.get(decl.prop))
37
+ byProperty.get(decl.prop).add(decl)
18
38
  }
19
39
 
20
40
  seen.set(decl.prop, decl)
21
41
  })
22
42
 
43
+ // Drop all the duplicate declarations with the exact same value we've
44
+ // already seen so far.
23
45
  for (let decl of droppable) {
24
46
  decl.remove()
25
47
  }
48
+
49
+ // Analyze the declarations based on its unit, drop all the declarations
50
+ // with the same unit but the last one in the list.
51
+ for (let declarations of byProperty.values()) {
52
+ let byUnit = new Map()
53
+
54
+ for (let decl of declarations) {
55
+ let unit = resolveUnit(decl.value)
56
+ if (unit === null) {
57
+ // We don't have a unit, so should never try and collapse this
58
+ // value. This is because we can't know how to do it in a correct
59
+ // way (e.g.: overrides for older browsers)
60
+ continue
61
+ }
62
+
63
+ if (!byUnit.has(unit)) {
64
+ byUnit.set(unit, new Set())
65
+ }
66
+
67
+ byUnit.get(unit).add(decl)
68
+ }
69
+
70
+ for (let declarations of byUnit.values()) {
71
+ // Get all but the last one
72
+ let removableDeclarations = Array.from(declarations).slice(0, -1)
73
+
74
+ for (let decl of removableDeclarations) {
75
+ decl.remove()
76
+ }
77
+ }
78
+ }
26
79
  })
27
80
  }
28
81
  }
82
+
83
+ let UNITLESS_NUMBER = Symbol('unitless-number')
84
+
85
+ function resolveUnit(input) {
86
+ let result = /^-?\d*.?\d+([\w%]+)?$/g.exec(input)
87
+
88
+ if (result) {
89
+ return result[1] ?? UNITLESS_NUMBER
90
+ }
91
+
92
+ return null
93
+ }
@@ -10,9 +10,11 @@ const PATTERNS = [
10
10
  /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
11
11
  /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
12
12
  /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
13
+ /([^<>"'`\s]*\[[^<>"'`\s]*:[^\]\s]*\])/.source, // `[attr:value]`
13
14
  /([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
14
15
  /([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
15
16
  /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
17
+ /([^"'`\s]*[^<>"'`\s:\\])/.source, // `<sm:underline`, `md>:font-bold`
16
18
  /([^<>"'`\s]*[^"'`\s:\\])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
17
19
 
18
20
  // Arbitrary properties
@@ -72,47 +72,6 @@ function extractApplyCandidates(params) {
72
72
  return [candidates, false]
73
73
  }
74
74
 
75
- function partitionApplyParents(root) {
76
- let applyParents = new Set()
77
-
78
- root.walkAtRules('apply', (rule) => {
79
- applyParents.add(rule.parent)
80
- })
81
-
82
- for (let rule of applyParents) {
83
- let nodeGroups = []
84
- let lastGroup = []
85
-
86
- for (let node of rule.nodes) {
87
- if (node.type === 'atrule' && node.name === 'apply') {
88
- if (lastGroup.length > 0) {
89
- nodeGroups.push(lastGroup)
90
- lastGroup = []
91
- }
92
- nodeGroups.push([node])
93
- } else {
94
- lastGroup.push(node)
95
- }
96
- }
97
-
98
- if (lastGroup.length > 0) {
99
- nodeGroups.push(lastGroup)
100
- }
101
-
102
- if (nodeGroups.length === 1) {
103
- continue
104
- }
105
-
106
- for (let group of [...nodeGroups].reverse()) {
107
- let newParent = rule.clone({ nodes: [] })
108
- newParent.append(group)
109
- rule.after(newParent)
110
- }
111
-
112
- rule.remove()
113
- }
114
- }
115
-
116
75
  function processApply(root, context) {
117
76
  let applyCandidates = new Set()
118
77
 
@@ -343,7 +302,6 @@ function processApply(root, context) {
343
302
 
344
303
  export default function expandApplyAtRules(context) {
345
304
  return (root) => {
346
- partitionApplyParents(root)
347
305
  processApply(root, context)
348
306
  }
349
307
  }
@@ -129,8 +129,6 @@ function buildStylesheet(rules, context) {
129
129
  return returnValue
130
130
  }
131
131
 
132
- export const DEFAULTS_LAYER = Symbol('defaults-layer')
133
-
134
132
  export default function expandTailwindAtRules(context) {
135
133
  return (root) => {
136
134
  let layerNodes = {
@@ -140,13 +138,15 @@ export default function expandTailwindAtRules(context) {
140
138
  variants: null,
141
139
  }
142
140
 
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
- root.walkAtRules('tailwind', (rule) => {
148
- if (Object.keys(layerNodes).includes(rule.params)) {
149
- layerNodes[rule.params] = rule
141
+ root.walkAtRules((rule) => {
142
+ // Make sure this file contains Tailwind directives. If not, we can save
143
+ // a lot of work and bail early. Also we don't have to register our touch
144
+ // file as a dependency since the output of this CSS does not depend on
145
+ // the source of any templates. Think Vue <style> blocks for example.
146
+ if (rule.name === 'tailwind') {
147
+ if (Object.keys(layerNodes).includes(rule.params)) {
148
+ layerNodes[rule.params] = rule
149
+ }
150
150
  }
151
151
  })
152
152
 
@@ -168,6 +168,8 @@ export default function expandTailwindAtRules(context) {
168
168
  getClassCandidates(transformer(content), extractor, candidates, seen)
169
169
  }
170
170
 
171
+ env.DEBUG && console.timeEnd('Reading changed files')
172
+
171
173
  // ---
172
174
 
173
175
  // Generate the actual CSS
@@ -201,18 +203,7 @@ export default function expandTailwindAtRules(context) {
201
203
  // Replace any Tailwind directives with generated CSS
202
204
 
203
205
  if (layerNodes.base) {
204
- layerNodes.base.before(cloneNodes([...baseNodes], layerNodes.base.source))
205
- }
206
-
207
- // @defaults rules are unconditionally added first to ensure that
208
- // using any utility that relies on defaults will work even when
209
- // compiled in an isolated environment like CSS modules
210
- if (context.tailwindConfig[DEFAULTS_LAYER] !== false) {
211
- if (layerNodes.base) {
212
- layerNodes.base.after(cloneNodes([...defaultNodes], root.source))
213
- } else {
214
- root.prepend(cloneNodes([...defaultNodes], root.source))
215
- }
206
+ layerNodes.base.before(cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source))
216
207
  }
217
208
 
218
209
  if (layerNodes.base) {
@@ -3,8 +3,13 @@ import log from '../util/log'
3
3
  export default function normalizeTailwindDirectives(root) {
4
4
  let tailwindDirectives = new Set()
5
5
  let layerDirectives = new Set()
6
+ let applyDirectives = new Set()
6
7
 
7
8
  root.walkAtRules((atRule) => {
9
+ if (atRule.name === 'apply') {
10
+ applyDirectives.add(atRule)
11
+ }
12
+
8
13
  if (atRule.name === 'import') {
9
14
  if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
10
15
  atRule.name = 'tailwind'
@@ -74,5 +79,5 @@ export default function normalizeTailwindDirectives(root) {
74
79
  }
75
80
  }
76
81
 
77
- return tailwindDirectives
82
+ return { tailwindDirectives, applyDirectives }
78
83
  }
@@ -113,12 +113,12 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
113
113
  }
114
114
  }
115
115
 
116
- if (selectorGroups.size === 0) {
117
- universal.remove()
118
- continue
119
- }
120
-
121
116
  if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
117
+ if (selectorGroups.size === 0) {
118
+ universal.remove()
119
+ continue
120
+ }
121
+
122
122
  for (let [, selectors] of selectorGroups) {
123
123
  let universalRule = postcss.rule()
124
124
 
@@ -20,6 +20,58 @@ import log from '../util/log'
20
20
  import negateValue from '../util/negateValue'
21
21
  import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
22
 
23
+ function partitionRules(root) {
24
+ if (!root.walkAtRules) return [root]
25
+
26
+ let applyParents = new Set()
27
+ let rules = []
28
+
29
+ root.walkAtRules('apply', (rule) => {
30
+ applyParents.add(rule.parent)
31
+ })
32
+
33
+ if (applyParents.size === 0) {
34
+ rules.push(root)
35
+ }
36
+
37
+ for (let rule of applyParents) {
38
+ let nodeGroups = []
39
+ let lastGroup = []
40
+
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)
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()
70
+ }
71
+
72
+ return rules
73
+ }
74
+
23
75
  function parseVariantFormatString(input) {
24
76
  if (input.includes('{')) {
25
77
  if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
@@ -89,39 +141,55 @@ function getClasses(selector) {
89
141
  return parser.transformSync(selector)
90
142
  }
91
143
 
92
- function extractCandidates(node) {
144
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
93
145
  let classes = []
94
146
 
147
+ // Handle normal rules
95
148
  if (node.type === 'rule') {
96
149
  for (let selector of node.selectors) {
97
150
  let classCandidates = getClasses(selector)
98
151
  // At least one of the selectors contains non-"on-demandable" candidates.
99
- if (classCandidates.length === 0) return []
152
+ if (classCandidates.length === 0) {
153
+ state.containsNonOnDemandable = true
154
+ }
100
155
 
101
- classes = [...classes, ...classCandidates]
156
+ for (let classCandidate of classCandidates) {
157
+ classes.push(classCandidate)
158
+ }
102
159
  }
103
- return classes
104
160
  }
105
161
 
106
- if (node.type === 'atrule') {
162
+ // Handle at-rules (which contains nested rules)
163
+ else if (node.type === 'atrule') {
107
164
  node.walkRules((rule) => {
108
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
165
+ for (let classCandidate of rule.selectors.flatMap((selector) =>
166
+ getClasses(selector, state, depth + 1)
167
+ )) {
168
+ classes.push(classCandidate)
169
+ }
109
170
  })
110
171
  }
111
172
 
173
+ if (depth === 0) {
174
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
175
+ }
176
+
112
177
  return classes
113
178
  }
114
179
 
115
180
  function withIdentifiers(styles) {
116
181
  return parseStyles(styles).flatMap((node) => {
117
182
  let nodeMap = new Map()
118
- let candidates = extractCandidates(node)
183
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
119
184
 
120
- // If this isn't "on-demandable", assign it a universal candidate.
121
- if (candidates.length === 0) {
122
- return [['*', node]]
185
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
186
+ if (containsNonOnDemandableSelectors) {
187
+ candidates.unshift('*')
123
188
  }
124
189
 
190
+ // However, it could be that it also contains "on-demandable" candidates.
191
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
192
+ // `@apply foo` for example.
125
193
  return candidates.map((c) => {
126
194
  if (!nodeMap.has(node)) {
127
195
  nodeMap.set(node, node)
@@ -216,7 +284,9 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
216
284
  context.candidateRuleMap.set(identifier, [])
217
285
  }
218
286
 
219
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
287
+ context.candidateRuleMap
288
+ .get(identifier)
289
+ .push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'user' }, rule]))
220
290
  }
221
291
  },
222
292
  addBase(base) {
@@ -230,7 +300,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
300
 
231
301
  context.candidateRuleMap
232
302
  .get(prefixedIdentifier)
233
- .push([{ sort: offset, layer: 'base' }, rule])
303
+ .push(...partitionRules(rule).map((rule) => [{ sort: offset, layer: 'base' }, rule]))
234
304
  }
235
305
  },
236
306
  /**
@@ -244,7 +314,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
244
314
 
245
315
  for (let [identifier, rule] of withIdentifiers(groups)) {
246
316
  let prefixedIdentifier = prefixIdentifier(identifier, {})
247
- let offset = offsets.base++
248
317
 
249
318
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
250
319
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -252,7 +321,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
252
321
 
253
322
  context.candidateRuleMap
254
323
  .get(prefixedIdentifier)
255
- .push([{ sort: offset, layer: 'defaults' }, rule])
324
+ .push(
325
+ ...partitionRules(rule).map((rule) => [
326
+ { sort: offsets.base++, layer: 'defaults' },
327
+ rule,
328
+ ])
329
+ )
256
330
  }
257
331
  },
258
332
  addComponents(components, options) {
@@ -265,7 +339,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
265
339
 
266
340
  for (let [identifier, rule] of withIdentifiers(components)) {
267
341
  let prefixedIdentifier = prefixIdentifier(identifier, options)
268
- let offset = offsets.components++
269
342
 
270
343
  classList.add(prefixedIdentifier)
271
344
 
@@ -275,7 +348,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
275
348
 
276
349
  context.candidateRuleMap
277
350
  .get(prefixedIdentifier)
278
- .push([{ sort: offset, layer: 'components', options }, rule])
351
+ .push(
352
+ ...partitionRules(rule).map((rule) => [
353
+ { sort: offsets.components++, layer: 'components', options },
354
+ rule,
355
+ ])
356
+ )
279
357
  }
280
358
  },
281
359
  addUtilities(utilities, options) {
@@ -288,7 +366,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
288
366
 
289
367
  for (let [identifier, rule] of withIdentifiers(utilities)) {
290
368
  let prefixedIdentifier = prefixIdentifier(identifier, options)
291
- let offset = offsets.utilities++
292
369
 
293
370
  classList.add(prefixedIdentifier)
294
371
 
@@ -298,7 +375,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
298
375
 
299
376
  context.candidateRuleMap
300
377
  .get(prefixedIdentifier)
301
- .push([{ sort: offset, layer: 'utilities', options }, rule])
378
+ .push(
379
+ ...partitionRules(rule).map((rule) => [
380
+ { sort: offsets.utilities++, layer: 'utilities', options },
381
+ rule,
382
+ ])
383
+ )
302
384
  }
303
385
  },
304
386
  matchUtilities: function (utilities, options) {
@@ -435,7 +517,14 @@ function trackModified(files, fileModifiedMap) {
435
517
  let parsed = url.parse(file)
436
518
  let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
437
519
  pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
438
- let newModified = fs.statSync(decodeURIComponent(pathname)).mtimeMs
520
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
521
+ if (!newModified) {
522
+ // It could happen that a file is passed in that doesn't exist. E.g.:
523
+ // postcss-cli will provide you a fake path when reading from stdin. This
524
+ // path then looks like /path-to-your-project/stdin In that case we just
525
+ // want to ignore it and don't track changes at all.
526
+ continue
527
+ }
439
528
 
440
529
  if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
441
530
  changed = true
@@ -112,19 +112,20 @@ 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 }) => {
115
+ return ({ tailwindDirectives, registerDependency, applyDirectives }) => {
116
116
  return (root, result) => {
117
117
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
118
118
  getTailwindConfig(configOrPath)
119
119
 
120
120
  let contextDependencies = new Set(configDependencies)
121
121
 
122
- // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies
123
- // to be dependencies of the context. Can reuse the context even if they change.
124
- // We may want to think about `@layer` being part of this trigger too, but it's tough
125
- // because it's impossible for a layer in one file to end up in the actual @tailwind rule
126
- // in another file since independent sources are effectively isolated.
127
- if (tailwindDirectives.size > 0) {
122
+ // If there are no @tailwind or @apply rules, we don't consider this CSS
123
+ // file or its dependencies to be dependencies of the context. Can reuse
124
+ // the context even if they change. We may want to think about `@layer`
125
+ // being part of this trigger too, but it's tough because it's impossible
126
+ // for a layer in one file to end up in the actual @tailwind rule in
127
+ // another file since independent sources are effectively isolated.
128
+ if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
128
129
  // Add current css file as a context dependencies.
129
130
  contextDependencies.add(result.opts.from)
130
131
 
@@ -147,12 +148,12 @@ export default function setupTrackingContext(configOrPath) {
147
148
 
148
149
  let candidateFiles = getCandidateFiles(context, tailwindConfig)
149
150
 
150
- // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies
151
- // to be dependencies of the context. Can reuse the context even if they change.
151
+ // If there are no @tailwind or @apply rules, we don't consider this CSS file or it's
152
+ // dependencies to be dependencies of the context. Can reuse the context even if they change.
152
153
  // We may want to think about `@layer` being part of this trigger too, but it's tough
153
154
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
154
155
  // in another file since independent sources are effectively isolated.
155
- if (tailwindDirectives.size > 0) {
156
+ if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
156
157
  let fileModifiedMap = getFileModifiedMap(context)
157
158
 
158
159
  // Add template paths as postcss dependencies.
@@ -1,10 +1,46 @@
1
1
  export const env = {
2
- TAILWIND_MODE: process.env.TAILWIND_MODE,
3
2
  NODE_ENV: process.env.NODE_ENV,
4
- DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0',
5
- TAILWIND_DISABLE_TOUCH: process.env.TAILWIND_DISABLE_TOUCH !== undefined,
6
- TAILWIND_TOUCH_DIR: process.env.TAILWIND_TOUCH_DIR,
3
+ DEBUG: resolveDebug(process.env.DEBUG),
7
4
  }
8
5
  export const contextMap = new Map()
9
6
  export const configContextMap = new Map()
10
7
  export const contextSourcesMap = new Map()
8
+
9
+ export function resolveDebug(debug) {
10
+ if (debug === undefined) {
11
+ return false
12
+ }
13
+
14
+ // Environment variables are strings, so convert to boolean
15
+ if (debug === 'true' || debug === '1') {
16
+ return true
17
+ }
18
+
19
+ if (debug === 'false' || debug === '0') {
20
+ return false
21
+ }
22
+
23
+ // Keep the debug convention into account:
24
+ // DEBUG=* -> This enables all debug modes
25
+ // DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC
26
+ // DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types)
27
+ // DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB
28
+
29
+ if (debug === '*') {
30
+ return true
31
+ }
32
+
33
+ let debuggers = debug.split(',').map((d) => d.split(':')[0])
34
+
35
+ // Ignoring tailwindcss
36
+ if (debuggers.includes('-tailwindcss')) {
37
+ return false
38
+ }
39
+
40
+ // Including tailwindcss
41
+ if (debuggers.includes('tailwindcss')) {
42
+ return true
43
+ }
44
+
45
+ return false
46
+ }
@@ -12,10 +12,13 @@ import { issueFlagNotices } from './featureFlags'
12
12
 
13
13
  export default function processTailwindFeatures(setupContext) {
14
14
  return function (root, result) {
15
- let tailwindDirectives = normalizeTailwindDirectives(root)
15
+ let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
16
+
17
+ detectNesting()(root, result)
16
18
 
17
19
  let context = setupContext({
18
20
  tailwindDirectives,
21
+ applyDirectives,
19
22
  registerDependency(dependency) {
20
23
  result.messages.push({
21
24
  plugin: 'tailwindcss',
@@ -36,7 +39,6 @@ export default function processTailwindFeatures(setupContext) {
36
39
 
37
40
  issueFlagNotices(context.tailwindConfig)
38
41
 
39
- detectNesting(context)(root, result)
40
42
  expandTailwindAtRules(context)(root, result)
41
43
  expandApplyAtRules(context)(root, result)
42
44
  evaluateTailwindFunctions(context)(root, result)
@@ -185,7 +185,7 @@ export function coerceValue(types, modifier, options, tailwindConfig) {
185
185
  // Find first matching type
186
186
  for (let type of [].concat(types)) {
187
187
  let result = typeMap[type](modifier, options, { tailwindConfig })
188
- if (result) return [result, type]
188
+ if (result !== undefined) return [result, type]
189
189
  }
190
190
 
191
191
  return []