tailwindcss 3.1.8 → 3.2.1

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 (101) hide show
  1. package/CHANGELOG.md +64 -3
  2. package/README.md +6 -5
  3. package/lib/cli/build/deps.js +54 -0
  4. package/lib/cli/build/index.js +44 -0
  5. package/lib/cli/build/plugin.js +351 -0
  6. package/lib/cli/build/utils.js +78 -0
  7. package/lib/cli/build/watching.js +113 -0
  8. package/lib/cli/help/index.js +71 -0
  9. package/lib/cli/index.js +18 -0
  10. package/lib/cli/init/index.js +46 -0
  11. package/lib/cli/shared.js +12 -0
  12. package/lib/cli.js +11 -590
  13. package/lib/corePlugins.js +332 -108
  14. package/lib/css/preflight.css +5 -0
  15. package/lib/featureFlags.js +7 -4
  16. package/lib/index.js +6 -1
  17. package/lib/lib/content.js +167 -0
  18. package/lib/lib/defaultExtractor.js +15 -10
  19. package/lib/lib/detectNesting.js +2 -2
  20. package/lib/lib/evaluateTailwindFunctions.js +17 -1
  21. package/lib/lib/expandApplyAtRules.js +66 -37
  22. package/lib/lib/expandTailwindAtRules.js +10 -42
  23. package/lib/lib/findAtConfigPath.js +44 -0
  24. package/lib/lib/generateRules.js +180 -93
  25. package/lib/lib/normalizeTailwindDirectives.js +1 -1
  26. package/lib/lib/offsets.js +217 -0
  27. package/lib/lib/regex.js +1 -1
  28. package/lib/lib/setupContextUtils.js +339 -100
  29. package/lib/lib/setupTrackingContext.js +5 -39
  30. package/lib/lib/sharedState.js +2 -0
  31. package/lib/public/colors.js +1 -1
  32. package/lib/util/buildMediaQuery.js +6 -3
  33. package/lib/util/configurePlugins.js +1 -1
  34. package/lib/util/dataTypes.js +15 -19
  35. package/lib/util/formatVariantSelector.js +92 -8
  36. package/lib/util/getAllConfigs.js +14 -3
  37. package/lib/util/isValidArbitraryValue.js +1 -1
  38. package/lib/util/nameClass.js +3 -0
  39. package/lib/util/negateValue.js +15 -2
  40. package/lib/util/normalizeConfig.js +17 -3
  41. package/lib/util/normalizeScreens.js +100 -3
  42. package/lib/util/parseAnimationValue.js +1 -1
  43. package/lib/util/parseBoxShadowValue.js +1 -1
  44. package/lib/util/parseDependency.js +33 -54
  45. package/lib/util/parseGlob.js +34 -0
  46. package/lib/util/parseObjectStyles.js +1 -1
  47. package/lib/util/pluginUtils.js +87 -17
  48. package/lib/util/resolveConfig.js +2 -2
  49. package/lib/util/splitAtTopLevelOnly.js +31 -81
  50. package/lib/util/transformThemeValue.js +9 -2
  51. package/lib/util/validateConfig.js +1 -1
  52. package/lib/util/validateFormalSyntax.js +24 -0
  53. package/package.json +14 -13
  54. package/peers/index.js +3263 -1887
  55. package/plugin.d.ts +3 -3
  56. package/scripts/release-channel.js +18 -0
  57. package/scripts/release-notes.js +21 -0
  58. package/src/cli/build/deps.js +56 -0
  59. package/src/cli/build/index.js +45 -0
  60. package/src/cli/build/plugin.js +417 -0
  61. package/src/cli/build/utils.js +76 -0
  62. package/src/cli/build/watching.js +134 -0
  63. package/src/cli/help/index.js +70 -0
  64. package/src/cli/index.js +3 -0
  65. package/src/cli/init/index.js +50 -0
  66. package/src/cli/shared.js +5 -0
  67. package/src/cli.js +4 -696
  68. package/src/corePlugins.js +262 -39
  69. package/src/css/preflight.css +5 -0
  70. package/src/featureFlags.js +12 -2
  71. package/src/index.js +5 -0
  72. package/src/lib/content.js +205 -0
  73. package/src/lib/defaultExtractor.js +3 -0
  74. package/src/lib/evaluateTailwindFunctions.js +22 -1
  75. package/src/lib/expandApplyAtRules.js +70 -29
  76. package/src/lib/expandTailwindAtRules.js +8 -46
  77. package/src/lib/findAtConfigPath.js +48 -0
  78. package/src/lib/generateRules.js +223 -101
  79. package/src/lib/offsets.js +270 -0
  80. package/src/lib/setupContextUtils.js +376 -89
  81. package/src/lib/setupTrackingContext.js +4 -45
  82. package/src/lib/sharedState.js +2 -0
  83. package/src/util/buildMediaQuery.js +5 -3
  84. package/src/util/dataTypes.js +15 -17
  85. package/src/util/formatVariantSelector.js +113 -9
  86. package/src/util/getAllConfigs.js +14 -2
  87. package/src/util/nameClass.js +4 -0
  88. package/src/util/negateValue.js +10 -2
  89. package/src/util/normalizeConfig.js +22 -2
  90. package/src/util/normalizeScreens.js +99 -4
  91. package/src/util/parseBoxShadowValue.js +1 -1
  92. package/src/util/parseDependency.js +37 -42
  93. package/src/util/parseGlob.js +24 -0
  94. package/src/util/pluginUtils.js +96 -14
  95. package/src/util/resolveConfig.js +1 -1
  96. package/src/util/splitAtTopLevelOnly.js +23 -49
  97. package/src/util/transformThemeValue.js +9 -1
  98. package/src/util/validateFormalSyntax.js +34 -0
  99. package/stubs/defaultConfig.stub.js +20 -3
  100. package/types/config.d.ts +48 -13
  101. package/types/generated/default-theme.d.ts +11 -0
