tailwindcss 3.0.6 → 3.0.10

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.
package/src/cli.js CHANGED
@@ -41,6 +41,26 @@ function formatNodes(root) {
41
41
  }
42
42
  }
43
43
 
44
+ async function outputFile(file, contents) {
45
+ if (fs.existsSync(file) && (await fs.promises.readFile(file, 'utf8')) === contents) {
46
+ return // Skip writing the file
47
+ }
48
+
49
+ // Write the file
50
+ await fs.promises.writeFile(file, contents, 'utf8')
51
+ }
52
+
53
+ function drainStdin() {
54
+ return new Promise((resolve, reject) => {
55
+ let result = ''
56
+ process.stdin.on('data', (chunk) => {
57
+ result += chunk
58
+ })
59
+ process.stdin.on('end', () => resolve(result))
60
+ process.stdin.on('error', (err) => reject(err))
61
+ })
62
+ }
63
+
44
64
  function help({ message, usage, commands, options }) {
45
65
  let indent = 2
46
66
 
@@ -355,7 +375,7 @@ async function build() {
355
375
  input = args['--input'] = args['_'][1]
356
376
  }
357
377
 
358
- if (input && !fs.existsSync((input = path.resolve(input)))) {
378
+ if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) {
359
379
  console.error(`Specified input file ${args['--input']} does not exist.`)
360
380
  process.exit(9)
361
381
  }
@@ -534,10 +554,11 @@ async function build() {
534
554
  if (!output) {
535
555
  return process.stdout.write(result.css)
536
556
  }
557
+
537
558
  return Promise.all(
538
559
  [
539
- fs.promises.writeFile(output, result.css, () => true),
540
- result.map && fs.writeFile(output + '.map', result.map.toString(), () => true),
560
+ outputFile(output, result.css),
561
+ result.map && outputFile(output + '.map', result.map.toString()),
541
562
  ].filter(Boolean)
542
563
  )
543
564
  })
@@ -548,9 +569,21 @@ async function build() {
548
569
  })
549
570
  }
550
571
 
551
- let css = input
552
- ? fs.readFileSync(path.resolve(input), 'utf8')
553
- : '@tailwind base; @tailwind components; @tailwind utilities'
572
+ let css = await (() => {
573
+ // Piping in data, let's drain the stdin
574
+ if (input === '-') {
575
+ return drainStdin()
576
+ }
577
+
578
+ // Input file has been provided
579
+ if (input) {
580
+ return fs.readFileSync(path.resolve(input), 'utf8')
581
+ }
582
+
583
+ // No input file provided, fallback to default atrules
584
+ return '@tailwind base; @tailwind components; @tailwind utilities'
585
+ })()
586
+
554
587
  return processCSS(css)
555
588
  }
556
589
 
@@ -664,10 +697,10 @@ async function build() {
664
697
  return process.stdout.write(result.css)
665
698
  }
666
699
 
667
- await Promise.all(
700
+ return Promise.all(
668
701
  [
669
- fs.promises.writeFile(output, result.css, () => true),
670
- result.map && fs.writeFile(output + '.map', result.map.toString(), () => true),
702
+ outputFile(output, result.css),
703
+ result.map && outputFile(output + '.map', result.map.toString()),
671
704
  ].filter(Boolean)
672
705
  )
673
706
  })
@@ -684,9 +717,21 @@ async function build() {
684
717
  })
685
718
  }
686
719
 
687
- let css = input
688
- ? fs.readFileSync(path.resolve(input), 'utf8')
689
- : '@tailwind base; @tailwind components; @tailwind utilities'
720
+ let css = await (() => {
721
+ // Piping in data, let's drain the stdin
722
+ if (input === '-') {
723
+ return drainStdin()
724
+ }
725
+
726
+ // Input file has been provided
727
+ if (input) {
728
+ return fs.readFileSync(path.resolve(input), 'utf8')
729
+ }
730
+
731
+ // No input file provided, fallback to default atrules
732
+ return '@tailwind base; @tailwind components; @tailwind utilities'
733
+ })()
734
+
690
735
  let result = await processCSS(css)
691
736
  env.DEBUG && console.timeEnd('Finished in')
692
737
  return result
