tailwindcss 3.1.7 → 3.2.0

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 (112) hide show
  1. package/README.md +12 -8
  2. package/lib/cli/build/deps.js +54 -0
  3. package/lib/cli/build/index.js +44 -0
  4. package/lib/cli/build/plugin.js +335 -0
  5. package/lib/cli/build/utils.js +78 -0
  6. package/lib/cli/build/watching.js +113 -0
  7. package/lib/cli/help/index.js +71 -0
  8. package/lib/cli/index.js +18 -0
  9. package/lib/cli/init/index.js +46 -0
  10. package/lib/cli/shared.js +12 -0
  11. package/lib/cli.js +11 -590
  12. package/lib/corePlugins.js +332 -108
  13. package/lib/css/preflight.css +5 -0
  14. package/lib/featureFlags.js +7 -4
  15. package/lib/index.js +6 -1
  16. package/lib/lib/content.js +167 -0
  17. package/lib/lib/defaultExtractor.js +15 -10
  18. package/lib/lib/detectNesting.js +2 -2
  19. package/lib/lib/evaluateTailwindFunctions.js +17 -1
  20. package/lib/lib/expandApplyAtRules.js +71 -37
  21. package/lib/lib/expandTailwindAtRules.js +10 -42
  22. package/lib/lib/findAtConfigPath.js +44 -0
  23. package/lib/lib/generateRules.js +181 -96
  24. package/lib/lib/normalizeTailwindDirectives.js +1 -1
  25. package/lib/lib/offsets.js +217 -0
  26. package/lib/lib/regex.js +1 -1
  27. package/lib/lib/setupContextUtils.js +339 -100
  28. package/lib/lib/setupTrackingContext.js +5 -39
  29. package/lib/lib/sharedState.js +2 -0
  30. package/lib/public/colors.js +1 -1
  31. package/lib/util/buildMediaQuery.js +6 -3
  32. package/lib/util/configurePlugins.js +1 -1
  33. package/lib/util/dataTypes.js +15 -19
  34. package/lib/util/formatVariantSelector.js +92 -8
  35. package/lib/util/getAllConfigs.js +14 -3
  36. package/lib/util/isValidArbitraryValue.js +1 -1
  37. package/lib/util/nameClass.js +3 -0
  38. package/lib/util/negateValue.js +15 -2
  39. package/lib/util/normalizeConfig.js +17 -3
  40. package/lib/util/normalizeScreens.js +100 -3
  41. package/lib/util/parseAnimationValue.js +1 -1
  42. package/lib/util/parseBoxShadowValue.js +1 -1
  43. package/lib/util/parseDependency.js +33 -54
  44. package/lib/util/parseGlob.js +34 -0
  45. package/lib/util/parseObjectStyles.js +1 -1
  46. package/lib/util/pluginUtils.js +86 -17
  47. package/lib/util/resolveConfig.js +3 -3
  48. package/lib/util/splitAtTopLevelOnly.js +31 -81
  49. package/lib/util/transformThemeValue.js +9 -2
  50. package/lib/util/validateConfig.js +1 -1
  51. package/lib/util/validateFormalSyntax.js +24 -0
  52. package/package.json +14 -12
  53. package/peers/.DS_Store +0 -0
  54. package/peers/.svgo.yml +75 -0
  55. package/peers/index.js +3690 -2274
  56. package/peers/orders/concentric-css.json +299 -0
  57. package/peers/orders/smacss.json +299 -0
  58. package/peers/orders/source.json +295 -0
  59. package/plugin.d.ts +3 -3
  60. package/scripts/release-channel.js +18 -0
  61. package/scripts/release-notes.js +21 -0
  62. package/src/.DS_Store +0 -0
  63. package/src/cli/build/deps.js +56 -0
  64. package/src/cli/build/index.js +45 -0
  65. package/src/cli/build/plugin.js +397 -0
  66. package/src/cli/build/utils.js +76 -0
  67. package/src/cli/build/watching.js +134 -0
  68. package/src/cli/help/index.js +70 -0
  69. package/src/cli/index.js +3 -0
  70. package/src/cli/init/index.js +50 -0
  71. package/src/cli/shared.js +5 -0
  72. package/src/cli.js +4 -696
  73. package/src/corePlugins.js +262 -39
  74. package/src/css/preflight.css +5 -0
  75. package/src/featureFlags.js +12 -2
  76. package/src/index.js +5 -0
  77. package/src/lib/content.js +205 -0
  78. package/src/lib/defaultExtractor.js +3 -0
  79. package/src/lib/evaluateTailwindFunctions.js +22 -1
  80. package/src/lib/expandApplyAtRules.js +76 -29
  81. package/src/lib/expandTailwindAtRules.js +8 -46
  82. package/src/lib/findAtConfigPath.js +48 -0
  83. package/src/lib/generateRules.js +224 -105
  84. package/src/lib/offsets.js +270 -0
  85. package/src/lib/setupContextUtils.js +376 -89
  86. package/src/lib/setupTrackingContext.js +4 -45
  87. package/src/lib/sharedState.js +2 -0
  88. package/src/util/buildMediaQuery.js +5 -3
  89. package/src/util/dataTypes.js +15 -17
  90. package/src/util/formatVariantSelector.js +113 -9
  91. package/src/util/getAllConfigs.js +14 -2
  92. package/src/util/nameClass.js +4 -0
  93. package/src/util/negateValue.js +10 -2
  94. package/src/util/normalizeConfig.js +22 -2
  95. package/src/util/normalizeScreens.js +99 -4
  96. package/src/util/parseBoxShadowValue.js +1 -1
  97. package/src/util/parseDependency.js +37 -42
  98. package/src/util/parseGlob.js +24 -0
  99. package/src/util/pluginUtils.js +90 -14
  100. package/src/util/resolveConfig.js +2 -2
  101. package/src/util/splitAtTopLevelOnly.js +23 -49
  102. package/src/util/transformThemeValue.js +9 -1
  103. package/src/util/validateFormalSyntax.js +34 -0
  104. package/stubs/defaultConfig.stub.js +19 -3
  105. package/tmp.css +11 -0
  106. package/tmp.dependency-graph.js +2 -0
  107. package/tmp.in.css +3 -0
  108. package/tmp.js +0 -0
  109. package/tmp.out.css +524 -0
  110. package/types/config.d.ts +47 -13
  111. package/types/generated/default-theme.d.ts +11 -0
  112. package/CHANGELOG.md +0 -2222