@@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
3
3
  import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses } from '../util/pluginUtils'
6
+ import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
8
  import * as sharedState from './sharedState'
9
9
  import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
@@ -18,7 +18,7 @@ let classNameParser = selectorParser((selectors) => {
18
18
  return selectors.first.filter(({ type }) => type === 'class').pop().value
19
19
  })
20
20
 
21
- function getClassNameFromSelector(selector) {
21
+ export function getClassNameFromSelector(selector) {
22
22
  return classNameParser.transformSync(selector)
23
23
  }
24
24
 
@@ -34,13 +34,24 @@ function* candidatePermutations(candidate) {
34
34
 
35
35
  while (lastIndex >= 0) {
36
36
  let dashIdx
37
+ let wasSlash = false
37
38
 
38
39
  if (lastIndex === Infinity && candidate.endsWith(']')) {
39
40
  let bracketIdx = candidate.indexOf('[')
40
41
 
41
42
  // If character before `[` isn't a dash or a slash, this isn't a dynamic class
42
43
  // eg. string[]
43
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
44
+ if (candidate[bracketIdx - 1] === '-') {
45
+ dashIdx = bracketIdx - 1
46
+ } else if (candidate[bracketIdx - 1] === '/') {
47
+ dashIdx = bracketIdx - 1
48
+ wasSlash = true
49
+ } else {
50
+ dashIdx = -1
51
+ }
52
+ } else if (lastIndex === Infinity && candidate.includes('/')) {
53
+ dashIdx = candidate.lastIndexOf('/')
54
+ wasSlash = true
44
55
  } else {
45
56
  dashIdx = candidate.lastIndexOf('-', lastIndex)
46
57
  }
@@ -50,11 +61,16 @@ function* candidatePermutations(candidate) {
50
61
  }
51
62
 
52
63
  let prefix = candidate.slice(0, dashIdx)
53
- let modifier = candidate.slice(dashIdx + 1)
54
-
55
- yield [prefix, modifier]
64
+ let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
56
65
 
57
66
  lastIndex = dashIdx - 1
67
+
68
+ // TODO: This feels a bit hacky
69
+ if (prefix === '' || modifier === '/') {
70
+ continue
71
+ }
72
+
73
+ yield [prefix, modifier]
58
74
  }
59
75
  }
60
76
 
@@ -128,12 +144,42 @@ function applyVariant(variant, matches, context) {
128
144
  return matches
129
145
  }
130
146
 
131
- let args
147
+ /** @type {{modifier: string | null, value: string | null}} */
148
+ let args = { modifier: null, value: sharedState.NONE }
149
+
150
+ // Retrieve "modifier"
151
+ {
152
+ let match = /(.*)\/(.*)$/g.exec(variant)
153
+ if (match) {
154
+ variant = match[1]
155
+ args.modifier = match[2]
132
156
 
133
- // Find partial arbitrary variants
157
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
158
+ return []
159
+ }
160
+ }
161
+ }
162
+
163
+ // Retrieve "arbitrary value"
134
164
  if (variant.endsWith(']') && !variant.startsWith('[')) {
135
- args = variant.slice(variant.lastIndexOf('[') + 1, -1)
136
- variant = variant.slice(0, variant.indexOf(args) - 1 /* - */ - 1 /* [ */)
165
+ // We either have:
166
+ // @[200px]
167
+ // group-[:hover]
168
+ //
169
+ // But we don't want:
170
+ // @-[200px] (`-` is incorrect)
171
+ // group[:hover] (`-` is missing)
172
+ let match = /(.)(-?)\[(.*)\]/g.exec(variant)
173
+ if (match) {
174
+ let [, char, seperator, value] = match
175
+ // @-[200px] case
176
+ if (char === '@' && seperator === '-') return []
177
+ // group[:hover] case
178
+ if (char !== '@' && seperator === '') return []
179
+
180
+ variant = variant.replace(`${seperator}[${value}]`, '')
181
+ args.value = value
182
+ }
137
183
  }
