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
@@ -19,6 +19,13 @@ import { toPath } from '../util/toPath'
19
19
  import log from '../util/log'
20
20
  import negateValue from '../util/negateValue'
21
21
  import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
+ import { generateRules } from './generateRules'
23
+ import { hasContentChanged } from './cacheInvalidation.js'
24
+
25
+ function prefix(context, selector) {
26
+ let prefix = context.tailwindConfig.prefix
27
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
28
+ }
22
29
 
23
30
  function parseVariantFormatString(input) {
24
31
  if (input.includes('{')) {
@@ -89,39 +96,55 @@ function getClasses(selector) {
89
96
  return parser.transformSync(selector)
90
97
  }
91
98
 
92
- function extractCandidates(node) {
99
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
93
100
  let classes = []
94
101
 
102
+ // Handle normal rules
95
103
  if (node.type === 'rule') {
96
104
  for (let selector of node.selectors) {
97
105
  let classCandidates = getClasses(selector)
98
106
  // At least one of the selectors contains non-"on-demandable" candidates.
99
- if (classCandidates.length === 0) return []
107
+ if (classCandidates.length === 0) {
108
+ state.containsNonOnDemandable = true
109
+ }
100
110
 
101
- classes = [...classes, ...classCandidates]
111
+ for (let classCandidate of classCandidates) {
112
+ classes.push(classCandidate)
113
+ }
102
114
  }
103
- return classes
104
115
  }
105
116
 
106
- if (node.type === 'atrule') {
117
+ // Handle at-rules (which contains nested rules)
118
+ else if (node.type === 'atrule') {
107
119
  node.walkRules((rule) => {
108
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
120
+ for (let classCandidate of rule.selectors.flatMap((selector) =>
121
+ getClasses(selector, state, depth + 1)
122
+ )) {
123
+ classes.push(classCandidate)
124
+ }
109
125
  })
110
126
  }
111
127
 
128
+ if (depth === 0) {
129
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
130
+ }
131
+
112
132
  return classes
113
133
  }
114
134
 
115
135
  function withIdentifiers(styles) {
116
136
  return parseStyles(styles).flatMap((node) => {
117
137
  let nodeMap = new Map()
118
- let candidates = extractCandidates(node)
138
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
119
139
 
120
- // If this isn't "on-demandable", assign it a universal candidate.
121
- if (candidates.length === 0) {
122
- return [['*', node]]
140
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
141
+ if (containsNonOnDemandableSelectors) {
142
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
123
143
  }
124
144
 
145
+ // However, it could be that it also contains "on-demandable" candidates.
146
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
147
+ // `@apply foo` for example.
125
148
  return candidates.map((c) => {
126
149
  if (!nodeMap.has(node)) {
127
150
  nodeMap.set(node, node)
@@ -141,8 +164,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
141
164
  }
142
165
 
143
166
  function prefixIdentifier(identifier, options) {
144
- if (identifier === '*') {
145
- return '*'
167
+ if (identifier === sharedState.NOT_ON_DEMAND) {
168
+ return sharedState.NOT_ON_DEMAND
146
169
  }
147
170
 
148
171
  if (!options.respectPrefix) {
@@ -208,21 +231,31 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
208
231
  // Preserved for backwards compatibility but not used in v3.0+
209
232
  return []
210
233
  },
211
- addUserCss(userCss) {
212
- for (let [identifier, rule] of withIdentifiers(userCss)) {
213
- let offset = offsets.user++
234
+ addBase(base) {
235
+ for (let [identifier, rule] of withIdentifiers(base)) {
236
+ let prefixedIdentifier = prefixIdentifier(identifier, {})
237
+ let offset = offsets.base++
214
238
 
215
- if (!context.candidateRuleMap.has(identifier)) {
216
- context.candidateRuleMap.set(identifier, [])
239
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
240
+ context.candidateRuleMap.set(prefixedIdentifier, [])
217
241
  }
218
242
 
219
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
243
+ context.candidateRuleMap
244
+ .get(prefixedIdentifier)
245
+ .push([{ sort: offset, layer: 'base' }, rule])
220
246
  }
221
247
  },
222
- addBase(base) {
223
- for (let [identifier, rule] of withIdentifiers(base)) {
248
+ /**
249
+ * @param {string} group
250
+ * @param {Record<string, string | string[]>} declarations
251
+ */
252
+ addDefaults(group, declarations) {
253
+ const groups = {
254
+ [`@defaults ${group}`]: declarations,
255
+ }
256
+
257
+ for (let [identifier, rule] of withIdentifiers(groups)) {
224
258
  let prefixedIdentifier = prefixIdentifier(identifier, {})
225
- let offset = offsets.base++
226
259
 
227
260
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
228
261
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -230,7 +263,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
263
 
231
264
  context.candidateRuleMap
232
265
  .get(prefixedIdentifier)
233
- .push([{ sort: offset, layer: 'base' }, rule])
266
+ .push([{ sort: offsets.base++, layer: 'defaults' }, rule])
234
267
  }
235
268
  },
236
269
  addComponents(components, options) {
@@ -243,7 +276,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
243
276
 
244
277
  for (let [identifier, rule] of withIdentifiers(components)) {
245
278
  let prefixedIdentifier = prefixIdentifier(identifier, options)
246
- let offset = offsets.components++
247
279
 
248
280
  classList.add(prefixedIdentifier)
249
281
 
@@ -253,7 +285,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
253
285
 
254
286
  context.candidateRuleMap
255
287
  .get(prefixedIdentifier)
256
- .push([{ sort: offset, layer: 'components', options }, rule])
288
+ .push([{ sort: offsets.components++, layer: 'components', options }, rule])
257
289
  }
258
290
  },
259
291
  addUtilities(utilities, options) {
@@ -266,7 +298,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
266
298
 
267
299
  for (let [identifier, rule] of withIdentifiers(utilities)) {
268
300
  let prefixedIdentifier = prefixIdentifier(identifier, options)
269
- let offset = offsets.utilities++
270
301
 
271
302
  classList.add(prefixedIdentifier)
272
303
 
@@ -276,7 +307,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
276
307
 
277
308
  context.candidateRuleMap
278
309
  .get(prefixedIdentifier)
279
- .push([{ sort: offset, layer: 'utilities', options }, rule])
310
+ .push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule])
280
311
  }
281
312
  },
282
313
  matchUtilities: function (utilities, options) {
@@ -413,7 +444,14 @@ function trackModified(files, fileModifiedMap) {
413
444
  let parsed = url.parse(file)
414
445
  let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
415
446
  pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
416
- let newModified = fs.statSync(decodeURIComponent(pathname)).mtimeMs
447
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
448
+ if (!newModified) {
449
+ // It could happen that a file is passed in that doesn't exist. E.g.:
450
+ // postcss-cli will provide you a fake path when reading from stdin. This
451
+ // path then looks like /path-to-your-project/stdin In that case we just
452
+ // want to ignore it and don't track changes at all.
453
+ continue
454
+ }
417
455
 
418
456
  if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
419
457
  changed = true
@@ -473,15 +511,6 @@ function collectLayerPlugins(root) {
473
511
  }
474
512
  })
475
513
 
476
- root.walkRules((rule) => {
477
- // At this point it is safe to include all the left-over css from the
478
- // user's css file. This is because the `@tailwind` and `@layer` directives
479
- // will already be handled and will be removed from the css tree.
480
- layerPlugins.push(function ({ addUserCss }) {
481
- addUserCss(rule, { respectPrefix: false })
482
- })
483
- })
484
-
485
514
  return layerPlugins
486
515
  }
487
516
 
@@ -528,6 +557,7 @@ function registerPlugins(plugins, context) {
528
557
  let variantList = []
529
558
  let variantMap = new Map()
530
559
  let offsets = {
560
+ defaults: 0n,
531
561
  base: 0n,
532
562
  components: 0n,
533
563
  utilities: 0n,
@@ -555,6 +585,7 @@ function registerPlugins(plugins, context) {
555
585
 
556
586
  let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
557
587
  offsets.base,
588
+ offsets.defaults,
558
589
  offsets.components,
559
590
  offsets.utilities,
560
591
  offsets.user,
@@ -566,13 +597,14 @@ function registerPlugins(plugins, context) {
566
597
  context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
567
598
 
568
599
  context.layerOrder = {
569
- base: (1n << reservedBits) << 0n,
570
- components: (1n << reservedBits) << 1n,
571
- utilities: (1n << reservedBits) << 2n,
572
- user: (1n << reservedBits) << 3n,
600
+ defaults: (1n << reservedBits) << 0n,
601
+ base: (1n << reservedBits) << 1n,
602
+ components: (1n << reservedBits) << 2n,
603
+ utilities: (1n << reservedBits) << 3n,
604
+ user: (1n << reservedBits) << 4n,
573
605
  }
574
606
 
575
- reservedBits += 4n
607
+ reservedBits += 5n
576
608
 
577
609
  let offset = 0
578
610
  context.variantOrder = new Map(
@@ -611,7 +643,7 @@ function registerPlugins(plugins, context) {
611
643
  log.warn('root-regex', [
612
644
  'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
613
645
  'Update your `safelist` configuration to eliminate this warning.',
614
- // TODO: Add https://tw.wtf/regex-safelist
646
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
615
647
  ])
616
648
  continue
617
649
  }
@@ -621,12 +653,33 @@ function registerPlugins(plugins, context) {
621
653
 
622
654
  if (checks.length > 0) {
623
655
  let patternMatchingCount = new Map()
656
+ let prefixLength = context.tailwindConfig.prefix.length
624
657
 
625
658
  for (let util of classList) {
626
659
  let utils = Array.isArray(util)
627
660
  ? (() => {
628
661
  let [utilName, options] = util
629
- return Object.keys(options?.values ?? {}).map((value) => formatClass(utilName, value))
662
+ let values = Object.keys(options?.values ?? {})
663
+ let classes = values.map((value) => formatClass(utilName, value))
664
+
665
+ if (options?.supportsNegativeValues) {
666
+ // This is the normal negated version
667
+ // e.g. `-inset-1` or `-tw-inset-1`
668
+ classes = [...classes, ...classes.map((cls) => '-' + cls)]
669
+
670
+ // This is the negated version *after* the prefix
671
+ // e.g. `tw--inset-1`
672
+ // The prefix is already attached to util name
673
+ // So we add the negative after the prefix
674
+ classes = [
675
+ ...classes,
676
+ ...classes.map(
677
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
678
+ ),
679
+ ]
680
+ }
681
+
682
+ return classes
630
683
  })()
631
684
  : [util]
632
685
 
@@ -661,14 +714,41 @@ function registerPlugins(plugins, context) {
661
714
  log.warn([
662
715
  `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
663
716
  'Fix this pattern or remove it from your `safelist` configuration.',
717
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
664
718
  ])
665
719
  }
666
720
  }
667
721
  }
668
722
 
723
+ // A list of utilities that are used by certain Tailwind CSS utilities but
724
+ // that don't exist on their own. This will result in them "not existing" and
725
+ // sorting could be weird since you still require them in order to make the
726
+ // host utitlies work properly. (Thanks Biology)
727
+ let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')])
728
+ context.getClassOrder = function getClassOrder(classes) {
729
+ let sortedClassNames = new Map()
730
+ for (let [sort, rule] of generateRules(new Set(classes), context)) {
731
+ if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue
732
+ sortedClassNames.set(rule.raws.tailwind.candidate, sort)
733
+ }
734
+
735
+ return classes.map((className) => {
736
+ let order = sortedClassNames.get(className) ?? null
737
+
738
+ if (order === null && parasiteUtilities.has(className)) {
739
+ // This will make sure that it is at the very beginning of the
740
+ // `components` layer which technically means 'before any
741
+ // components'.
742
+ order = context.layerOrder.components
743
+ }
744
+
745
+ return [className, order]
746
+ })
747
+ }
748
+
669
749
  // Generate a list of strings for autocompletion purposes, e.g.
670
750
  // ['uppercase', 'lowercase', ...]
671
- context.getClassList = function () {
751
+ context.getClassList = function getClassList() {
672
752
  let output = []
673
753
 
674
754
  for (let util of classList) {
@@ -743,6 +823,8 @@ export function getContext(
743
823
  existingContext = context
744
824
  }
745
825
 
826
+ let cssDidChange = hasContentChanged(sourcePath, root)
827
+
746
828
  // If there's already a context in the cache and we don't need to
747
829
  // reset the context, return the cached context.
748
830
  if (existingContext) {
@@ -750,7 +832,7 @@ export function getContext(
750
832
  [...contextDependencies],
751
833
  getFileModifiedMap(existingContext)
752
834
  )
753
- if (!contextDependenciesChanged) {
835
+ if (!contextDependenciesChanged && !cssDidChange) {
754
836
  return [existingContext, false]
755
837
  }
756
838
  }
@@ -119,11 +119,12 @@ export default function setupTrackingContext(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.
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.
127
128
  if (tailwindDirectives.size > 0) {
128
129
  // Add current css file as a context dependencies.
129
130
  contextDependencies.add(result.opts.from)
@@ -147,8 +148,8 @@ 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.
@@ -1,10 +1,48 @@
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
+ export const sourceHashMap = new Map()
9
+ export const NOT_ON_DEMAND = new String('*')
10
+
11
+ export function resolveDebug(debug) {
12
+ if (debug === undefined) {
13
+ return false
14
+ }
15
+
16
+ // Environment variables are strings, so convert to boolean
17
+ if (debug === 'true' || debug === '1') {
18
+ return true
19
+ }
20
+
21
+ if (debug === 'false' || debug === '0') {
22
+ return false
23
+ }
24
+
25
+ // Keep the debug convention into account:
26
+ // DEBUG=* -> This enables all debug modes
27
+ // DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC
28
+ // DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types)
29
+ // DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB
30
+
31
+ if (debug === '*') {
32
+ return true
33
+ }
34
+
35
+ let debuggers = debug.split(',').map((d) => d.split(':')[0])
36
+
37
+ // Ignoring tailwindcss
38
+ if (debuggers.includes('-tailwindcss')) {
39
+ return false
40
+ }
41
+
42
+ // Including tailwindcss
43
+ if (debuggers.includes('tailwindcss')) {
44
+ return true
45
+ }
46
+
47
+ return false
48
+ }
@@ -0,0 +1,42 @@
1
+ # tailwindcss/nesting
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`.
4
+
5
+ Add it to your PostCSS configuration, somewhere before Tailwind itself:
6
+
7
+ ```js
8
+ // postcss.config.js
9
+ module.exports = {
10
+ plugins: [
11
+ require('postcss-import'),
12
+ require('tailwindcss/nesting'),
13
+ require('tailwindcss'),
14
+ require('autoprefixer'),
15
+ ]
16
+ }
17
+ ```
18
+
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
+
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:
22
+
23
+ ```shell
24
+ npm install postcss-nesting
25
+ ```
26
+
27
+ Then pass the plugin itself as an argument to `tailwindcss/nesting` in your PostCSS configuration:
28
+
29
+ ```js
30
+ // postcss.config.js
31
+ module.exports = {
32
+ plugins: [
33
+ require('postcss-import'),
34
+ require('tailwindcss/nesting')(require('postcss-nesting')),
35
+ require('tailwindcss'),
36
+ require('autoprefixer'),
37
+ ]
38
+ }
39
+ ```
40
+
41
+ This can also be helpful if for whatever reason you need to use a very specific version of `postcss-nested` and want to override the version we bundle with `tailwindcss/nesting` itself.
42
+
@@ -0,0 +1,13 @@
1
+ import { nesting } from './plugin'
2
+
3
+ export default Object.assign(
4
+ function (opts) {
5
+ return {
6
+ postcssPlugin: 'tailwindcss/nesting',
7
+ Once(root, { result }) {
8
+ return nesting(opts)(root, result)
9
+ },
10
+ }
11
+ },
12
+ { postcss: true }
13
+ )
@@ -0,0 +1,80 @@
1
+ import postcss from 'postcss'
2
+ import postcssNested from 'postcss-nested'
3
+
4
+ export function nesting(opts = postcssNested) {
5
+ return (root, result) => {
6
+ root.walkAtRules('screen', (rule) => {
7
+ rule.name = 'media'
8
+ rule.params = `screen(${rule.params})`
9
+ })
10
+
11
+ root.walkAtRules('apply', (rule) => {
12
+ rule.before(postcss.decl({ prop: '__apply', value: rule.params, source: rule.source }))
13
+ rule.remove()
14
+ })
15
+
16
+ let plugin = (() => {
17
+ if (
18
+ typeof opts === 'function' ||
19
+ (typeof opts === 'object' && opts?.hasOwnProperty?.('postcssPlugin'))
20
+ ) {
21
+ return opts
22
+ }
23
+
24
+ if (typeof opts === 'string') {
25
+ return require(opts)
26
+ }
27
+
28
+ if (Object.keys(opts).length <= 0) {
29
+ return postcssNested
30
+ }
31
+
32
+ throw new Error('tailwindcss/nesting should be loaded with a nesting plugin.')
33
+ })()
34
+
35
+ postcss([plugin]).process(root, result.opts).sync()
36
+
37
+ root.walkDecls('__apply', (decl) => {
38
+ decl.before(postcss.atRule({ name: 'apply', params: decl.value, source: decl.source }))
39
+ decl.remove()
40
+ })
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
+
78
+ return root
79
+ }
80
+ }
@@ -6,16 +6,24 @@ import substituteScreenAtRules from './lib/substituteScreenAtRules'
6
6
  import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
7
7
  import collapseAdjacentRules from './lib/collapseAdjacentRules'
8
8
  import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
9
+ import partitionApplyAtRules from './lib/partitionApplyAtRules'
9
10
  import detectNesting from './lib/detectNesting'
10
11
  import { createContext } from './lib/setupContextUtils'
11
12
  import { issueFlagNotices } from './featureFlags'
12
13
 
13
14
  export default function processTailwindFeatures(setupContext) {
14
15
  return function (root, result) {
15
- let tailwindDirectives = normalizeTailwindDirectives(root)
16
+ let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
17
+
18
+ detectNesting()(root, result)
19
+
20
+ // Partition apply rules that are found in the css
21
+ // itself.
22
+ partitionApplyAtRules()(root, result)
16
23
 
17
24
  let context = setupContext({
18
25
  tailwindDirectives,
26
+ applyDirectives,
19
27
  registerDependency(dependency) {
20
28
  result.messages.push({
21
29
  plugin: 'tailwindcss',
@@ -36,8 +44,10 @@ export default function processTailwindFeatures(setupContext) {
36
44
 
37
45
  issueFlagNotices(context.tailwindConfig)
38
46
 
39
- detectNesting(context)(root, result)
40
47
  expandTailwindAtRules(context)(root, result)
48
+ // Partition apply rules that are generated by
49
+ // addComponents, addUtilities and so on.
50
+ partitionApplyAtRules()(root, result)
41
51
  expandApplyAtRules(context)(root, result)
42
52
  evaluateTailwindFunctions(context)(root, result)
43
53
  substituteScreenAtRules(context)(root, result)
@@ -1,9 +1,22 @@
1
- export default function cloneNodes(nodes, source) {
1
+ export default function cloneNodes(nodes, source = undefined, raws = undefined) {
2
2
  return nodes.map((node) => {
3
3
  let cloned = node.clone()
4
4
 
5
5
  if (source !== undefined) {
6
6
  cloned.source = source
7
+
8
+ if ('walk' in cloned) {
9
+ cloned.walk((child) => {
10
+ child.source = source
11
+ })
12
+ }
13
+ }
14
+
15
+ if (raws !== undefined) {
16
+ cloned.raws.tailwind = {
17
+ ...cloned.raws.tailwind,
18
+ ...raws,
19
+ }
7
20
  }
8
21
 
9
22
  return cloned
package/src/util/color.js CHANGED
@@ -5,8 +5,11 @@ let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i
5
5
  let VALUE = `(?:\\d+|\\d*\\.\\d+)%?`
6
6
  let SEP = `(?:\\s*,\\s*|\\s+)`
7
7
  let ALPHA_SEP = `\\s*[,/]\\s*`
8
- let RGB_HSL = new RegExp(
9
- `^(rgb|hsl)a?\\(\\s*(${VALUE})${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\s*\\)$`
8
+ let RGB = new RegExp(
9
+ `^rgba?\\(\\s*(${VALUE})${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\s*\\)$`
10
+ )
11
+ let HSL = new RegExp(
12
+ `^hsla?\\(\\s*((?:${VALUE})(?:deg|rad|grad|turn)?)${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\s*\\)$`
10
13
  )
11
14
 
12
15
  export function parseColor(value) {
@@ -37,13 +40,23 @@ export function parseColor(value) {
37
40
  }
38
41
  }
39
42
 
40
- let match = value.match(RGB_HSL)
43
+ let rgbMatch = value.match(RGB)
44
+
45
+ if (rgbMatch !== null) {
46
+ return {
47
+ mode: 'rgb',
48
+ color: [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((v) => v.toString()),
49
+ alpha: rgbMatch[4]?.toString?.(),
50
+ }
51
+ }
52
+
53
+ let hslMatch = value.match(HSL)
41
54
 
42
- if (match !== null) {
55
+ if (hslMatch !== null) {
43
56
  return {
44
- mode: match[1],
45
- color: [match[2], match[3], match[4]].map((v) => v.toString()),
46
- alpha: match[5]?.toString?.(),
57
+ mode: 'hsl',
58
+ color: [hslMatch[1], hslMatch[2], hslMatch[3]].map((v) => v.toString()),
59
+ alpha: hslMatch[4]?.toString?.(),
47
60
  }
48
61
  }
49
62