@@ -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,13 +144,42 @@ function applyVariant(variant, matches, context) {
128
144
  return matches
129
145
  }
130
146
 
131
- let args
132
- let isArbitraryVariant = false
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]
133
156
 
134
- // Find partial arbitrary variants
157
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
158
+ return []
159
+ }
160
+ }
161
+ }
162
+
163
+ // Retrieve "arbitrary value"
135
164
  if (variant.endsWith(']') && !variant.startsWith('[')) {
136
- args = variant.slice(variant.lastIndexOf('[') + 1, -1)
137
- 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
+ }
138
183
  }
139
184
 
140
185
  // Register arbitrary variants
@@ -145,13 +190,11 @@ function applyVariant(variant, matches, context) {
145
190
  return []
146
191
  }
147
192
 
148
- isArbitraryVariant = true
149
-
150
193
  let fn = parseVariant(selector)
151
194
 
152
- let sort = Array.from(context.variantOrder.values()).pop() << 1n
195
+ let sort = context.offsets.recordVariant(variant)
196
+
153
197
  context.variantMap.set(variant, [[sort, fn]])
154
- context.variantOrder.set(variant, sort)
155
198
  }
156
199
 
157
200
  if (context.variantMap.has(variant)) {
@@ -230,11 +273,7 @@ function applyVariant(variant, matches, context) {
230
273
  // where you keep handling jobs until everything is done and each job can queue more
231
274
  // jobs if needed.
232
275
  variantFunctionTuples.push([
233
- // TODO: This could have potential bugs if we shift the sort order from variant A far
234
- // enough into the sort space of variant B. The chances are low, but if this happens
235
- // then this might be the place too look at. One potential solution to this problem is
236
- // reserving additional X places for these 'unknown' variants in between.
237
- variantSort | BigInt(idx << ruleWithVariant.length),
276
+ context.offsets.applyParallelOffset(variantSort, idx),
238
277
  variantFunction,
239
278
 
240
279
  // If the clone has been modified we have to pass that back
@@ -301,9 +340,13 @@ function applyVariant(variant, matches, context) {
301
340
  let withOffset = [
302
341
  {
303
342
  ...meta,
304
- sort: variantSort | meta.sort,
343
+ sort: context.offsets.applyVariantOffset(
344
+ meta.sort,
345
+ variantSort,
346
+ Object.assign(args, context.variantOptions.get(variant))
347
+ ),
305
348
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
306
- isArbitraryVariant,
349
+ isArbitraryVariant: isArbitraryValue(variant),
307
350
  },
308
351
  clone.nodes[0],
309
352
  ]
@@ -412,9 +455,11 @@ function extractArbitraryProperty(classCandidate, context) {
412
455
  return null
413
456
  }
414
457
 
458
+ let sort = context.offsets.arbitraryProperty()
459
+
415
460
  return [
416
461
  [
417
- { sort: context.arbitraryPropertiesSort, layer: 'utilities' },
462
+ { sort, layer: 'utilities' },
418
463
  () => ({
419
464
  [asClass(classCandidate)]: {
420
465
  [property]: normalized,
@@ -466,7 +511,7 @@ function splitWithSeparator(input, separator) {
466
511
  return [sharedState.NOT_ON_DEMAND]
467
512
  }
468
513
 
469
- return Array.from(splitAtTopLevelOnly(input, separator))
514
+ return splitAtTopLevelOnly(input, separator)
470
515
  }
471
516
 
472
517
  function* recordCandidates(matches, classCandidate) {
@@ -540,68 +585,133 @@ function* resolveMatches(candidate, context, original = candidate) {
540
585
  }
541
586
 
542
587
  if (matchesPerPlugin.length > 0) {
543
- 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
+
544
601
  matches.push(matchesPerPlugin)
545
602
  }
546
603
  }
547
604
 
548
605
  if (isArbitraryValue(modifier)) {
549
- // When generated arbitrary values are ambiguous, we can't know
550
- // which to pick so don't generate any utilities for them
551
606
  if (matches.length > 1) {
552
- 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
+ )
553
614
 
554
- // Remove duplicates, so that we can detect proper unique types for each plugin.
555
- for (let pluginTypes of typesPerPlugin) {
556
- for (let type of pluginTypes) {
557
- 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
+ )
558
624
 
559
- for (let otherGroup of typesPerPlugin) {
560
- 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
+ }
561
630
 
562
- if (otherGroup.has(type)) {
563
- otherGroup.delete(type)
564
- 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
565
638
  }
566
- }
567
639
 
568
- if (removeFromOwnGroup) pluginTypes.delete(type)
569
- }
640
+ return options.types.some(
641
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
642
+ )
643
+ })
644
+ })
570
645
  }
571
646
 
572
- let messages = []
573
-
574
- for (let [idx, group] of typesPerPlugin.entries()) {
575
- for (let type of group) {
576
- let rules = matches[idx]
577
- .map(([, rule]) => rule)
578
- .flat()
579
- .map((rule) =>
580
- rule
581
- .toString()
582
- .split('\n')
583
- .slice(1, -1) // Remove selector and closing '}'
584
- .map((line) => line.trim())
585
- .map((x) => ` ${x}`) // Re-indent
586
- .join('\n')
587
- )
588
- .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
+ }
589
653
 
590
- messages.push(
591
- ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
592
- )
593
- 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
+ }
594
679
  }
595
- }
596
680
 
597
- log.warn([
598
- `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
599
- ...messages,
600
- `If this is content and not a class, replace it with \`${candidate
601
- .replace('[', '&lsqb;')
602
- .replace(']', '&rsqb;')}\` to silence this warning.`,
603
- ])
604
- 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
+ }
605
715
  }
