tailwindcss 3.0.22 → 3.1.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 (119) hide show
  1. package/CHANGELOG.md +92 -2
  2. package/colors.d.ts +3 -0
  3. package/defaultConfig.d.ts +3 -0
  4. package/defaultTheme.d.ts +3 -0
  5. package/lib/cli-peer-dependencies.js +10 -5
  6. package/lib/cli.js +266 -203
  7. package/lib/constants.js +8 -8
  8. package/lib/corePluginList.js +1 -0
  9. package/lib/corePlugins.js +1662 -1554
  10. package/lib/css/preflight.css +1 -8
  11. package/lib/featureFlags.js +14 -12
  12. package/lib/index.js +16 -6
  13. package/lib/lib/cacheInvalidation.js +87 -0
  14. package/lib/lib/collapseAdjacentRules.js +30 -15
  15. package/lib/lib/collapseDuplicateDeclarations.js +1 -1
  16. package/lib/lib/defaultExtractor.js +191 -30
  17. package/lib/lib/detectNesting.js +9 -9
  18. package/lib/lib/evaluateTailwindFunctions.js +37 -28
  19. package/lib/lib/expandApplyAtRules.js +379 -189
  20. package/lib/lib/expandTailwindAtRules.js +168 -144
  21. package/lib/lib/generateRules.js +190 -81
  22. package/lib/lib/getModuleDependencies.js +14 -14
  23. package/lib/lib/normalizeTailwindDirectives.js +35 -35
  24. package/lib/lib/partitionApplyAtRules.js +7 -7
  25. package/lib/lib/regex.js +52 -0
  26. package/lib/lib/resolveDefaultsAtRules.js +80 -79
  27. package/lib/lib/setupContextUtils.js +207 -170
  28. package/lib/lib/setupTrackingContext.js +61 -63
  29. package/lib/lib/sharedState.js +11 -8
  30. package/lib/lib/substituteScreenAtRules.js +3 -4
  31. package/lib/postcss-plugins/nesting/README.md +2 -2
  32. package/lib/postcss-plugins/nesting/index.js +1 -1
  33. package/lib/postcss-plugins/nesting/plugin.js +40 -9
  34. package/lib/processTailwindFeatures.js +7 -7
  35. package/lib/public/colors.js +241 -241
  36. package/lib/public/resolve-config.js +5 -5
  37. package/lib/util/buildMediaQuery.js +2 -3
  38. package/lib/util/cloneDeep.js +3 -5
  39. package/lib/util/cloneNodes.js +12 -1
  40. package/lib/util/color.js +42 -51
  41. package/lib/util/createPlugin.js +1 -2
  42. package/lib/util/createUtilityPlugin.js +6 -7
  43. package/lib/util/dataTypes.js +85 -81
  44. package/lib/util/escapeClassName.js +5 -5
  45. package/lib/util/escapeCommas.js +1 -1
  46. package/lib/util/flattenColorPalette.js +4 -7
  47. package/lib/util/formatVariantSelector.js +82 -75
  48. package/lib/util/getAllConfigs.js +15 -10
  49. package/lib/util/hashConfig.js +5 -5
  50. package/lib/util/isKeyframeRule.js +1 -1
  51. package/lib/util/isPlainObject.js +1 -1
  52. package/lib/util/isValidArbitraryValue.js +26 -27
  53. package/lib/util/log.js +9 -10
  54. package/lib/util/nameClass.js +7 -7
  55. package/lib/util/negateValue.js +4 -5
  56. package/lib/util/normalizeConfig.js +68 -58
  57. package/lib/util/normalizeScreens.js +5 -6
  58. package/lib/util/parseAnimationValue.js +56 -57
  59. package/lib/util/parseBoxShadowValue.js +19 -20
  60. package/lib/util/parseDependency.js +32 -32
  61. package/lib/util/parseObjectStyles.js +6 -6
  62. package/lib/util/pluginUtils.js +20 -12
  63. package/lib/util/prefixSelector.js +1 -1
  64. package/lib/util/resolveConfig.js +81 -58
  65. package/lib/util/resolveConfigPath.js +16 -16
  66. package/lib/util/responsive.js +6 -6
  67. package/lib/util/splitAtTopLevelOnly.js +90 -0
  68. package/lib/util/toColorValue.js +1 -1
  69. package/lib/util/toPath.js +2 -2
  70. package/lib/util/transformThemeValue.js +30 -28
  71. package/lib/util/validateConfig.js +21 -0
  72. package/lib/util/withAlphaVariable.js +23 -23
  73. package/package.json +33 -27
  74. package/peers/index.js +7728 -5848
  75. package/plugin.d.ts +11 -0
  76. package/scripts/generate-types.js +52 -0
  77. package/src/cli-peer-dependencies.js +7 -1
  78. package/src/cli.js +118 -24
  79. package/src/corePluginList.js +1 -1
  80. package/src/corePlugins.js +142 -30
  81. package/src/css/preflight.css +1 -8
  82. package/src/featureFlags.js +4 -4
  83. package/src/index.js +15 -1
  84. package/src/lib/cacheInvalidation.js +52 -0
  85. package/src/lib/collapseAdjacentRules.js +21 -2
  86. package/src/lib/defaultExtractor.js +177 -33
  87. package/src/lib/evaluateTailwindFunctions.js +20 -4
  88. package/src/lib/expandApplyAtRules.js +418 -186
  89. package/src/lib/expandTailwindAtRules.js +30 -10
  90. package/src/lib/generateRules.js +142 -51
  91. package/src/lib/regex.js +74 -0
  92. package/src/lib/resolveDefaultsAtRules.js +7 -3
  93. package/src/lib/setupContextUtils.js +142 -87
  94. package/src/lib/setupTrackingContext.js +7 -3
  95. package/src/lib/sharedState.js +2 -0
  96. package/src/postcss-plugins/nesting/README.md +2 -2
  97. package/src/postcss-plugins/nesting/plugin.js +36 -0
  98. package/src/util/cloneNodes.js +14 -1
  99. package/src/util/color.js +25 -21
  100. package/src/util/dataTypes.js +14 -6
  101. package/src/util/formatVariantSelector.js +79 -62
  102. package/src/util/getAllConfigs.js +7 -0
  103. package/src/util/log.js +8 -8
  104. package/src/util/normalizeConfig.js +0 -8
  105. package/src/util/parseBoxShadowValue.js +3 -2
  106. package/src/util/pluginUtils.js +13 -1
  107. package/src/util/resolveConfig.js +66 -22
  108. package/src/util/splitAtTopLevelOnly.js +71 -0
  109. package/src/util/toPath.js +1 -1
  110. package/src/util/transformThemeValue.js +4 -2
  111. package/src/util/validateConfig.js +13 -0
  112. package/src/util/withAlphaVariable.js +1 -1
  113. package/stubs/defaultConfig.stub.js +5 -1
  114. package/stubs/simpleConfig.stub.js +1 -0
  115. package/types/config.d.ts +325 -0
  116. package/types/generated/.gitkeep +0 -0
  117. package/types/generated/colors.d.ts +276 -0
  118. package/types/generated/corePluginList.d.ts +1 -0
  119. package/types/index.d.ts +1 -0