138
184
 
139
185
  // Register arbitrary variants
@@ -146,9 +192,9 @@ function applyVariant(variant, matches, context) {
146
192
 
147
193
  let fn = parseVariant(selector)
148
194
 
149
- let sort = Array.from(context.variantOrder.values()).pop() << 1n
195
+ let sort = context.offsets.recordVariant(variant)
196
+
150
197
  context.variantMap.set(variant, [[sort, fn]])
151
- context.variantOrder.set(variant, sort)
152
198
  }
153
199
 
154
200
  if (context.variantMap.has(variant)) {
@@ -227,11 +273,7 @@ function applyVariant(variant, matches, context) {
227
273
  // where you keep handling jobs until everything is done and each job can queue more
228
274
  // jobs if needed.
229
275
  variantFunctionTuples.push([
230
- // TODO: This could have potential bugs if we shift the sort order from variant A far
231
- // enough into the sort space of variant B. The chances are low, but if this happens
232
- // then this might be the place too look at. One potential solution to this problem is
233
- // reserving additional X places for these 'unknown' variants in between.
234
- variantSort | BigInt(idx << ruleWithVariant.length),
276
+ context.offsets.applyParallelOffset(variantSort, idx),
235
277
  variantFunction,
236
278
 
237
279
  // If the clone has been modified we have to pass that back
@@ -298,7 +340,11 @@ function applyVariant(variant, matches, context) {
298
340
  let withOffset = [
299
341
  {
300
342
  ...meta,
301
- sort: variantSort | meta.sort,
343
+ sort: context.offsets.applyVariantOffset(
344
+ meta.sort,
345
+ variantSort,
346
+ Object.assign(args, context.variantOptions.get(variant))
347
+ ),
302
348
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
303
349
  isArbitraryVariant: isArbitraryValue(variant),
304
350
  },
@@ -409,9 +455,11 @@ function extractArbitraryProperty(classCandidate, context) {
409
455
  return null
410
456
  }
411
457
 
458
+ let sort = context.offsets.arbitraryProperty()
459
+
412
460
  return [
413
461
  [
414
- { sort: context.arbitraryPropertiesSort, layer: 'utilities' },
462
+ { sort, layer: 'utilities' },
415
463
  () => ({
416
464
  [asClass(classCandidate)]: {
417
465
  [property]: normalized,
@@ -463,7 +511,7 @@ function splitWithSeparator(input, separator) {
463
511
  return [sharedState.NOT_ON_DEMAND]
464
512
  }
465
513
 
466
- return Array.from(splitAtTopLevelOnly(input, separator))
514
+ return splitAtTopLevelOnly(input, separator)
467
515
  }
468
516
 
469
517
  function* recordCandidates(matches, classCandidate) {
@@ -537,68 +585,133 @@ function* resolveMatches(candidate, context, original = candidate) {
537
585
  }
538
586
 
539
587
  if (matchesPerPlugin.length > 0) {
540
- typesByMatches.set(matchesPerPlugin, sort.options?.type)
588
+ let matchingTypes = Array.from(
589
+ getMatchingTypes(
590
+ sort.options?.types ?? [],
591
+ modifier,
592
+ sort.options ?? {},
593
+ context.tailwindConfig
594
+ )
595
+ ).map(([_, type]) => type)
596
+
597
+ if (matchingTypes.length > 0) {
598
+ typesByMatches.set(matchesPerPlugin, matchingTypes)
599
+ }
600
+
541
601
  matches.push(matchesPerPlugin)
542
602
  }
543
603
  }
544
604
 
545
605
  if (isArbitraryValue(modifier)) {
546
- // When generated arbitrary values are ambiguous, we can't know
547
- // which to pick so don't generate any utilities for them
548
606
  if (matches.length > 1) {
549
- let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
607
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
608
+ // don't have `any` as a type first.
609
+ let [withAny, withoutAny] = matches.reduce(
610
+ (group, plugin) => {
611
+ let hasAnyType = plugin.some(([{ options }]) =>
612
+ options.types.some(({ type }) => type === 'any')
613
+ )
550
614
 
551
- // Remove duplicates, so that we can detect proper unique types for each plugin.
552
- for (let pluginTypes of typesPerPlugin) {
553
- for (let type of pluginTypes) {
554
- let removeFromOwnGroup = false
615
+ if (hasAnyType) {
616
+ group[0].push(plugin)
617
+ } else {
618
+ group[1].push(plugin)
619
+ }
620
+ return group
621
+ },
622
+ [[], []]
623
+ )
555
624
 
556
- for (let otherGroup of typesPerPlugin) {
557
- if (pluginTypes === otherGroup) continue
625
+ function findFallback(matches) {
626
+ // If only a single plugin matches, let's take that one
627
+ if (matches.length === 1) {
628
+ return matches[0]
629
+ }
558
630
 
559
- if (otherGroup.has(type)) {
560
- otherGroup.delete(type)
561
- removeFromOwnGroup = true
631
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
632
+ // also has the correct type which preferOnConflicts the plugin in case of clashes.
633
+ return matches.find((rules) => {
634
+ let matchingTypes = typesByMatches.get(rules)
635
+ return rules.some(([{ options }, rule]) => {
636
+ if (!isParsableNode(rule)) {
637
+ return false
562
638
  }
563
- }
564
639
 
565
- if (removeFromOwnGroup) pluginTypes.delete(type)
566
- }
640
+ return options.types.some(
641
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
642
+ )
643
+ })
644
+ })
567
645
  }
568
646
 
569
- let messages = []
570
-
571
- for (let [idx, group] of typesPerPlugin.entries()) {
572
- for (let type of group) {
573
- let rules = matches[idx]
574
- .map(([, rule]) => rule)
575
- .flat()
576
- .map((rule) =>
577
- rule
578
- .toString()
579
- .split('\n')
580
- .slice(1, -1) // Remove selector and closing '}'
581
- .map((line) => line.trim())
582
- .map((x) => ` ${x}`) // Re-indent
583
- .join('\n')
584
- )
585
- .join('\n\n')
647
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
648
+ // the given arbitrary value.
649
+ let fallback = findFallback(withoutAny) ?? findFallback(withAny)
650
+ if (fallback) {
651
+ matches = [fallback]
652
+ }
586
653
 
587
- messages.push(
588
- ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
589
- )
590
- break
654
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
655
+ // generated css for the current candidate. This means that the result is ambiguous and this
656
+ // should not happen. We won't generate anything right now, so let's report this to the user
657
+ // by logging some options about what they can do.
658
+ else {
659
+ let typesPerPlugin = matches.map(
660
+ (match) => new Set([...(typesByMatches.get(match) ?? [])])
661
+ )
662
+
663
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
664
+ for (let pluginTypes of typesPerPlugin) {
665
+ for (let type of pluginTypes) {
666
+ let removeFromOwnGroup = false
667
+
668
+ for (let otherGroup of typesPerPlugin) {
669
+ if (pluginTypes === otherGroup) continue
670
+
671
+ if (otherGroup.has(type)) {
672
+ otherGroup.delete(type)
673
+ removeFromOwnGroup = true
674
+ }
675
+ }
676
+
677
+ if (removeFromOwnGroup) pluginTypes.delete(type)
678
+ }
591
679
  }
592
- }
593
680
 
594
- log.warn([
595
- `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
596
- ...messages,
597
- `If this is content and not a class, replace it with \`${candidate
598
- .replace('[', '&lsqb;')
599
- .replace(']', '&rsqb;')}\` to silence this warning.`,
600
- ])
601
- continue
681
+ let messages = []
682
+
683
+ for (let [idx, group] of typesPerPlugin.entries()) {
684
+ for (let type of group) {
685
+ let rules = matches[idx]
686
+ .map(([, rule]) => rule)
687
+ .flat()
688
+ .map((rule) =>
689
+ rule
690
+ .toString()
691
+ .split('\n')
692
+ .slice(1, -1) // Remove selector and closing '}'
693
+ .map((line) => line.trim())
694
+ .map((x) => ` ${x}`) // Re-indent
695
+ .join('\n')
696
+ )
697
+ .join('\n\n')
698
+
699
+ messages.push(
700
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
701
+ )
702
+ break
703
+ }
704
+ }
705
+
706
+ log.warn([
707
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
708
+ ...messages,
709
+ `If this is content and not a class, replace it with \`${candidate
710
+ .replace('[', '&lsqb;')
711
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
712
+ ])
713
+ continue
714
+ }
602
715
  }
603
716
 
604
717
  matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
@@ -649,16 +762,45 @@ function inKeyframes(rule) {
649
762
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
650
763
  }
651
764
 
765
+ function getImportantStrategy(important) {
766
+ if (important === true) {
767
+ return (rule) => {
768
+ if (inKeyframes(rule)) {
769
+ return
770
+ }
771
+
772
+ rule.walkDecls((d) => {
773
+ if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
774
+ d.important = true
775
+ }
776
+ })
777
+ }
778
+ }
779
+
780
+ if (typeof important === 'string') {
781
+ return (rule) => {
782
+ if (inKeyframes(rule)) {
783
+ return
784
+ }
785
+
786
+ rule.selectors = rule.selectors.map((selector) => {
787
+ return `${important} ${selector}`
788
+ })
789
+ }
790
+ }
791
+ }
792
+
652
793
  function generateRules(candidates, context) {
653
794
  let allRules = []
795
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
654
796
 
655
797
  for (let candidate of candidates) {
656
798
  if (context.notClassCache.has(candidate)) {
657
799
  continue
658
800
  }
659
801
 
660
- if (context.classCache.has(candidate)) {
661
- allRules.push(context.classCache.get(candidate))
802
+ if (context.candidateRuleCache.has(candidate)) {
803
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
662
804
  continue
663
805
  }
664
806
 
@@ -670,47 +812,27 @@ function generateRules(candidates, context) {
670
812
  }
671
813
 
672
814
  context.classCache.set(candidate, matches)
673
- allRules.push(matches)
674
- }
675
815
 
676
- // Strategy based on `tailwindConfig.important`
677
- let strategy = ((important) => {
678
- if (important === true) {
679
- return (rule) => {
680
- rule.walkDecls((d) => {
681
- if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
682
- d.important = true
683
- }
684
- })
685
- }
686
- }
816
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
817
+ context.candidateRuleCache.set(candidate, rules)
687
818
 
688
- if (typeof important === 'string') {
689
- return (rule) => {
690
- rule.selectors = rule.selectors.map((selector) => {
691
- return `${important} ${selector}`
692
- })
693
- }
694
- }
695
- })(context.tailwindConfig.important)
819
+ for (const match of matches) {
820
+ let [{ sort, options }, rule] = match
696
821
 
697
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
698
- if (options.respectImportant) {
699
- if (strategy) {
822
+ if (options.respectImportant && strategy) {
700
823
  let container = postcss.root({ nodes: [rule.clone()] })
701
- container.walkRules((r) => {
702
- if (inKeyframes(r)) {
703
- return
704
- }
705
-
706
- strategy(r)
707
- })
824
+ container.walkRules(strategy)
708
825
  rule = container.nodes[0]
709
826
  }
827
+
828
+ let newEntry = [sort, rule]
829
+ rules.add(newEntry)
830
+ context.ruleCache.add(newEntry)
831
+ allRules.push(newEntry)
710
832
  }
833
+ }
711
834
 
712
- return [sort | context.layerOrder[layer], rule]
713
- })
835
+ return allRules
714
836
  }
715
837
 
716
838
  function isArbitraryValue(input) {