tailwindcss 0.0.0-insiders.fda68f7 → 0.0.0-oxide.6bf5e56

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