tailwindcss 0.0.0-insiders.ea139f2 → 0.0.0-insiders.ea4e1cd

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 (236) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +15 -7
  3. package/colors.d.ts +3 -0
  4. package/colors.js +2 -1
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultConfig.js +2 -1
  7. package/defaultTheme.d.ts +4 -0
  8. package/defaultTheme.js +2 -1
  9. package/lib/cli/build/deps.js +62 -0
  10. package/lib/cli/build/index.js +54 -0
  11. package/lib/cli/build/plugin.js +378 -0
  12. package/lib/cli/build/utils.js +88 -0
  13. package/lib/cli/build/watching.js +182 -0
  14. package/lib/cli/help/index.js +73 -0
  15. package/lib/cli/index.js +230 -0
  16. package/lib/cli/init/index.js +63 -0
  17. package/lib/cli-peer-dependencies.js +28 -7
  18. package/lib/cli.js +4 -703
  19. package/lib/corePluginList.js +12 -3
  20. package/lib/corePlugins.js +2373 -1863
  21. package/lib/css/preflight.css +10 -8
  22. package/lib/featureFlags.js +49 -26
  23. package/lib/index.js +1 -31
  24. package/lib/lib/cacheInvalidation.js +92 -0
  25. package/lib/lib/collapseAdjacentRules.js +30 -10
  26. package/lib/lib/collapseDuplicateDeclarations.js +60 -4
  27. package/lib/lib/content.js +181 -0
  28. package/lib/lib/defaultExtractor.js +243 -0
  29. package/lib/lib/detectNesting.js +21 -10
  30. package/lib/lib/evaluateTailwindFunctions.js +115 -50
  31. package/lib/lib/expandApplyAtRules.js +467 -161
  32. package/lib/lib/expandTailwindAtRules.js +160 -133
  33. package/lib/lib/findAtConfigPath.js +46 -0
  34. package/lib/lib/generateRules.js +553 -200
  35. package/lib/lib/getModuleDependencies.js +88 -37
  36. package/lib/lib/load-config.js +42 -0
  37. package/lib/lib/normalizeTailwindDirectives.js +46 -33
  38. package/lib/lib/offsets.js +306 -0
  39. package/lib/lib/partitionApplyAtRules.js +58 -0
  40. package/lib/lib/regex.js +74 -0
  41. package/lib/lib/remap-bitfield.js +89 -0
  42. package/lib/lib/resolveDefaultsAtRules.js +98 -58
  43. package/lib/lib/setupContextUtils.js +773 -321
  44. package/lib/lib/setupTrackingContext.js +70 -75
  45. package/lib/lib/sharedState.js +78 -10
  46. package/lib/lib/substituteScreenAtRules.js +14 -10
  47. package/lib/oxide/cli/build/deps.js +89 -0
  48. package/lib/oxide/cli/build/index.js +53 -0
  49. package/lib/oxide/cli/build/plugin.js +375 -0
  50. package/lib/oxide/cli/build/utils.js +87 -0
  51. package/lib/oxide/cli/build/watching.js +179 -0
  52. package/lib/oxide/cli/help/index.js +72 -0
  53. package/lib/oxide/cli/index.js +214 -0
  54. package/lib/oxide/cli/init/index.js +52 -0
  55. package/lib/oxide/cli.js +5 -0
  56. package/lib/oxide/postcss-plugin.js +2 -0
  57. package/lib/plugin.js +98 -0
  58. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  59. package/lib/postcss-plugins/nesting/index.js +21 -0
  60. package/lib/postcss-plugins/nesting/plugin.js +89 -0
  61. package/lib/processTailwindFeatures.js +39 -26
  62. package/lib/public/colors.js +272 -246
  63. package/lib/public/create-plugin.js +9 -5
  64. package/lib/public/default-config.js +10 -6
  65. package/lib/public/default-theme.js +10 -6
  66. package/lib/public/load-config.js +12 -0
  67. package/lib/public/resolve-config.js +11 -6
  68. package/lib/util/applyImportantSelector.js +36 -0
  69. package/lib/util/bigSign.js +6 -1
  70. package/lib/util/buildMediaQuery.js +13 -6
  71. package/lib/util/cloneDeep.js +9 -6
  72. package/lib/util/cloneNodes.js +23 -3
  73. package/lib/util/color.js +70 -38
  74. package/lib/util/colorNames.js +752 -0
  75. package/lib/util/configurePlugins.js +7 -2
  76. package/lib/util/createPlugin.js +8 -6
  77. package/lib/util/createUtilityPlugin.js +16 -16
  78. package/lib/util/dataTypes.js +173 -108
  79. package/lib/util/defaults.js +14 -3
  80. package/lib/util/escapeClassName.js +13 -8
  81. package/lib/util/escapeCommas.js +7 -2
  82. package/lib/util/flattenColorPalette.js +11 -12
  83. package/lib/util/formatVariantSelector.js +228 -151
  84. package/lib/util/getAllConfigs.js +33 -12
  85. package/lib/util/hashConfig.js +9 -4
  86. package/lib/util/isKeyframeRule.js +7 -2
  87. package/lib/util/isPlainObject.js +7 -2
  88. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +25 -15
  89. package/lib/util/log.js +27 -13
  90. package/lib/util/nameClass.js +27 -10
  91. package/lib/util/negateValue.js +25 -8
  92. package/lib/util/normalizeConfig.js +139 -65
  93. package/lib/util/normalizeScreens.js +131 -11
  94. package/lib/util/parseAnimationValue.js +44 -40
  95. package/lib/util/parseBoxShadowValue.js +34 -23
  96. package/lib/util/parseDependency.js +39 -55
  97. package/lib/util/parseGlob.js +36 -0
  98. package/lib/util/parseObjectStyles.js +15 -10
  99. package/lib/util/pluginUtils.js +159 -69
  100. package/lib/util/prefixSelector.js +30 -12
  101. package/lib/util/pseudoElements.js +229 -0
  102. package/lib/util/removeAlphaVariables.js +31 -0
  103. package/lib/util/resolveConfig.js +97 -75
  104. package/lib/util/resolveConfigPath.js +30 -12
  105. package/lib/util/responsive.js +11 -6
  106. package/lib/util/splitAtTopLevelOnly.js +51 -0
  107. package/lib/util/tap.js +6 -1
  108. package/lib/util/toColorValue.js +7 -3
  109. package/lib/util/toPath.js +26 -3
  110. package/lib/util/transformThemeValue.js +40 -30
  111. package/lib/util/validateConfig.js +37 -0
  112. package/lib/util/validateFormalSyntax.js +26 -0
  113. package/lib/util/withAlphaVariable.js +27 -15
  114. package/loadConfig.d.ts +4 -0
  115. package/loadConfig.js +2 -0
  116. package/nesting/index.js +2 -12
  117. package/package.json +66 -57
  118. package/peers/index.js +75964 -55560
  119. package/plugin.d.ts +11 -0
  120. package/plugin.js +2 -1
  121. package/resolveConfig.d.ts +12 -0
  122. package/resolveConfig.js +2 -1
  123. package/scripts/generate-types.js +105 -0
  124. package/scripts/release-channel.js +18 -0
  125. package/scripts/release-notes.js +21 -0
  126. package/scripts/swap-engines.js +40 -0
  127. package/scripts/type-utils.js +27 -0
  128. package/src/cli/build/deps.js +56 -0
  129. package/src/cli/build/index.js +49 -0
  130. package/src/cli/build/plugin.js +444 -0
  131. package/src/cli/build/utils.js +76 -0
  132. package/src/cli/build/watching.js +229 -0
  133. package/src/cli/help/index.js +70 -0
  134. package/src/cli/index.js +216 -0
  135. package/src/cli/init/index.js +79 -0
  136. package/src/cli-peer-dependencies.js +7 -1
  137. package/src/cli.js +4 -765
  138. package/src/corePluginList.js +1 -1
  139. package/src/corePlugins.js +786 -306
  140. package/src/css/preflight.css +10 -8
  141. package/src/featureFlags.js +21 -5
  142. package/src/index.js +1 -34
  143. package/src/lib/cacheInvalidation.js +52 -0
  144. package/src/lib/collapseAdjacentRules.js +21 -2
  145. package/src/lib/collapseDuplicateDeclarations.js +66 -1
  146. package/src/lib/content.js +208 -0
  147. package/src/lib/defaultExtractor.js +217 -0
  148. package/src/lib/detectNesting.js +9 -1
  149. package/src/lib/evaluateTailwindFunctions.js +79 -8
  150. package/src/lib/expandApplyAtRules.js +515 -153
  151. package/src/lib/expandTailwindAtRules.js +115 -86
  152. package/src/lib/findAtConfigPath.js +48 -0
  153. package/src/lib/generateRules.js +545 -147
  154. package/src/lib/getModuleDependencies.js +70 -30
  155. package/src/lib/load-config.ts +31 -0
  156. package/src/lib/normalizeTailwindDirectives.js +7 -1
  157. package/src/lib/offsets.js +373 -0
  158. package/src/lib/partitionApplyAtRules.js +52 -0
  159. package/src/lib/regex.js +74 -0
  160. package/src/lib/remap-bitfield.js +82 -0
  161. package/src/lib/resolveDefaultsAtRules.js +59 -17
  162. package/src/lib/setupContextUtils.js +701 -175
  163. package/src/lib/setupTrackingContext.js +51 -62
  164. package/src/lib/sharedState.js +58 -7
  165. package/src/oxide/cli/build/deps.ts +91 -0
  166. package/src/oxide/cli/build/index.ts +47 -0
  167. package/src/oxide/cli/build/plugin.ts +442 -0
  168. package/src/oxide/cli/build/utils.ts +74 -0
  169. package/src/oxide/cli/build/watching.ts +225 -0
  170. package/src/oxide/cli/help/index.ts +69 -0
  171. package/src/oxide/cli/index.ts +204 -0
  172. package/src/oxide/cli/init/index.ts +59 -0
  173. package/src/oxide/cli.ts +1 -0
  174. package/src/oxide/postcss-plugin.ts +1 -0
  175. package/src/plugin.js +107 -0
  176. package/src/postcss-plugins/nesting/README.md +42 -0
  177. package/src/postcss-plugins/nesting/index.js +13 -0
  178. package/src/postcss-plugins/nesting/plugin.js +80 -0
  179. package/src/processTailwindFeatures.js +12 -2
  180. package/src/public/colors.js +22 -0
  181. package/src/public/default-config.js +1 -1
  182. package/src/public/default-theme.js +2 -2
  183. package/src/public/load-config.js +2 -0
  184. package/src/util/applyImportantSelector.js +27 -0
  185. package/src/util/buildMediaQuery.js +5 -3
  186. package/src/util/cloneNodes.js +19 -2
  187. package/src/util/color.js +44 -12
  188. package/src/util/colorNames.js +150 -0
  189. package/src/util/dataTypes.js +51 -16
  190. package/src/util/defaults.js +6 -0
  191. package/src/util/formatVariantSelector.js +264 -144
  192. package/src/util/getAllConfigs.js +21 -2
  193. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  194. package/src/util/log.js +11 -7
  195. package/src/util/nameClass.js +4 -0
  196. package/src/util/negateValue.js +11 -3
  197. package/src/util/normalizeConfig.js +57 -5
  198. package/src/util/normalizeScreens.js +105 -7
  199. package/src/util/parseBoxShadowValue.js +4 -3
  200. package/src/util/parseDependency.js +37 -42
  201. package/src/util/parseGlob.js +24 -0
  202. package/src/util/pluginUtils.js +123 -24
  203. package/src/util/prefixSelector.js +30 -10
  204. package/src/util/pseudoElements.js +170 -0
  205. package/src/util/removeAlphaVariables.js +24 -0
  206. package/src/util/resolveConfig.js +74 -26
  207. package/src/util/resolveConfigPath.js +12 -1
  208. package/src/util/splitAtTopLevelOnly.js +52 -0
  209. package/src/util/toPath.js +23 -1
  210. package/src/util/transformThemeValue.js +13 -3
  211. package/src/util/validateConfig.js +26 -0
  212. package/src/util/validateFormalSyntax.js +34 -0
  213. package/src/util/withAlphaVariable.js +1 -1
  214. package/stubs/.gitignore +1 -0
  215. package/stubs/.prettierrc.json +6 -0
  216. package/stubs/{defaultConfig.stub.js → config.full.js} +206 -166
  217. package/stubs/postcss.config.js +6 -0
  218. package/stubs/tailwind.config.cjs +2 -0
  219. package/stubs/tailwind.config.js +2 -0
  220. package/stubs/tailwind.config.ts +3 -0
  221. package/types/config.d.ts +368 -0
  222. package/types/generated/.gitkeep +0 -0
  223. package/types/generated/colors.d.ts +298 -0
  224. package/types/generated/corePluginList.d.ts +1 -0
  225. package/types/generated/default-theme.d.ts +371 -0
  226. package/types/index.d.ts +7 -0
  227. package/CHANGELOG.md +0 -1843
  228. package/lib/constants.js +0 -37
  229. package/lib/lib/setupWatchingContext.js +0 -288
  230. package/nesting/plugin.js +0 -41
  231. package/scripts/install-integrations.js +0 -27
  232. package/scripts/rebuildFixtures.js +0 -68
  233. package/src/constants.js +0 -17
  234. package/src/lib/setupWatchingContext.js +0 -311
  235. /package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -0
  236. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -11,42 +11,93 @@ import isPlainObject from '../util/isPlainObject'