606
716
 
607
717
  matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
@@ -652,16 +762,45 @@ function inKeyframes(rule) {
652
762
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
653
763
  }
654
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
+
655
793
  function generateRules(candidates, context) {
656
794
  let allRules = []
795
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
657
796
 
658
797
  for (let candidate of candidates) {
659
798
  if (context.notClassCache.has(candidate)) {
660
799
  continue
661
800
  }
662
801
 
663
- if (context.classCache.has(candidate)) {
664
- allRules.push(context.classCache.get(candidate))
802
+ if (context.candidateRuleCache.has(candidate)) {
803
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
665
804
  continue
666
805
  }
667
806
 
@@ -673,47 +812,27 @@ function generateRules(candidates, context) {
673
812
  }
674
813
 
675
814
  context.classCache.set(candidate, matches)
676
- allRules.push(matches)
677
- }
678
815
 
679
- // Strategy based on `tailwindConfig.important`
680
- let strategy = ((important) => {
681
- if (important === true) {
682
- return (rule) => {
683
- rule.walkDecls((d) => {
684
- if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
685
- d.important = true
686
- }
687
- })
688
- }
689
- }
816
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
817
+ context.candidateRuleCache.set(candidate, rules)
690
818
 
691
- if (typeof important === 'string') {
692
- return (rule) => {
693
- rule.selectors = rule.selectors.map((selector) => {
694
- return `${important} ${selector}`
695
- })
696
- }
697
- }
698
- })(context.tailwindConfig.important)
819
+ for (const match of matches) {
820
+ let [{ sort, options }, rule] = match
699
821
 
700
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
701
- if (options.respectImportant) {
702
- if (strategy) {
822
+ if (options.respectImportant && strategy) {
703
823
  let container = postcss.root({ nodes: [rule.clone()] })
704
- container.walkRules((r) => {
705
- if (inKeyframes(r)) {
706
- return
707
- }
708
-
709
- strategy(r)
710
- })
824
+ container.walkRules(strategy)
711
825
  rule = container.nodes[0]
712
826
  }
827
+
828
+ let newEntry = [sort, rule]
829
+ rules.add(newEntry)
830
+ context.ruleCache.add(newEntry)
831
+ allRules.push(newEntry)
713
832
  }
833
+ }
714
834
 
715
- return [sort | context.layerOrder[layer], rule]
716
- })
835
+ return allRules
717
836
  }
718
837
 
719
838
  function isArbitraryValue(input) {