@@ -58,7 +58,7 @@ hr {
58
58
  Add the correct text decoration in Chrome, Edge, and Safari.
59
59
  */
60
60
 
61
- abbr[title] {
61
+ abbr:where([title]) {
62
62
  text-decoration: underline dotted;
63
63
  }
64
64
 
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
+ }
@@ -0,0 +1,48 @@
1
+ const PATTERNS = [
2
+ /(?:\['([^'\s]+[^<>"'`\s:\\])')/.source, // ['text-lg' -> text-lg
3
+ /(?:\["([^"\s]+[^<>"'`\s:\\])")/.source, // ["text-lg" -> text-lg
4
+ /(?:\[`([^`\s]+[^<>"'`\s:\\])`)/.source, // [`text-lg` -> text-lg
5
+ /([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
6
+ /([^<>"'`\s]*\[\w*"[^'`\s]*"?\])/.source, // font-["some_font",sans-serif]
7
+ /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
8
+ /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
9
+ /([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
10
+ /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
11
+ /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
12
+ /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
13
+ /([^<>"'`\s]*\[[^<>"'`\s]*:[^\]\s]*\])/.source, // `[attr:value]`
14
+ /([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
15
+ /([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
16
+ /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
17
+ /([^"'`\s]*[^<>"'`\s:\\])/.source, // `<sm:underline`, `md>:font-bold`
18
+ /([^<>"'`\s]*[^"'`\s:\\])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
19
+
20
+ // Arbitrary properties
21
+ // /([^"\s]*\[[^\s]+?\][^"\s]*)/.source,
22
+ // /([^'\s]*\[[^\s]+?\][^'\s]*)/.source,
23
+ // /([^`\s]*\[[^\s]+?\][^`\s]*)/.source,
24
+ ].join('|')
25
+
26
+ const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
27
+ const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
28
+
29
+ /**
30
+ * @param {string} content
31
+ */
32
+ export function defaultExtractor(content) {
33
+ let broadMatches = content.matchAll(BROAD_MATCH_GLOBAL_REGEXP)
34
+ let innerMatches = content.match(INNER_MATCH_GLOBAL_REGEXP) || []
35
+ let results = [...broadMatches, ...innerMatches].flat().filter((v) => v !== undefined)
36
+
37
+ return results
38
+ }
39
+
40
+ // Regular utilities
41
+ // {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}?
42
+
43
+ // Arbitrary values
44
+ // {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}?
45
+ // arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes
46
+
47
+ // Arbitrary properties
48
+ // {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}]
@@ -1,22 +1,33 @@
1
1
  import postcss from 'postcss'
2
2
  import parser from 'postcss-selector-parser'
3
+
3
4
  import { resolveMatches } from './generateRules'
4
5
  import bigSign from '../util/bigSign'
5
6
  import escapeClassName from '../util/escapeClassName'
6
7
 
7
- function containsBase(selector, classCandidateBase, separator) {
8
- return parser((selectors) => {
9
- let contains = false
8
+ function extractClasses(node) {
9
+ let classes = new Set()
10
+ let container = postcss.root({ nodes: [node.clone()] })
10
11
 
11
- selectors.walkClasses((classSelector) => {
12
- if (classSelector.value.split(separator).pop() === classCandidateBase) {
13
- contains = true
14
- return false
15
- }
16
- })
12
+ container.walkRules((rule) => {
13
+ parser((selectors) => {
14
+ selectors.walkClasses((classSelector) => {
15
+ classes.add(classSelector.value)
16
+ })
17
+ }).processSync(rule.selector)
18
+ })
19
+
20
+ return Array.from(classes)
21
+ }
22
+
23
+ function extractBaseCandidates(candidates, separator) {
24
+ let baseClasses = new Set()
17
25
 
18
- return contains
19
- }).transformSync(selector)
26
+ for (let candidate of candidates) {
27
+ baseClasses.add(candidate.split(separator).pop())
28
+ }
29
+
30
+ return Array.from(baseClasses)
20
31
  }
21
32
 
22
33
  function prefix(context, selector) {
@@ -212,15 +223,40 @@ function processApply(root, context) {
212
223
  let siblings = []
213
224
 
214
225
  for (let [applyCandidate, important, rules] of candidates) {
215
- let base = applyCandidate.split(context.tailwindConfig.separator).pop()
216
-
217
226
  for (let [meta, node] of rules) {
218
- if (
219
- containsBase(parent.selector, base, context.tailwindConfig.separator) &&
220
- containsBase(node.selector, base, context.tailwindConfig.separator)
221
- ) {
227
+ let parentClasses = extractClasses(parent)
228
+ let nodeClasses = extractClasses(node)
229
+
230
+ // Add base utility classes from the @apply node to the list of
231
+ // classes to check whether it intersects and therefore results in a
232
+ // circular dependency or not.
233
+ //
234
+ // E.g.:
235
+ // .foo {
236
+ // @apply hover:a; // This applies "a" but with a modifier
237
+ // }
238
+ //
239
+ // We only have to do that with base classes of the `node`, not of the `parent`
240
+ // E.g.:
241
+ // .hover\:foo {
242
+ // @apply bar;
243
+ // }
244
+ // .bar {
245
+ // @apply foo;
246
+ // }
247
+ //
248
+ // This should not result in a circular dependency because we are
249
+ // just applying `.foo` and the rule above is `.hover\:foo` which is
250
+ // unrelated. However, if we were to apply `hover:foo` then we _did_
251
+ // have to include this one.
252
+ nodeClasses = nodeClasses.concat(
253
+ extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
254
+ )
255
+
256
+ let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
257
+ if (intersects) {
222
258
  throw node.error(
223
- `Circular dependency detected when using: \`@apply ${applyCandidate}\``
259
+ `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
224
260
  )
225
261
  }
226
262
 
@@ -230,6 +266,42 @@ function processApply(root, context) {
230
266
 
231
267
  if (canRewriteSelector) {
232
268
  root.walkRules((rule) => {
269
+ // Let's imagine you have the following structure:
270
+ //
271
+ // .foo {
272
+ // @apply bar;
273
+ // }
274
+ //
275
+ // @supports (a: b) {
276
+ // .bar {
277
+ // color: blue
278
+ // }
279
+ //
280
+ // .something-unrelated {}
281
+ // }
282
+ //
283
+ // In this case we want to apply `.bar` but it happens to be in
284
+ // an atrule node. We clone that node instead of the nested one
285
+ // because we still want that @supports rule to be there once we
286
+ // applied everything.
287
+ //
288
+ // However it happens to be that the `.something-unrelated` is
289
+ // also in that same shared @supports atrule. This is not good,
290
+ // and this should not be there. The good part is that this is
291
+ // a clone already and it can be safely removed. The question is
292
+ // how do we know we can remove it. Basically what we can do is
293
+ // match it against the applyCandidate that you want to apply. If
294
+ // it doesn't match the we can safely delete it.
295
+ //
296
+ // If we didn't do this, then the `replaceSelector` function
297
+ // would have replaced this with something that didn't exist and
298
+ // therefore it removed the selector altogether. In this specific
299
+ // case it would result in `{}` instead of `.something-unrelated {}`
300
+ if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
301
+ rule.remove()
302
+ return
303
+ }
304
+
233
305
  rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)
234
306
 
235
307
  rule.walkDecls((d) => {
@@ -250,7 +322,6 @@ function processApply(root, context) {
250
322
  // Inject the rules, sorted, correctly
251
323
  let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
252
324
 
253
- // console.log(parent)
254
325
  // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
255
326
  parent.after(nodes)
256
327
  }
@@ -3,33 +3,12 @@ import * as sharedState from './sharedState'
3
3
  import { generateRules } from './generateRules'
4
4
  import bigSign from '../util/bigSign'
5
5
  import cloneNodes from '../util/cloneNodes'
6
+ import { defaultExtractor } from './defaultExtractor'
6
7
 
7
8
  let env = sharedState.env
8
9
 
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
10
  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
- },
11
+ DEFAULT: defaultExtractor,
33
12
  }
34
13
 
35
14
  const builtInTransformers = {
@@ -161,17 +140,28 @@ export default function expandTailwindAtRules(context) {
161
140
  variants: null,
162
141
  }
163
142
 
164
- // Make sure this file contains Tailwind directives. If not, we can save
165
- // a lot of work and bail early. Also we don't have to register our touch
166
- // file as a dependency since the output of this CSS does not depend on
167
- // the source of any templates. Think Vue <style> blocks for example.
168
- root.walkAtRules('tailwind', (rule) => {
169
- if (Object.keys(layerNodes).includes(rule.params)) {
170
- layerNodes[rule.params] = rule
143
+ let hasApply = false
144
+
145
+ root.walkAtRules((rule) => {
146
+ // Make sure this file contains Tailwind directives. If not, we can save
147
+ // a lot of work and bail early. Also we don't have to register our touch
148
+ // file as a dependency since the output of this CSS does not depend on
149
+ // the source of any templates. Think Vue <style> blocks for example.
150
+ if (rule.name === 'tailwind') {
151
+ if (Object.keys(layerNodes).includes(rule.params)) {
152
+ layerNodes[rule.params] = rule
153
+ }
154
+ }
155
+
156
+ // We also want to check for @apply because the user can
157
+ // apply classes in an isolated environment like CSS
158
+ // modules and we still need to inject defaults
159
+ if (rule.name === 'apply') {
160
+ hasApply = true
171
161
  }
172
162
  })
173
163
 
174
- if (Object.values(layerNodes).every((n) => n === null)) {
164
+ if (Object.values(layerNodes).every((n) => n === null) && !hasApply) {
175
165
  return root
176
166
  }
177
167
 
@@ -112,6 +112,11 @@ function applyVariant(variant, matches, context) {
112
112
  let result = []
113
113
 
114
114
  for (let [meta, rule] of matches) {
115
+ // Don't generate variants for user css
116
+ if (meta.layer === 'user') {
117
+ continue
118
+ }
119
+
115
120
  let container = postcss.root({ nodes: [rule.clone()] })
116
121
 
117
122
  for (let [variantSort, variantFunction] of variantFunctionTuples) {
@@ -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
  }
@@ -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,11 @@ 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
16
 
17
17
  let context = setupContext({
18
18
  tailwindDirectives,
19
+ applyDirectives,
19
20
  registerDependency(dependency) {
20
21
  result.messages.push({
21
22
  plugin: 'tailwindcss',