11
11
  import escapeClassName from '../util/escapeClassName'
12
12
  import nameClass, { formatClass } from '../util/nameClass'
13
13
  import { coerceValue } from '../util/pluginUtils'
14
- import bigSign from '../util/bigSign'
15
14
  import { variantPlugins, corePlugins } from '../corePlugins'
16
15
  import * as sharedState from './sharedState'
17
16
  import { env } from './sharedState'
18
17
  import { toPath } from '../util/toPath'
19
18
  import log from '../util/log'
20
19
  import negateValue from '../util/negateValue'
21
- import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
-
23
- function parseVariantFormatString(input) {
24
- if (input.includes('{')) {
25
- if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
20
+ import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPropertyValue'
21
+ import { generateRules, getClassNameFromSelector } from './generateRules'
22
+ import { hasContentChanged } from './cacheInvalidation.js'
23
+ import { Offsets } from './offsets.js'
24
+ import { flagEnabled } from '../featureFlags.js'
25
+ import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
26
+
27
+ const VARIANT_TYPES = {
28
+ AddVariant: Symbol.for('ADD_VARIANT'),
29
+ MatchVariant: Symbol.for('MATCH_VARIANT'),
30
+ }
26
31
 
27
- return input
28
- .split(/{(.*)}/gim)
29
- .flatMap((line) => parseVariantFormatString(line))
30
- .filter(Boolean)
31
- }
32
+ const VARIANT_INFO = {
33
+ Base: 1 << 0,
34
+ Dynamic: 1 << 1,
35
+ }
32
36
 
33
- return [input.trim()]
37
+ function prefix(context, selector) {
38
+ let prefix = context.tailwindConfig.prefix
39
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
34
40
  }
35
41
 
36
- function isBalanced(input) {
37
- let count = 0
42
+ function normalizeOptionTypes({ type = 'any', ...options }) {
43
+ let types = [].concat(type)
38
44
 
39
- for (let char of input) {
40
- if (char === '{') {
41
- count++
45
+ return {
46
+ ...options,
47
+ types: types.map((type) => {
48
+ if (Array.isArray(type)) {
49
+ return { type: type[0], ...type[1] }
50
+ }
51
+ return { type, preferOnConflict: false }
52
+ }),
53
+ }
54
+ }
55
+
56
+ function parseVariantFormatString(input) {
57
+ /** @type {string[]} */
58
+ let parts = []
59
+
60
+ // When parsing whitespace around special characters are insignificant
61
+ // However, _inside_ of a variant they could be
62
+ // Because the selector could look like this
63
+ // @media { &[data-name="foo bar"] }
64
+ // This is why we do not skip whitespace
65
+
66
+ let current = ''
67
+ let depth = 0
68
+
69
+ for (let idx = 0; idx < input.length; idx++) {
70
+ let char = input[idx]
71
+
72
+ if (char === '\\') {
73
+ // Escaped characters are not special
74
+ current += '\\' + input[++idx]
75
+ } else if (char === '{') {
76
+ // Nested rule: start
77
+ ++depth
78
+ parts.push(current.trim())
79
+ current = ''
42
80
  } else if (char === '}') {
43
- if (--count < 0) {
44
- return false // unbalanced
81
+ // Nested rule: end
82
+ if (--depth < 0) {
83
+ throw new Error(`Your { and } are unbalanced.`)
45
84
  }
85
+
86
+ parts.push(current.trim())
87
+ current = ''
88
+ } else {
89
+ // Normal character
90
+ current += char
46
91
  }
47
92
  }
48
93
 
49
- return count === 0
94
+ if (current.length > 0) {
95
+ parts.push(current.trim())
96
+ }
97
+
98
+ parts = parts.filter((part) => part !== '')
99
+
100
+ return parts
50
101
  }
51
102
 
52
103
  function insertInto(list, value, { before = [] } = {}) {
@@ -78,50 +129,82 @@ function parseStyles(styles) {
78
129
  })
79
130
  }
80
131
 
81
- function getClasses(selector) {
132
+ function getClasses(selector, mutate) {
82
133
  let parser = selectorParser((selectors) => {
83
134
  let allClasses = []
135
+
136
+ if (mutate) {
137
+ mutate(selectors)
138
+ }
139
+
84
140
  selectors.walkClasses((classNode) => {
85
141
  allClasses.push(classNode.value)
86
142
  })
143
+
87
144
  return allClasses
88
145
  })
89
146
  return parser.transformSync(selector)
90
147
  }
91
148
 
92
- function extractCandidates(node) {
149
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
93
150
  let classes = []
94
151
 
152
+ // Handle normal rules
95
153
  if (node.type === 'rule') {
154
+ // Ignore everything inside a :not(...). This allows you to write code like
155
+ // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
156
+ // not generated it. But now we will ignore everything inside a `:not`, so
157
+ // that it still gets generated.
158
+ function ignoreNot(selectors) {
159
+ selectors.walkPseudos((pseudo) => {
160
+ if (pseudo.value === ':not') {
161
+ pseudo.remove()
162
+ }
163
+ })
164
+ }
165
+
96
166
  for (let selector of node.selectors) {
97
- let classCandidates = getClasses(selector)
167
+ let classCandidates = getClasses(selector, ignoreNot)
98
168
  // At least one of the selectors contains non-"on-demandable" candidates.
99
- if (classCandidates.length === 0) return []
169
+ if (classCandidates.length === 0) {
170
+ state.containsNonOnDemandable = true
171
+ }
100
172
 
101
- classes = [...classes, ...classCandidates]
173
+ for (let classCandidate of classCandidates) {
174
+ classes.push(classCandidate)
175
+ }
102
176
  }
103
- return classes
104
177
  }
105
178
 
106
- if (node.type === 'atrule') {
179
+ // Handle at-rules (which contains nested rules)
180
+ else if (node.type === 'atrule') {
107
181
  node.walkRules((rule) => {
108
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
182
+ for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
183
+ classes.push(classCandidate)
184
+ }
109
185
  })
110
186
  }
111
187
 
188
+ if (depth === 0) {
189
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
190
+ }
191
+
112
192
  return classes
113
193
  }
114
194
 
115
195
  function withIdentifiers(styles) {
116
196
  return parseStyles(styles).flatMap((node) => {
117
197
  let nodeMap = new Map()
118
- let candidates = extractCandidates(node)
198
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
119
199
 
120
- // If this isn't "on-demandable", assign it a universal candidate.
121
- if (candidates.length === 0) {
122
- return [['*', node]]
200
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
201
+ if (containsNonOnDemandableSelectors) {
202
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
123
203
  }
124
204
 
205
+ // However, it could be that it also contains "on-demandable" candidates.
206
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
207
+ // `@apply foo` for example.
125
208
  return candidates.map((c) => {
126
209
  if (!nodeMap.has(node)) {
127
210
  nodeMap.set(node, node)
@@ -131,6 +214,41 @@ function withIdentifiers(styles) {
131
214
  })
132
215
  }
133
216
 
217
+ export function isValidVariantFormatString(format) {
218
+ return format.startsWith('@') || format.includes('&')
219
+ }
220
+
221
+ export function parseVariant(variant) {
222
+ variant = variant
223
+ .replace(/\n+/g, '')
224
+ .replace(/\s{1,}/g, ' ')
225
+ .trim()
226
+
227
+ let fns = parseVariantFormatString(variant)
228
+ .map((str) => {
229
+ if (!str.startsWith('@')) {
230
+ return ({ format }) => format(str)
231
+ }
232
+
233
+ let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str)
234
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() }))
235
+ })
236
+ .reverse()
237
+
238
+ return (api) => {
239
+ for (let fn of fns) {
240
+ fn(api)
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ *
247
+ * @param {any} tailwindConfig
248
+ * @param {any} context
249
+ * @param {object} param2
250
+ * @param {Offsets} param2.offsets
251
+ */
134
252
  function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
135
253
  function getConfigValue(path, defaultValue) {
136
254
  return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
@@ -141,8 +259,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
141
259
  }
142
260
 
143
261
  function prefixIdentifier(identifier, options) {
144
- if (identifier === '*') {
145
- return '*'
262
+ if (identifier === sharedState.NOT_ON_DEMAND) {
263
+ return sharedState.NOT_ON_DEMAND
146
264
  }
147
265
 
148
266
  if (!options.respectPrefix) {
@@ -152,51 +270,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
152
270
  return context.tailwindConfig.prefix + identifier
153
271
  }
154
272
 
155
- return {
156
- addVariant(variantName, variantFunctions, options = {}) {
157
- variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
158
- if (typeof variantFunction !== 'string') {
159
- // Safelist public API functions
160
- return ({ modifySelectors, container, separator }) => {
161
- return variantFunction({ modifySelectors, container, separator })
162
- }
163
- }
164
-
165
- variantFunction = variantFunction
166
- .replace(/\n+/g, '')
167
- .replace(/\s{1,}/g, ' ')
168
- .trim()
169
-
170
- let fns = parseVariantFormatString(variantFunction)
171
- .map((str) => {
172
- if (!str.startsWith('@')) {
173
- return ({ format }) => format(str)
174
- }
175
-
176
- let [, name, params] = /@(.*?) (.*)/g.exec(str)
177
- return ({ wrap }) => wrap(postcss.atRule({ name, params }))
178
- })
179
- .reverse()
180
-
181
- return (api) => {
182
- for (let fn of fns) {
183
- fn(api)
184
- }
185
- }
186
- })
273
+ function resolveThemeValue(path, defaultValue, opts = {}) {
274
+ let parts = toPath(path)
275
+ let value = getConfigValue(['theme', ...parts], defaultValue)
276
+ return transformThemeValue(parts[0])(value, opts)
277
+ }
187
278
 
188
- insertInto(variantList, variantName, options)
189
- variantMap.set(variantName, variantFunctions)
190
- },
279
+ let variantIdentifier = 0
280
+ let api = {
191
281
  postcss,
192
282
  prefix: applyConfiguredPrefix,
193
283
  e: escapeClassName,
194
284
  config: getConfigValue,
195
- theme(path, defaultValue) {
196
- const [pathRoot, ...subPaths] = toPath(path)
197
- const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
198
- return transformThemeValue(pathRoot)(value)
199
- },
285
+ theme: resolveThemeValue,
200
286
  corePlugins: (path) => {
201
287
  if (Array.isArray(tailwindConfig.corePlugins)) {
202
288
  return tailwindConfig.corePlugins.includes(path)
@@ -208,21 +294,31 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
208
294
  // Preserved for backwards compatibility but not used in v3.0+
209
295
  return []
210
296
  },
211
- addUserCss(userCss) {
212
- for (let [identifier, rule] of withIdentifiers(userCss)) {
213
- let offset = offsets.user++
297
+ addBase(base) {
298
+ for (let [identifier, rule] of withIdentifiers(base)) {
299
+ let prefixedIdentifier = prefixIdentifier(identifier, {})
300
+ let offset = offsets.create('base')
214
301
 
215
- if (!context.candidateRuleMap.has(identifier)) {
216
- context.candidateRuleMap.set(identifier, [])
302
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
303
+ context.candidateRuleMap.set(prefixedIdentifier, [])
217
304
  }
218
305
 
219
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
306
+ context.candidateRuleMap
307
+ .get(prefixedIdentifier)
308
+ .push([{ sort: offset, layer: 'base' }, rule])
220
309
  }
221
310
  },
222
- addBase(base) {
223
- for (let [identifier, rule] of withIdentifiers(base)) {
311
+ /**
312
+ * @param {string} group
313
+ * @param {Record<string, string | string[]>} declarations
314
+ */
315
+ addDefaults(group, declarations) {
316
+ const groups = {
317
+ [`@defaults ${group}`]: declarations,
318
+ }
319
+
320
+ for (let [identifier, rule] of withIdentifiers(groups)) {
224
321
  let prefixedIdentifier = prefixIdentifier(identifier, {})
225
- let offset = offsets.base++
226
322
 
227
323
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
228
324
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -230,11 +326,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
326
 
231
327
  context.candidateRuleMap
232
328
  .get(prefixedIdentifier)
233
- .push([{ sort: offset, layer: 'base' }, rule])
329
+ .push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule])
234
330
  }
235
331
  },
236
332
  addComponents(components, options) {
237
333
  let defaultOptions = {
334
+ preserveSource: false,
238
335
  respectPrefix: true,
239
336
  respectImportant: false,
240
337
  }
@@ -243,7 +340,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
243
340
 
244
341
  for (let [identifier, rule] of withIdentifiers(components)) {
245
342
  let prefixedIdentifier = prefixIdentifier(identifier, options)
246
- let offset = offsets.components++
247
343
 
248
344
  classList.add(prefixedIdentifier)
249
345
 
@@ -253,11 +349,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
253
349
 
254
350
  context.candidateRuleMap
255
351
  .get(prefixedIdentifier)
256
- .push([{ sort: offset, layer: 'components', options }, rule])
352
+ .push([{ sort: offsets.create('components'), layer: 'components', options }, rule])
257
353
  }
258
354
  },
259
355
  addUtilities(utilities, options) {
260
356
  let defaultOptions = {
357
+ preserveSource: false,
261
358
  respectPrefix: true,
262
359
  respectImportant: true,
263
360
  }
@@ -266,7 +363,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
266
363
 
267
364
  for (let [identifier, rule] of withIdentifiers(utilities)) {
268
365
  let prefixedIdentifier = prefixIdentifier(identifier, options)
269
- let offset = offsets.utilities++
270
366
 
271
367
  classList.add(prefixedIdentifier)
272
368
 
@@ -276,18 +372,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
276
372
 
277
373
  context.candidateRuleMap
278
374
  .get(prefixedIdentifier)
279
- .push([{ sort: offset, layer: 'utilities', options }, rule])
375
+ .push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule])
280
376
  }
281
377
  },
282
378
  matchUtilities: function (utilities, options) {
283
379
  let defaultOptions = {
284
380
  respectPrefix: true,
285
381
  respectImportant: true,
382
+ modifiers: false,
286
383
  }
287
384
 
288
- options = { ...defaultOptions, ...options }
385
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
289
386
 
290
- let offset = offsets.utilities++
387
+ let offset = offsets.create('utilities')
291
388
 
292
389
  for (let identifier in utilities) {
293
390
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -296,24 +393,51 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
296
393
  classList.add([prefixedIdentifier, options])
297
394
 
298
395
  function wrapped(modifier, { isOnlyPlugin }) {
299
- let { type = 'any' } = options
300
- type = [].concat(type)
301
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
396
+ let [value, coercedType, utilityModifier] = coerceValue(
397
+ options.types,
398
+ modifier,
399
+ options,
400
+ tailwindConfig
401
+ )
302
402
 
303
403
  if (value === undefined) {
304
404
  return []
305
405
  }
306
406
 
307
- if (!type.includes(coercedType) && !isOnlyPlugin) {
308
- return []
407
+ if (!options.types.some(({ type }) => type === coercedType)) {
408
+ if (isOnlyPlugin) {
409
+ log.warn([
410
+ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
411
+ `You can safely update it to \`${identifier}-${modifier.replace(
412
+ coercedType + ':',
413
+ ''
414
+ )}\`.`,
415
+ ])
416
+ } else {
417
+ return []
418
+ }
309
419
  }
310
420
 
311
- if (!isValidArbitraryValue(value)) {
421
+ if (!isSyntacticallyValidPropertyValue(value)) {
312
422
  return []
313
423
  }
314
424
 
425
+ let extras = {
426
+ get modifier() {
427
+ if (!options.modifiers) {
428
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
429
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
430
+ ])
431
+ }
432
+
433
+ return utilityModifier
434
+ },
435
+ }
436
+
437
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
438
+
315
439
  let ruleSets = []
316
- .concat(rule(value))
440
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
317
441
  .filter(Boolean)
318
442
  .map((declaration) => ({
319
443
  [nameClass(identifier, modifier)]: declaration,
@@ -335,11 +459,12 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
335
459
  let defaultOptions = {
336
460
  respectPrefix: true,
337
461
  respectImportant: false,
462
+ modifiers: false,
338
463
  }
339
464
 
340
- options = { ...defaultOptions, ...options }
465
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
341
466
 
342
- let offset = offsets.components++
467
+ let offset = offsets.create('components')
343
468
 
344
469
  for (let identifier in components) {
345
470
  let prefixedIdentifier = prefixIdentifier(identifier, options)
@@ -348,15 +473,18 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
348
473
  classList.add([prefixedIdentifier, options])
349
474
 
350
475
  function wrapped(modifier, { isOnlyPlugin }) {
351
- let { type = 'any' } = options
352
- type = [].concat(type)
353
- let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
476
+ let [value, coercedType, utilityModifier] = coerceValue(
477
+ options.types,
478
+ modifier,
479
+ options,
480
+ tailwindConfig
481
+ )
354
482
 
355
483
  if (value === undefined) {
356
484
  return []
357
485
  }
358
486
 
359
- if (!type.includes(coercedType)) {
487
+ if (!options.types.some(({ type }) => type === coercedType)) {
360
488
  if (isOnlyPlugin) {
361
489
  log.warn([
362
490
  `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
@@ -370,12 +498,26 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
370
498
  }
371
499
  }
372
500
 
373
- if (!isValidArbitraryValue(value)) {
501
+ if (!isSyntacticallyValidPropertyValue(value)) {
374
502
  return []
375
503
  }
376
504
 
505
+ let extras = {
506
+ get modifier() {
507
+ if (!options.modifiers) {
508
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
509
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
510
+ ])
511
+ }
512
+
513
+ return utilityModifier
514
+ },
515
+ }
516
+
517
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
518
+
377
519
  let ruleSets = []
378
- .concat(rule(value))
520
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
379
521
  .filter(Boolean)
380
522
  .map((declaration) => ({
381
523
  [nameClass(identifier, modifier)]: declaration,
@@ -393,7 +535,109 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
393
535
  context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
394
536
  }
395
537
  },
538
+ addVariant(variantName, variantFunctions, options = {}) {
539
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
540
+ if (typeof variantFunction !== 'string') {
541
+ // Safelist public API functions
542
+ return (api = {}) => {
543
+ let { args, modifySelectors, container, separator, wrap, format } = api
544
+ let result = variantFunction(
545
+ Object.assign(
546
+ { modifySelectors, container, separator },
547
+ options.type === VARIANT_TYPES.MatchVariant && { args, wrap, format }
548
+ )
549
+ )
550
+
551
+ if (typeof result === 'string' && !isValidVariantFormatString(result)) {
552
+ throw new Error(
553
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
554
+ )
555
+ }
556
+
557
+ if (Array.isArray(result)) {
558
+ return result
559
+ .filter((variant) => typeof variant === 'string')
560
+ .map((variant) => parseVariant(variant))
561
+ }
562
+
563
+ // result may be undefined with legacy variants that use APIs like `modifySelectors`
564
+ // result may also be a postcss node if someone was returning the result from `modifySelectors`
565
+ return result && typeof result === 'string' && parseVariant(result)(api)
566
+ }
567
+ }
568
+
569
+ if (!isValidVariantFormatString(variantFunction)) {
570
+ throw new Error(
571
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
572
+ )
573
+ }
574
+
575
+ return parseVariant(variantFunction)
576
+ })
577
+
578
+ insertInto(variantList, variantName, options)
579
+ variantMap.set(variantName, variantFunctions)
580
+ context.variantOptions.set(variantName, options)
581
+ },
582
+ matchVariant(variant, variantFn, options) {
583
+ // A unique identifier that "groups" these variants together.
584
+ // This is for internal use only which is why it is not present in the types
585
+ let id = options?.id ?? ++variantIdentifier
586
+ let isSpecial = variant === '@'
587
+
588
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
589
+
590
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
591
+ if (key === 'DEFAULT') continue
592
+
593
+ api.addVariant(
594
+ isSpecial ? `${variant}${key}` : `${variant}-${key}`,
595
+ ({ args, container }) => {
596
+ return variantFn(
597
+ value,
598
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
599
+ )
600
+ },
601
+
602
+ {
603
+ ...options,
604
+ value,
605
+ id,
606
+ type: VARIANT_TYPES.MatchVariant,
607
+ variantInfo: VARIANT_INFO.Base,
608
+ }
609
+ )
610
+ }
611
+
612
+ let hasDefault = 'DEFAULT' in (options?.values ?? {})
613
+
614
+ api.addVariant(
615
+ variant,
616
+ ({ args, container }) => {
617
+ if (args?.value === sharedState.NONE && !hasDefault) {
618
+ return null
619
+ }
620
+
621
+ return variantFn(
622
+ args?.value === sharedState.NONE
623
+ ? options.values.DEFAULT
624
+ : // Falling back to args if it is a string, otherwise '' for older intellisense
625
+ // (JetBrains) plugins.
626
+ args?.value ?? (typeof args === 'string' ? args : ''),
627
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
628
+ )
629
+ },
630
+ {
631
+ ...options,
632
+ id,
633
+ type: VARIANT_TYPES.MatchVariant,
634
+ variantInfo: VARIANT_INFO.Dynamic,
635
+ }
636
+ )
637
+ },
396
638
  }
639
+
640
+ return api
397
641
  }
398
642
 
399
643
  let fileModifiedMapCache = new WeakMap()
@@ -406,6 +650,7 @@ export function getFileModifiedMap(context) {
406
650
 
407
651
  function trackModified(files, fileModifiedMap) {
408
652
  let changed = false
653
+ let mtimesToCommit = new Map()
409
654
 
410
655
  for (let file of files) {
411
656
  if (!file) continue
@@ -413,16 +658,23 @@ function trackModified(files, fileModifiedMap) {
413
658
  let parsed = url.parse(file)
414
659
  let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
415
660
  pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
416
- let newModified = fs.statSync(decodeURIComponent(pathname)).mtimeMs
661
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
662
+ if (!newModified) {
663
+ // It could happen that a file is passed in that doesn't exist. E.g.:
664
+ // postcss-cli will provide you a fake path when reading from stdin. This
665
+ // path then looks like /path-to-your-project/stdin In that case we just
666
+ // want to ignore it and don't track changes at all.
667
+ continue
668
+ }
417
669
 
418
670
  if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
419
671
  changed = true
420
672
  }
421
673
 
422
- fileModifiedMap.set(file, newModified)
674
+ mtimesToCommit.set(file, newModified)
423
675
  }
424
676
 
425
- return changed
677
+ return [changed, mtimesToCommit]
426
678
  }
427
679
 
428
680
  function extractVariantAtRules(node) {
@@ -459,29 +711,20 @@ function collectLayerPlugins(root) {
459
711
  } else if (layerRule.params === 'components') {
460
712
  for (let node of layerRule.nodes) {
461
713
  layerPlugins.push(function ({ addComponents }) {
462
- addComponents(node, { respectPrefix: false })
714
+ addComponents(node, { respectPrefix: false, preserveSource: true })
463
715
  })
464
716
  }
465
717
  layerRule.remove()
466
718
  } else if (layerRule.params === 'utilities') {
467
719
  for (let node of layerRule.nodes) {
468
720
  layerPlugins.push(function ({ addUtilities }) {
469
- addUtilities(node, { respectPrefix: false })
721
+ addUtilities(node, { respectPrefix: false, preserveSource: true })
470
722
  })
471
723
  }
472
724
  layerRule.remove()
473
725
  }
474
726
  })
475
727
 
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
728
  return layerPlugins
486
729
  }
487
730
 
@@ -511,10 +754,14 @@ function resolvePlugins(context, root) {
511
754
  let beforeVariants = [
512
755
  variantPlugins['pseudoElementVariants'],
513
756
  variantPlugins['pseudoClassVariants'],
757
+ variantPlugins['ariaVariants'],
758
+ variantPlugins['dataVariants'],
514
759
  ]
515
760
  let afterVariants = [
761
+ variantPlugins['supportsVariants'],
516
762
  variantPlugins['directionVariants'],
517
763
  variantPlugins['reducedMotionVariants'],
764
+ variantPlugins['prefersContrastVariants'],
518
765
  variantPlugins['darkVariants'],
519
766
  variantPlugins['printVariant'],
520
767
  variantPlugins['screenVariants'],
@@ -527,12 +774,10 @@ function resolvePlugins(context, root) {
527
774
  function registerPlugins(plugins, context) {
528
775
  let variantList = []
529
776
  let variantMap = new Map()
530
- let offsets = {
531
- base: 0n,
532
- components: 0n,
533
- utilities: 0n,
534
- user: 0n,
535
- }
777
+ context.variantMap = variantMap
778
+
779
+ let offsets = new Offsets()
780
+ context.offsets = offsets
536
781
 
537
782
  let classList = new Set()
538
783
 
@@ -553,47 +798,17 @@ function registerPlugins(plugins, context) {
553
798
  }
554
799
  }
555
800
 
556
- let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
557
- offsets.base,
558
- offsets.components,
559
- offsets.utilities,
560
- offsets.user,
561
- ])
562
- let reservedBits = BigInt(highestOffset.toString(2).length)
563
-
564
- // A number one less than the top range of the highest offset area
565
- // so arbitrary properties are always sorted at the end.
566
- context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
567
-
568
- context.layerOrder = {
569
- base: (1n << reservedBits) << 0n,
570
- components: (1n << reservedBits) << 1n,
571
- utilities: (1n << reservedBits) << 2n,
572
- user: (1n << reservedBits) << 3n,
573
- }
574
-
575
- reservedBits += 4n
576
-
577
- let offset = 0
578
- context.variantOrder = new Map(
579
- variantList
580
- .map((variant, i) => {
581
- let variantFunctions = variantMap.get(variant).length
582
- let bits = (1n << BigInt(i + offset)) << reservedBits
583
- offset += variantFunctions - 1
584
- return [variant, bits]
585
- })
586
- .sort(([, a], [, z]) => bigSign(a - z))
587
- )
588
-
589
- context.minimumScreen = [...context.variantOrder.values()].shift()
801
+ // Make sure to record bit masks for every variant
802
+ offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length)
590
803
 
591
804
  // Build variantMap
592
805
  for (let [variantName, variantFunctions] of variantMap.entries()) {
593
- let sort = context.variantOrder.get(variantName)
594
806
  context.variantMap.set(
595
807
  variantName,
596
- variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction])
808
+ variantFunctions.map((variantFunction, idx) => [
809
+ offsets.forVariant(variantName, idx),
810
+ variantFunction,
811
+ ])
597
812
  )
598
813
  }
599
814
 
@@ -611,7 +826,7 @@ function registerPlugins(plugins, context) {
611
826
  log.warn('root-regex', [
612
827
  'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
613
828
  'Update your `safelist` configuration to eliminate this warning.',
614
- // TODO: Add https://tw.wtf/regex-safelist
829
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
615
830
  ])
616
831
  continue
617
832
  }
@@ -621,12 +836,49 @@ function registerPlugins(plugins, context) {
621
836
 
622
837
  if (checks.length > 0) {
623
838
  let patternMatchingCount = new Map()
839
+ let prefixLength = context.tailwindConfig.prefix.length
840
+ let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
624
841
 
625
842
  for (let util of classList) {
626
843
  let utils = Array.isArray(util)
627
844
  ? (() => {
628
845
  let [utilName, options] = util
629
- return Object.keys(options?.values ?? {}).map((value) => formatClass(utilName, value))
846
+ let values = Object.keys(options?.values ?? {})
847
+ let classes = values.map((value) => formatClass(utilName, value))
848
+
849
+ if (options?.supportsNegativeValues) {
850
+ // This is the normal negated version
851
+ // e.g. `-inset-1` or `-tw-inset-1`
852
+ classes = [...classes, ...classes.map((cls) => '-' + cls)]
853
+
854
+ // This is the negated version *after* the prefix
855
+ // e.g. `tw--inset-1`
856
+ // The prefix is already attached to util name
857
+ // So we add the negative after the prefix
858
+ classes = [
859
+ ...classes,
860
+ ...classes.map(
861
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
862
+ ),
863
+ ]
864
+ }
865
+
866
+ if (options.types.some(({ type }) => type === 'color')) {
867
+ classes = [
868
+ ...classes,
869
+ ...classes.flatMap((cls) =>
870
+ Object.keys(context.tailwindConfig.theme.opacity).map(
871
+ (opacity) => `${cls}/${opacity}`
872
+ )
873
+ ),
874
+ ]
875
+ }
876
+
877
+ if (checkImportantUtils && options?.respectImportant) {
878
+ classes = [...classes, ...classes.map((cls) => '!' + cls)]
879
+ }
880
+
881
+ return classes
630
882
  })()
631
883
  : [util]
632
884
 
@@ -661,25 +913,91 @@ function registerPlugins(plugins, context) {
661
913
  log.warn([
662
914
  `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
663
915
  'Fix this pattern or remove it from your `safelist` configuration.',
916
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
664
917
  ])
665
918
  }
666
919
  }
667
920
  }
668
921
 
922
+ let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark'
923
+
924
+ // A list of utilities that are used by certain Tailwind CSS utilities but
925
+ // that don't exist on their own. This will result in them "not existing" and
926
+ // sorting could be weird since you still require them in order to make the
927
+ // host utilities work properly. (Thanks Biology)
928
+ let parasiteUtilities = [
929
+ prefix(context, darkClassName),
930
+ prefix(context, 'group'),
931
+ prefix(context, 'peer'),
932
+ ]
933
+ context.getClassOrder = function getClassOrder(classes) {
934
+ // Sort classes so they're ordered in a deterministic manner
935
+ let sorted = [...classes].sort((a, z) => {
936
+ if (a === z) return 0
937
+ if (a < z) return -1
938
+ return 1
939
+ })
940
+
941
+ // Non-util classes won't be generated, so we default them to null
942
+ let sortedClassNames = new Map(sorted.map((className) => [className, null]))
943
+
944
+ // Sort all classes in order
945
+ // Non-tailwind classes won't be generated and will be left as `null`
946
+ let rules = generateRules(new Set(sorted), context)
947
+ rules = context.offsets.sort(rules)
948
+
949
+ let idx = BigInt(parasiteUtilities.length)
950
+
951
+ for (const [, rule] of rules) {
952
+ sortedClassNames.set(rule.raws.tailwind.candidate, idx++)
953
+ }
954
+
955
+ return classes.map((className) => {
956
+ let order = sortedClassNames.get(className) ?? null
957
+ let parasiteIndex = parasiteUtilities.indexOf(className)
958
+
959
+ if (order === null && parasiteIndex !== -1) {
960
+ // This will make sure that it is at the very beginning of the
961
+ // `components` layer which technically means 'before any
962
+ // components'.
963
+ order = BigInt(parasiteIndex)
964
+ }
965
+
966
+ return [className, order]
967
+ })
968
+ }
969
+
669
970
  // Generate a list of strings for autocompletion purposes, e.g.
670
971
  // ['uppercase', 'lowercase', ...]
671
- context.getClassList = function () {
972
+ context.getClassList = function getClassList(options = {}) {
672
973
  let output = []
673
974
 
674
975
  for (let util of classList) {
675
976
  if (Array.isArray(util)) {
676
- let [utilName, options] = util
977
+ let [utilName, utilOptions] = util
677
978
  let negativeClasses = []
678
979
 
679
- for (let [key, value] of Object.entries(options?.values ?? {})) {
680
- output.push(formatClass(utilName, key))
681
- if (options?.supportsNegativeValues && negateValue(value)) {
682
- negativeClasses.push(formatClass(utilName, `-${key}`))
980
+ let modifiers = Object.keys(utilOptions?.modifiers ?? {})
981
+
982
+ if (utilOptions?.types?.some(({ type }) => type === 'color')) {
983
+ modifiers.push(...Object.keys(context.tailwindConfig.theme.opacity ?? {}))
984
+ }
985
+
986
+ let metadata = { modifiers }
987
+ let includeMetadata = options.includeMetadata && modifiers.length > 0
988
+
989
+ for (let [key, value] of Object.entries(utilOptions?.values ?? {})) {
990
+ // Ignore undefined and null values
991
+ if (value == null) {
992
+ continue
993
+ }
994
+
995
+ let cls = formatClass(utilName, key)
996
+ output.push(includeMetadata ? [cls, metadata] : cls)
997
+
998
+ if (utilOptions?.supportsNegativeValues && negateValue(value)) {
999
+ let cls = formatClass(utilName, `-${key}`)
1000
+ negativeClasses.push(includeMetadata ? [cls, metadata] : cls)
683
1001
  }
684
1002
  }
685
1003
 
@@ -691,21 +1009,223 @@ function registerPlugins(plugins, context) {
691
1009
 
692
1010
  return output
693
1011
  }
1012
+
1013
+ // Generate a list of available variants with meta information of the type of variant.
1014
+ context.getVariants = function getVariants() {
1015
+ let result = []
1016
+ for (let [name, options] of context.variantOptions.entries()) {
1017
+ if (options.variantInfo === VARIANT_INFO.Base) continue
1018
+
1019
+ result.push({
1020
+ name,
1021
+ isArbitrary: options.type === Symbol.for('MATCH_VARIANT'),
1022
+ values: Object.keys(options.values ?? {}),
1023
+ hasDash: name !== '@',
1024
+ selectors({ modifier, value } = {}) {
1025
+ let candidate = '__TAILWIND_PLACEHOLDER__'
1026
+
1027
+ let rule = postcss.rule({ selector: `.${candidate}` })
1028
+ let container = postcss.root({ nodes: [rule.clone()] })
1029
+
1030
+ let before = container.toString()
1031
+
1032
+ let fns = (context.variantMap.get(name) ?? []).flatMap(([_, fn]) => fn)
1033
+ let formatStrings = []
1034
+ for (let fn of fns) {
1035
+ let localFormatStrings = []
1036
+
1037
+ let api = {
1038
+ args: { modifier, value: options.values?.[value] ?? value },
1039
+ separator: context.tailwindConfig.separator,
1040
+ modifySelectors(modifierFunction) {
1041
+ // Run the modifierFunction over each rule
1042
+ container.each((rule) => {
1043
+ if (rule.type !== 'rule') {
1044
+ return
1045
+ }
1046
+
1047
+ rule.selectors = rule.selectors.map((selector) => {
1048
+ return modifierFunction({
1049
+ get className() {
1050
+ return getClassNameFromSelector(selector)
1051
+ },
1052
+ selector,
1053
+ })
1054
+ })
1055
+ })
1056
+
1057
+ return container
1058
+ },
1059
+ format(str) {
1060
+ localFormatStrings.push(str)
1061
+ },
1062
+ wrap(wrapper) {
1063
+ localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`)
1064
+ },
1065
+ container,
1066
+ }
1067
+
1068
+ let ruleWithVariant = fn(api)
1069
+ if (localFormatStrings.length > 0) {
1070
+ formatStrings.push(localFormatStrings)
1071
+ }
1072
+
1073
+ if (Array.isArray(ruleWithVariant)) {
1074
+ for (let variantFunction of ruleWithVariant) {
1075
+ localFormatStrings = []
1076
+ variantFunction(api)
1077
+ formatStrings.push(localFormatStrings)
1078
+ }
1079
+ }
1080
+ }
1081
+
1082
+ // Reverse engineer the result of the `container`
1083
+ let manualFormatStrings = []
1084
+ let after = container.toString()
1085
+
1086
+ if (before !== after) {
1087
+ // Figure out all selectors
1088
+ container.walkRules((rule) => {
1089
+ let modified = rule.selector
1090
+
1091
+ // Rebuild the base selector, this is what plugin authors would do
1092
+ // as well. E.g.: `${variant}${separator}${className}`.
1093
+ // However, plugin authors probably also prepend or append certain
1094
+ // classes, pseudos, ids, ...
1095
+ let rebuiltBase = selectorParser((selectors) => {
1096
+ selectors.walkClasses((classNode) => {
1097
+ classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}`
1098
+ })
1099
+ }).processSync(modified)
1100
+
1101
+ // Now that we know the original selector, the new selector, and
1102
+ // the rebuild part in between, we can replace the part that plugin
1103
+ // authors need to rebuild with `&`, and eventually store it in the
1104
+ // collectedFormats. Similar to what `format('...')` would do.
1105
+ //
1106
+ // E.g.:
1107
+ // variant: foo
1108
+ // selector: .markdown > p
1109
+ // modified (by plugin): .foo .foo\\:markdown > p
1110
+ // rebuiltBase (internal): .foo\\:markdown > p
1111
+ // format: .foo &
1112
+ manualFormatStrings.push(modified.replace(rebuiltBase, '&').replace(candidate, '&'))
1113
+ })
1114
+
1115
+ // Figure out all atrules
1116
+ container.walkAtRules((atrule) => {
1117
+ manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`)
1118
+ })
1119
+ }
1120
+
1121
+ let isArbitraryVariant = !(value in (options.values ?? {}))
1122
+
1123
+ formatStrings = formatStrings.map((format) =>
1124
+ format.map((str) => ({
1125
+ format: str,
1126
+ isArbitraryVariant,
1127
+ }))
1128
+ )
1129
+
1130
+ manualFormatStrings = manualFormatStrings.map((format) => ({
1131
+ format,
1132
+ isArbitraryVariant,
1133
+ }))
1134
+
1135
+ let opts = {
1136
+ candidate,
1137
+ context,
1138
+ }
1139
+
1140
+ let result = formatStrings.map((formats) =>
1141
+ finalizeSelector(`.${candidate}`, formatVariantSelector(formats, opts), opts)
1142
+ .replace(`.${candidate}`, '&')
1143
+ .replace('{ & }', '')
1144
+ .trim()
1145
+ )
1146
+
1147
+ if (manualFormatStrings.length > 0) {
1148
+ result.push(
1149
+ formatVariantSelector(manualFormatStrings, opts)
1150
+ .toString()
1151
+ .replace(`.${candidate}`, '&')
1152
+ )
1153
+ }
1154
+
1155
+ return result
1156
+ },
1157
+ })
1158
+ }
1159
+
1160
+ return result
1161
+ }
1162
+ }
1163
+
1164
+ /**
1165
+ * Mark as class as retroactively invalid
1166
+ *
1167
+ *
1168
+ * @param {string} candidate
1169
+ */
1170
+ function markInvalidUtilityCandidate(context, candidate) {
1171
+ if (!context.classCache.has(candidate)) {
1172
+ return
1173
+ }
1174
+
1175
+ // Mark this as not being a real utility
1176
+ context.notClassCache.add(candidate)
1177
+
1178
+ // Remove it from any candidate-specific caches
1179
+ context.classCache.delete(candidate)
1180
+ context.applyClassCache.delete(candidate)
1181
+ context.candidateRuleMap.delete(candidate)
1182
+ context.candidateRuleCache.delete(candidate)
1183
+
1184
+ // Ensure the stylesheet gets rebuilt
1185
+ context.stylesheetCache = null
1186
+ }
1187
+
1188
+ /**
1189
+ * Mark as class as retroactively invalid
1190
+ *
1191
+ * @param {import('postcss').Node} node
1192
+ */
1193
+ function markInvalidUtilityNode(context, node) {
1194
+ let candidate = node.raws.tailwind.candidate
1195
+
1196
+ if (!candidate) {
1197
+ return
1198
+ }
1199
+
1200
+ for (const entry of context.ruleCache) {
1201
+ if (entry[1].raws.tailwind.candidate === candidate) {
1202
+ context.ruleCache.delete(entry)
1203
+ // context.postCssNodeCache.delete(node)
1204
+ }
1205
+ }
1206
+
1207
+ markInvalidUtilityCandidate(context, candidate)
694
1208
  }
695
1209
 
696
1210
  export function createContext(tailwindConfig, changedContent = [], root = postcss.root()) {
697
1211
  let context = {
698
1212
  disposables: [],
699
1213
  ruleCache: new Set(),
1214
+ candidateRuleCache: new Map(),
700
1215
  classCache: new Map(),
701
1216
  applyClassCache: new Map(),
702
- notClassCache: new Set(),
1217
+ // Seed the not class cache with the blocklist (which is only strings)
1218
+ notClassCache: new Set(tailwindConfig.blocklist ?? []),
703
1219
  postCssNodeCache: new Map(),
704
1220
  candidateRuleMap: new Map(),
705
1221
  tailwindConfig,
706
1222
  changedContent: changedContent,
707
1223
  variantMap: new Map(),
708
1224
  stylesheetCache: null,
1225
+ variantOptions: new Map(),
1226
+
1227
+ markInvalidUtilityCandidate: (candidate) => markInvalidUtilityCandidate(context, candidate),
1228
+ markInvalidUtilityNode: (node) => markInvalidUtilityNode(context, node),
709
1229
  }
710
1230
 
711
1231
  let resolvedPlugins = resolvePlugins(context, root)
@@ -743,15 +1263,17 @@ export function getContext(
743
1263
  existingContext = context
744
1264
  }
745
1265
 
1266
+ let cssDidChange = hasContentChanged(sourcePath, root)
1267
+
746
1268
  // If there's already a context in the cache and we don't need to
747
1269
  // reset the context, return the cached context.
748
1270
  if (existingContext) {
749
- let contextDependenciesChanged = trackModified(
1271
+ let [contextDependenciesChanged, mtimesToCommit] = trackModified(
750
1272
  [...contextDependencies],
751
1273
  getFileModifiedMap(existingContext)
752
1274
  )
753
- if (!contextDependenciesChanged) {
754
- return [existingContext, false]
1275
+ if (!contextDependenciesChanged && !cssDidChange) {
1276
+ return [existingContext, false, mtimesToCommit]
755
1277
  }
756
1278
  }
757
1279
 
@@ -782,7 +1304,11 @@ export function getContext(
782
1304
 
783
1305
  let context = createContext(tailwindConfig, [], root)
784
1306
 
785
- trackModified([...contextDependencies], getFileModifiedMap(context))
1307
+ Object.assign(context, {
1308
+ userConfigPath,
1309
+ })
1310
+
1311
+ let [, mtimesToCommit] = trackModified([...contextDependencies], getFileModifiedMap(context))
786
1312
 
787
1313
  // ---
788
1314
 
@@ -797,5 +1323,5 @@ export function getContext(
797
1323
 
798
1324
  contextSourcesMap.get(context).add(sourcePath)
799
1325
 
800
- return [context, true]
1326
+ return [context, true, mtimesToCommit]
801
1327
  }