@@ -3,6 +3,7 @@ import * as path from 'path'
3
3
  import postcss from 'postcss'
4
4
  import createUtilityPlugin from './util/createUtilityPlugin'
5
5
  import buildMediaQuery from './util/buildMediaQuery'
6
+ import escapeClassName from './util/escapeClassName'
6
7
  import parseAnimationValue from './util/parseAnimationValue'
7
8
  import flattenColorPalette from './util/flattenColorPalette'
8
9
  import withAlphaVariable, { withAlphaValue } from './util/withAlphaVariable'
@@ -13,6 +14,7 @@ import { version as tailwindVersion } from '../package.json'
13
14
  import log from './util/log'
14
15
  import { normalizeScreens } from './util/normalizeScreens'
15
16
  import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
17
+ import { flagEnabled } from './featureFlags'
16
18
 
17
19
  export let variantPlugins = {
18
20
  pseudoElementVariants: ({ addVariant }) => {
@@ -26,6 +28,8 @@ export let variantPlugins = {
26
28
 
27
29
  addVariant('placeholder', '&::placeholder')
28
30
 
31
+ addVariant('backdrop', '&::backdrop')
32
+
29
33
  addVariant('before', ({ container }) => {
30
34
  container.walkRules((rule) => {
31
35
  let foundContent = false
@@ -57,22 +61,43 @@ export let variantPlugins = {
57
61
  })
58
62
  },
59
63
 
60
- pseudoClassVariants: ({ addVariant }) => {
64
+ pseudoClassVariants: ({ addVariant, config }) => {
61
65
  let pseudoVariants = [
62
66
  // Positional
63
- ['first', ':first-child'],
64
- ['last', ':last-child'],
65
- ['only', ':only-child'],
66
- ['odd', ':nth-child(odd)'],
67
- ['even', ':nth-child(even)'],
67
+ ['first', '&:first-child'],
68
+ ['last', '&:last-child'],
69
+ ['only', '&:only-child'],
70
+ ['odd', '&:nth-child(odd)'],
71
+ ['even', '&:nth-child(even)'],
68
72
  'first-of-type',
69
73
  'last-of-type',
70
74
  'only-of-type',
71
75
 
72
76
  // State
73
- 'visited',
77
+ [
78
+ 'visited',
79
+ ({ container }) => {
80
+ let toRemove = ['--tw-text-opacity', '--tw-border-opacity', '--tw-bg-opacity']
81
+
82
+ container.walkDecls((decl) => {
83
+ if (toRemove.includes(decl.prop)) {
84
+ decl.remove()
85
+
86
+ return
87
+ }
88
+
89
+ for (const varName of toRemove) {
90
+ if (decl.value.includes(`/ var(${varName})`)) {
91
+ decl.value = decl.value.replace(`/ var(${varName})`, '')
92
+ }
93
+ }
94
+ })
95
+
96
+ return '&:visited'
97
+ },
98
+ ],
74
99
  'target',
75
- ['open', '[open]'],
100
+ ['open', '&[open]'],
76
101
 
77
102
  // Forms
78
103
  'default',
@@ -80,6 +105,7 @@ export let variantPlugins = {
80
105
  'indeterminate',
81
106
  'placeholder-shown',
82
107
  'autofill',
108
+ 'optional',
83
109
  'required',
84
110
  'valid',
85
111
  'invalid',
@@ -92,23 +118,41 @@ export let variantPlugins = {
92
118
 
93
119
  // Interactive
94
120
  'focus-within',
95
- 'hover',
121
+ [
122
+ 'hover',
123
+ !flagEnabled(config(), 'hoverOnlyWhenSupported')
124
+ ? '&:hover'
125
+ : '@media (hover: hover) and (pointer: fine) { &:hover }',
126
+ ],
96
127
  'focus',
97
128
  'focus-visible',
98
129
  'active',
130
+ 'enabled',
99
131
  'disabled',
100
- ].map((variant) => (Array.isArray(variant) ? variant : [variant, `:${variant}`]))
132
+ ].map((variant) => (Array.isArray(variant) ? variant : [variant, `&:${variant}`]))
101
133
 
102
134
  for (let [variantName, state] of pseudoVariants) {
103
- addVariant(variantName, `&${state}`)
135
+ addVariant(variantName, (ctx) => {
136
+ let result = typeof state === 'function' ? state(ctx) : state
137
+
138
+ return result
139
+ })
104
140
  }
105
141
 
106
142
  for (let [variantName, state] of pseudoVariants) {
107
- addVariant(`group-${variantName}`, `:merge(.group)${state} &`)
143
+ addVariant(`group-${variantName}`, (ctx) => {
144
+ let result = typeof state === 'function' ? state(ctx) : state
145
+
146
+ return result.replace(/&(\S+)/, ':merge(.group)$1 &')
147
+ })
108
148
  }
109
149
 
110
150
  for (let [variantName, state] of pseudoVariants) {
111
- addVariant(`peer-${variantName}`, `:merge(.peer)${state} ~ &`)
151
+ addVariant(`peer-${variantName}`, (ctx) => {
152
+ let result = typeof state === 'function' ? state(ctx) : state
153
+
154
+ return result.replace(/&(\S+)/, ':merge(.peer)$1 ~ &')
155
+ })
112
156
  }
113
157
  },
114
158
 
@@ -138,7 +182,8 @@ export let variantPlugins = {
138
182
  },
139
183
 
140
184
  darkVariants: ({ config, addVariant }) => {
141
- let mode = config('darkMode', 'media')
185
+ let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'))
186
+
142
187
  if (mode === false) {
143
188
  mode = 'media'
144
189
  log.warn('darkmode-false', [
@@ -149,7 +194,7 @@ export let variantPlugins = {
149
194
  }
150
195
 
151
196
  if (mode === 'class') {
152
- addVariant('dark', '.dark &')
197
+ addVariant('dark', `${className} &`)
153
198
  } else if (mode === 'media') {
154
199
  addVariant('dark', '@media (prefers-color-scheme: dark)')
155
200
  }
@@ -171,6 +216,11 @@ export let variantPlugins = {
171
216
  addVariant('portrait', '@media (orientation: portrait)')
172
217
  addVariant('landscape', '@media (orientation: landscape)')
173
218
  },
219
+
220
+ prefersContrastVariants: ({ addVariant }) => {
221
+ addVariant('contrast-more', '@media (prefers-contrast: more)')
222
+ addVariant('contrast-less', '@media (prefers-contrast: less)')
223
+ },
174
224
  }
175
225
 
176
226
  let cssTransformValue = [
@@ -494,6 +544,41 @@ export let corePlugins = {
494
544
  })
495
545
  },
496
546
 
547
+ borderSpacing: ({ addDefaults, matchUtilities, theme }) => {
548
+ addDefaults('border-spacing', {
549
+ '--tw-border-spacing-x': 0,
550
+ '--tw-border-spacing-y': 0,
551
+ })
552
+
553
+ matchUtilities(
554
+ {
555
+ 'border-spacing': (value) => {
556
+ return {
557
+ '--tw-border-spacing-x': value,
558
+ '--tw-border-spacing-y': value,
559
+ '@defaults border-spacing': {},
560
+ 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)',
561
+ }
562
+ },
563
+ 'border-spacing-x': (value) => {
564
+ return {
565
+ '--tw-border-spacing-x': value,
566
+ '@defaults border-spacing': {},
567
+ 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)',
568
+ }
569
+ },
570
+ 'border-spacing-y': (value) => {
571
+ return {
572
+ '--tw-border-spacing-y': value,
573
+ '@defaults border-spacing': {},
574
+ 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)',
575
+ }
576
+ },
577
+ },
578
+ { values: theme('borderSpacing') }
579
+ )
580
+ },
581
+
497
582
  transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]),
498
583
  translate: createUtilityPlugin(
499
584
  'translate',
@@ -578,8 +663,8 @@ export let corePlugins = {
578
663
  })
579
664
  },
580
665
 
581
- animation: ({ matchUtilities, theme, prefix }) => {
582
- let prefixName = (name) => prefix(`.${name}`).slice(1)
666
+ animation: ({ matchUtilities, theme, config }) => {
667
+ let prefixName = (name) => `${config('prefix')}${escapeClassName(name)}`
583
668
  let keyframes = Object.fromEntries(
584
669
  Object.entries(theme('keyframes') ?? {}).map(([key, value]) => {
585
670
  return [key, { [`@keyframes ${prefixName(key)}`]: value }]
@@ -811,6 +896,7 @@ export let corePlugins = {
811
896
  addUtilities({
812
897
  '.grid-flow-row': { gridAutoFlow: 'row' },
813
898
  '.grid-flow-col': { gridAutoFlow: 'column' },
899
+ '.grid-flow-dense': { gridAutoFlow: 'dense' },
814
900
  '.grid-flow-row-dense': { gridAutoFlow: 'row dense' },
815
901
  '.grid-flow-col-dense': { gridAutoFlow: 'column dense' },
816
902
  })
@@ -1351,7 +1437,8 @@ export let corePlugins = {
1351
1437
 
1352
1438
  return {
1353
1439
  '--tw-gradient-from': toColorValue(value, 'from'),
1354
- '--tw-gradient-stops': `var(--tw-gradient-from), var(--tw-gradient-to, ${transparentToValue})`,
1440
+ '--tw-gradient-to': transparentToValue,
1441
+ '--tw-gradient-stops': `var(--tw-gradient-from), var(--tw-gradient-to)`,
1355
1442
  }
1356
1443
  },
1357
1444
  },
@@ -1363,10 +1450,11 @@ export let corePlugins = {
1363
1450
  let transparentToValue = transparentTo(value)
1364
1451
 
1365
1452
  return {
1453
+ '--tw-gradient-to': transparentToValue,
1366
1454
  '--tw-gradient-stops': `var(--tw-gradient-from), ${toColorValue(
1367
1455
  value,
1368
1456
  'via'
1369
- )}, var(--tw-gradient-to, ${transparentToValue})`,
1457
+ )}, var(--tw-gradient-to)`,
1370
1458
  }
1371
1459
  },
1372
1460
  },
@@ -1489,6 +1577,8 @@ export let corePlugins = {
1489
1577
  '.text-center': { 'text-align': 'center' },
1490
1578
  '.text-right': { 'text-align': 'right' },
1491
1579
  '.text-justify': { 'text-align': 'justify' },
1580
+ '.text-start': { 'text-align': 'start' },
1581
+ '.text-end': { 'text-align': 'end' },
1492
1582
  })
1493
1583
  },
1494
1584
 
@@ -1795,6 +1885,7 @@ export let corePlugins = {
1795
1885
  '.mix-blend-saturation': { 'mix-blend-mode': 'saturation' },
1796
1886
  '.mix-blend-color': { 'mix-blend-mode': 'color' },
1797
1887
  '.mix-blend-luminosity': { 'mix-blend-mode': 'luminosity' },
1888
+ '.mix-blend-plus-lighter': { 'mix-blend-mode': 'plus-lighter' },
1798
1889
  })
1799
1890
  },
1800
1891
 
@@ -1889,13 +1980,24 @@ export let corePlugins = {
1889
1980
  )
1890
1981
  },
1891
1982
 
1892
- ringWidth: ({ matchUtilities, addDefaults, addUtilities, theme }) => {
1893
- let ringOpacityDefault = theme('ringOpacity.DEFAULT', '0.5')
1894
- let ringColorDefault = withAlphaValue(
1895
- theme('ringColor.DEFAULT'),
1896
- ringOpacityDefault,
1897
- `rgb(147 197 253 / ${ringOpacityDefault})`
1898
- )
1983
+ ringWidth: ({ matchUtilities, addDefaults, addUtilities, theme, config }) => {
1984
+ let ringColorDefault = (() => {
1985
+ if (flagEnabled(config(), 'respectDefaultRingColorOpacity')) {
1986
+ return theme('ringColor.DEFAULT')
1987
+ }
1988
+
1989
+ let ringOpacityDefault = theme('ringOpacity.DEFAULT', '0.5')
1990
+
1991
+ if (!theme('ringColor')?.DEFAULT) {
1992
+ return `rgb(147 197 253 / ${ringOpacityDefault})`
1993
+ }
1994
+
1995
+ return withAlphaValue(
1996
+ theme('ringColor')?.DEFAULT,
1997
+ ringOpacityDefault,
1998
+ `rgb(147 197 253 / ${ringOpacityDefault})`
1999
+ )
2000
+ })()
1899
2001
 
1900
2002
  addDefaults('ring-width', {
1901
2003
  '--tw-ring-inset': ' ',
@@ -1931,10 +2033,16 @@ export let corePlugins = {
1931
2033
  })
1932
2034
  },
1933
2035
 
1934
- ringColor: ({ matchUtilities, theme }) => {
2036
+ ringColor: ({ matchUtilities, theme, corePlugins }) => {
1935
2037
  matchUtilities(
1936
2038
  {
1937
2039
  ring: (value) => {
2040
+ if (!corePlugins('ringOpacity')) {
2041
+ return {
2042
+ '--tw-ring-color': toColorValue(value),
2043
+ }
2044
+ }
2045
+
1938
2046
  return withAlphaVariable({
1939
2047
  color: value,
1940
2048
  property: '--tw-ring-color',
@@ -1953,9 +2061,13 @@ export let corePlugins = {
1953
2061
  )
1954
2062
  },
1955
2063
 
1956
- ringOpacity: createUtilityPlugin('ringOpacity', [['ring-opacity', ['--tw-ring-opacity']]], {
1957
- filterDefault: true,
1958
- }),
2064
+ ringOpacity: (helpers) => {
2065
+ let { config } = helpers
2066
+
2067
+ return createUtilityPlugin('ringOpacity', [['ring-opacity', ['--tw-ring-opacity']]], {
2068
+ filterDefault: !flagEnabled(config(), 'respectDefaultRingColorOpacity'),
2069
+ })(helpers)
2070
+ },
1959
2071
  ringOffsetWidth: createUtilityPlugin(
1960
2072
  'ringOffsetWidth',
1961
2073
  [['ring-offset', ['--tw-ring-offset-width']]],
@@ -160,6 +160,7 @@ select,
160
160
  textarea {
161
161
  font-family: inherit; /* 1 */
162
162
  font-size: 100%; /* 1 */
163
+ font-weight: inherit; /* 1 */
163
164
  line-height: inherit; /* 1 */
164
165
  color: inherit; /* 1 */
165
166
  margin: 0; /* 2 */
@@ -357,11 +358,3 @@ video {
357
358
  max-width: 100%;
358
359
  height: auto;
359
360
  }
360
-
361
- /*
362
- Ensure the default browser behavior of the `hidden` attribute.
363
- */
364
-
365
- [hidden] {
366
- display: none;
367
- }
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk'
1
+ import colors from 'picocolors'
2
2
  import log from './util/log'
3
3
 
4
4
  let defaults = {
@@ -6,8 +6,8 @@ let defaults = {
6
6
  }
7
7
 
8
8
  let featureFlags = {
9
- future: [],
10
- experimental: ['optimizeUniversalDefaults'],
9
+ future: ['hoverOnlyWhenSupported', 'respectDefaultRingColorOpacity'],
10
+ experimental: ['optimizeUniversalDefaults', 'matchVariant' /* , 'variantGrouping' */],
11
11
  }
12
12
 
13
13
  export function flagEnabled(config, flag) {
@@ -41,7 +41,7 @@ export function issueFlagNotices(config) {
41
41
 
42
42
  if (experimentalFlagsEnabled(config).length > 0) {
43
43
  let changes = experimentalFlagsEnabled(config)
44
- .map((s) => chalk.yellow(s))
44
+ .map((s) => colors.yellow(s))
45
45
  .join(', ')
46
46
 
47
47
  log.warn('experimental-flags-enabled', [
package/src/index.js CHANGED
@@ -13,7 +13,21 @@ module.exports = function tailwindcss(configOrPath) {
13
13
  return root
14
14
  },
15
15
  function (root, result) {
16
- processTailwindFeatures(setupTrackingContext(configOrPath))(root, result)
16
+ let context = setupTrackingContext(configOrPath)
17
+
18
+ if (root.type === 'document') {
19
+ let roots = root.nodes.filter((node) => node.type === 'root')
20
+
21
+ for (const root of roots) {
22
+ if (root.type === 'root') {
23
+ processTailwindFeatures(context)(root, result)
24
+ }
25
+ }
26
+
27
+ return
28
+ }
29
+
30
+ processTailwindFeatures(context)(root, result)
17
31
  },
18
32
  env.DEBUG &&
19
33
  function (root) {
@@ -0,0 +1,52 @@
1
+ import crypto from 'crypto'
2
+ import * as sharedState from './sharedState'
3
+
4
+ /**
5
+ * Calculate the hash of a string.
6
+ *
7
+ * This doesn't need to be cryptographically secure or
8
+ * anything like that since it's used only to detect
9
+ * when the CSS changes to invalidate the context.
10
+ *
11
+ * This is wrapped in a try/catch because it's really dependent
12
+ * on how Node itself is build and the environment and OpenSSL
13
+ * version / build that is installed on the user's machine.
14
+ *
15
+ * Based on the environment this can just outright fail.
16
+ *
17
+ * See https://github.com/nodejs/node/issues/40455
18
+ *
19
+ * @param {string} str
20
+ */
21
+ function getHash(str) {
22
+ try {
23
+ return crypto.createHash('md5').update(str, 'utf-8').digest('binary')
24
+ } catch (err) {
25
+ return ''
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Determine if the CSS tree is different from the
31
+ * previous version for the given `sourcePath`.
32
+ *
33
+ * @param {string} sourcePath
34
+ * @param {import('postcss').Node} root
35
+ */
36
+ export function hasContentChanged(sourcePath, root) {
37
+ let css = root.toString()
38
+
39
+ // We only care about files with @tailwind directives
40
+ // Other files use an existing context
41
+ if (!css.includes('@tailwind')) {
42
+ return false
43
+ }
44
+
45
+ let existingHash = sharedState.sourceHashMap.get(sourcePath)
46
+ let rootHash = getHash(css)
47
+ let didChange = existingHash !== rootHash
48
+
49
+ sharedState.sourceHashMap.set(sourcePath, rootHash)
50
+
51
+ return didChange
52
+ }
@@ -5,7 +5,7 @@ let comparisonMap = {
5
5
  let types = new Set(Object.keys(comparisonMap))
6
6
 
7
7
  export default function collapseAdjacentRules() {
8
- return (root) => {
8
+ function collapseRulesIn(root) {
9
9
  let currentRule = null
10
10
  root.each((node) => {
11
11
  if (!types.has(node.type)) {
@@ -29,11 +29,30 @@ export default function collapseAdjacentRules() {
29
29
  (currentRule[property] ?? '').replace(/\s+/g, ' ')
30
30
  )
31
31
  ) {
32
- currentRule.append(node.nodes)
32
+ // An AtRule may not have children (for example if we encounter duplicate @import url(…) rules)
33
+ if (node.nodes) {
34
+ currentRule.append(node.nodes)
35
+ }
36
+
33
37
  node.remove()
34
38
  } else {
35
39
  currentRule = node
36
40
  }
37
41
  })
42
+
43
+ // After we've collapsed adjacent rules & at-rules, we need to collapse
44
+ // adjacent rules & at-rules that are children of at-rules.
45
+ // We do not care about nesting rules because Tailwind CSS
46
+ // explicitly does not handle rule nesting on its own as
47
+ // the user is expected to use a nesting plugin
48
+ root.each((node) => {
49
+ if (node.type === 'atrule') {
50
+ collapseRulesIn(node)
51
+ }
52
+ })
53
+ }
54
+
55
+ return (root) => {
56
+ collapseRulesIn(root)
38
57
  }
39
58
  }