tailwindcss 0.0.0-insiders.ca1dfd6 → 0.0.0-insiders.cab1fce

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 (148) hide show
  1. package/CHANGELOG.md +380 -1
  2. package/LICENSE +1 -2
  3. package/README.md +8 -4
  4. package/colors.d.ts +3 -0
  5. package/colors.js +2 -1
  6. package/defaultConfig.d.ts +3 -0
  7. package/defaultConfig.js +2 -1
  8. package/defaultTheme.d.ts +3 -0
  9. package/defaultTheme.js +2 -1
  10. package/lib/cli-peer-dependencies.js +10 -5
  11. package/lib/cli.js +311 -213
  12. package/lib/constants.js +9 -9
  13. package/lib/corePluginList.js +11 -1
  14. package/lib/corePlugins.js +1895 -1773
  15. package/lib/css/preflight.css +19 -13
  16. package/lib/featureFlags.js +22 -16
  17. package/lib/index.js +17 -8
  18. package/lib/lib/cacheInvalidation.js +69 -0
  19. package/lib/lib/collapseAdjacentRules.js +30 -14
  20. package/lib/lib/collapseDuplicateDeclarations.js +80 -0
  21. package/lib/lib/defaultExtractor.js +187 -0
  22. package/lib/lib/detectNesting.js +17 -2
  23. package/lib/lib/evaluateTailwindFunctions.js +40 -24
  24. package/lib/lib/expandApplyAtRules.js +414 -157
  25. package/lib/lib/expandTailwindAtRules.js +156 -151
  26. package/lib/lib/generateRules.js +402 -68
  27. package/lib/lib/getModuleDependencies.js +14 -14
  28. package/lib/lib/normalizeTailwindDirectives.js +45 -36
  29. package/lib/lib/partitionApplyAtRules.js +53 -0
  30. package/lib/lib/regex.js +53 -0
  31. package/lib/lib/resolveDefaultsAtRules.js +105 -90
  32. package/lib/lib/setupContextUtils.js +416 -291
  33. package/lib/lib/setupTrackingContext.js +60 -56
  34. package/lib/lib/sharedState.js +38 -15
  35. package/lib/lib/substituteScreenAtRules.js +9 -6
  36. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  37. package/lib/postcss-plugins/nesting/index.js +17 -0
  38. package/lib/postcss-plugins/nesting/plugin.js +85 -0
  39. package/lib/processTailwindFeatures.js +21 -9
  40. package/lib/public/colors.js +244 -248
  41. package/lib/public/resolve-config.js +5 -5
  42. package/lib/util/buildMediaQuery.js +13 -24
  43. package/lib/util/cloneDeep.js +1 -1
  44. package/lib/util/cloneNodes.js +12 -1
  45. package/lib/util/color.js +43 -32
  46. package/lib/util/createPlugin.js +1 -2
  47. package/lib/util/createUtilityPlugin.js +13 -17
  48. package/lib/util/dataTypes.js +114 -74
  49. package/lib/util/defaults.js +6 -0
  50. package/lib/util/escapeClassName.js +5 -5
  51. package/lib/util/escapeCommas.js +1 -1
  52. package/lib/util/flattenColorPalette.js +2 -4
  53. package/lib/util/formatVariantSelector.js +194 -0
  54. package/lib/util/getAllConfigs.js +13 -5
  55. package/lib/util/hashConfig.js +5 -5
  56. package/lib/util/isKeyframeRule.js +1 -1
  57. package/lib/util/isPlainObject.js +1 -1
  58. package/lib/util/isValidArbitraryValue.js +64 -0
  59. package/lib/util/log.js +35 -19
  60. package/lib/util/nameClass.js +7 -6
  61. package/lib/util/negateValue.js +5 -3
  62. package/lib/util/normalizeConfig.js +233 -0
  63. package/lib/util/normalizeScreens.js +59 -0
  64. package/lib/util/parseAnimationValue.js +56 -56
  65. package/lib/util/parseBoxShadowValue.js +76 -0
  66. package/lib/util/parseDependency.js +32 -32
  67. package/lib/util/parseObjectStyles.js +6 -6
  68. package/lib/util/pluginUtils.js +67 -170
  69. package/lib/util/prefixSelector.js +4 -7
  70. package/lib/util/resolveConfig.js +123 -133
  71. package/lib/util/resolveConfigPath.js +17 -18
  72. package/lib/util/responsive.js +6 -6
  73. package/lib/util/splitAtTopLevelOnly.js +72 -0
  74. package/lib/util/toColorValue.js +1 -2
  75. package/lib/util/toPath.js +6 -1
  76. package/lib/util/transformThemeValue.js +42 -34
  77. package/lib/util/validateConfig.js +21 -0
  78. package/lib/util/withAlphaVariable.js +19 -19
  79. package/nesting/index.js +2 -12
  80. package/package.json +41 -42
  81. package/peers/index.js +11539 -10859
  82. package/plugin.d.ts +11 -0
  83. package/plugin.js +2 -1
  84. package/resolveConfig.js +2 -1
  85. package/scripts/create-plugin-list.js +2 -2
  86. package/scripts/generate-types.js +52 -0
  87. package/src/cli-peer-dependencies.js +7 -1
  88. package/src/cli.js +174 -41
  89. package/src/corePluginList.js +1 -1
  90. package/src/corePlugins.js +641 -596
  91. package/src/css/preflight.css +19 -13
  92. package/src/featureFlags.js +16 -10
  93. package/src/index.js +14 -6
  94. package/src/lib/cacheInvalidation.js +52 -0
  95. package/src/lib/collapseAdjacentRules.js +21 -2
  96. package/src/lib/collapseDuplicateDeclarations.js +93 -0
  97. package/src/lib/defaultExtractor.js +192 -0
  98. package/src/lib/detectNesting.js +22 -3
  99. package/src/lib/evaluateTailwindFunctions.js +24 -7
  100. package/src/lib/expandApplyAtRules.js +442 -154
  101. package/src/lib/expandTailwindAtRules.js +91 -65
  102. package/src/lib/generateRules.js +409 -43
  103. package/src/lib/normalizeTailwindDirectives.js +10 -3
  104. package/src/lib/partitionApplyAtRules.js +52 -0
  105. package/src/lib/regex.js +74 -0
  106. package/src/lib/resolveDefaultsAtRules.js +74 -53
  107. package/src/lib/setupContextUtils.js +388 -208
  108. package/src/lib/setupTrackingContext.js +15 -12
  109. package/src/lib/sharedState.js +42 -7
  110. package/src/lib/substituteScreenAtRules.js +6 -3
  111. package/src/postcss-plugins/nesting/README.md +42 -0
  112. package/src/postcss-plugins/nesting/index.js +13 -0
  113. package/src/postcss-plugins/nesting/plugin.js +80 -0
  114. package/src/processTailwindFeatures.js +17 -2
  115. package/src/public/colors.js +4 -9
  116. package/src/util/buildMediaQuery.js +14 -18
  117. package/src/util/cloneNodes.js +14 -1
  118. package/src/util/color.js +31 -14
  119. package/src/util/createUtilityPlugin.js +2 -2
  120. package/src/util/dataTypes.js +56 -16
  121. package/src/util/defaults.js +6 -0
  122. package/src/util/formatVariantSelector.js +213 -0
  123. package/src/util/getAllConfigs.js +7 -0
  124. package/src/util/isValidArbitraryValue.js +61 -0
  125. package/src/util/log.js +23 -22
  126. package/src/util/nameClass.js +2 -2
  127. package/src/util/negateValue.js +4 -2
  128. package/src/util/normalizeConfig.js +262 -0
  129. package/src/util/normalizeScreens.js +45 -0
  130. package/src/util/parseBoxShadowValue.js +72 -0
  131. package/src/util/pluginUtils.js +51 -147
  132. package/src/util/prefixSelector.js +7 -8
  133. package/src/util/resolveConfig.js +108 -79
  134. package/src/util/splitAtTopLevelOnly.js +71 -0
  135. package/src/util/toPath.js +23 -1
  136. package/src/util/transformThemeValue.js +24 -7
  137. package/src/util/validateConfig.js +13 -0
  138. package/stubs/defaultConfig.stub.js +157 -94
  139. package/stubs/simpleConfig.stub.js +0 -1
  140. package/types/config.d.ts +325 -0
  141. package/types/generated/.gitkeep +0 -0
  142. package/types/generated/colors.d.ts +276 -0
  143. package/types/generated/corePluginList.d.ts +1 -0
  144. package/types/index.d.ts +1 -0
  145. package/types.d.ts +1 -0
  146. package/lib/lib/setupWatchingContext.js +0 -284
  147. package/nesting/plugin.js +0 -41
  148. package/src/lib/setupWatchingContext.js +0 -306
@@ -12,11 +12,51 @@ import escapeClassName from '../util/escapeClassName'
12
12
  import nameClass, { formatClass } from '../util/nameClass'
13
13
  import { coerceValue } from '../util/pluginUtils'
14
14
  import bigSign from '../util/bigSign'
15
- import corePlugins from '../corePlugins'
15
+ import { variantPlugins, corePlugins } from '../corePlugins'
16
16
  import * as sharedState from './sharedState'
17
17
  import { env } from './sharedState'
18
18
  import { toPath } from '../util/toPath'
19
19
  import log from '../util/log'
20
+ import negateValue from '../util/negateValue'
21
+ import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
+ import { generateRules } from './generateRules'
23
+ import { hasContentChanged } from './cacheInvalidation.js'
24
+
25
+ let MATCH_VARIANT = Symbol()
26
+
27
+ function prefix(context, selector) {
28
+ let prefix = context.tailwindConfig.prefix
29
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
30
+ }
31
+
32
+ function parseVariantFormatString(input) {
33
+ if (input.includes('{')) {
34
+ if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
35
+
36
+ return input
37
+ .split(/{(.*)}/gim)
38
+ .flatMap((line) => parseVariantFormatString(line))
39
+ .filter(Boolean)
40
+ }
41
+
42
+ return [input.trim()]
43
+ }
44
+
45
+ function isBalanced(input) {
46
+ let count = 0
47
+
48
+ for (let char of input) {
49
+ if (char === '{') {
50
+ count++
51
+ } else if (char === '}') {
52
+ if (--count < 0) {
53
+ return false // unbalanced
54
+ }
55
+ }
56
+ }
57
+
58
+ return count === 0
59
+ }
20
60
 
21
61
  function insertInto(list, value, { before = [] } = {}) {
22
62
  before = [].concat(before)
@@ -47,50 +87,82 @@ function parseStyles(styles) {
47
87
  })
48
88
  }
49
89
 
50
- function getClasses(selector) {
90
+ function getClasses(selector, mutate) {
51
91
  let parser = selectorParser((selectors) => {
52
92
  let allClasses = []
93
+
94
+ if (mutate) {
95
+ mutate(selectors)
96
+ }
97
+
53
98
  selectors.walkClasses((classNode) => {
54
99
  allClasses.push(classNode.value)
55
100
  })
101
+
56
102
  return allClasses
57
103
  })
58
104
  return parser.transformSync(selector)
59
105
  }
60
106
 
61
- function extractCandidates(node) {
107
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
62
108
  let classes = []
63
109
 
110
+ // Handle normal rules
64
111
  if (node.type === 'rule') {
112
+ // Ignore everything inside a :not(...). This allows you to write code like
113
+ // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
114
+ // not generated it. But now we will ignore everything inside a `:not`, so
115
+ // that it still gets generated.
116
+ function ignoreNot(selectors) {
117
+ selectors.walkPseudos((pseudo) => {
118
+ if (pseudo.value === ':not') {
119
+ pseudo.remove()
120
+ }
121
+ })
122
+ }
123
+
65
124
  for (let selector of node.selectors) {
66
- let classCandidates = getClasses(selector)
125
+ let classCandidates = getClasses(selector, ignoreNot)
67
126
  // At least one of the selectors contains non-"on-demandable" candidates.
68
- if (classCandidates.length === 0) return []
127
+ if (classCandidates.length === 0) {
128
+ state.containsNonOnDemandable = true
129
+ }
69
130
 
70
- classes = [...classes, ...classCandidates]
131
+ for (let classCandidate of classCandidates) {
132
+ classes.push(classCandidate)
133
+ }
71
134
  }
72
- return classes
73
135
  }
74
136
 
75
- if (node.type === 'atrule') {
137
+ // Handle at-rules (which contains nested rules)
138
+ else if (node.type === 'atrule') {
76
139
  node.walkRules((rule) => {
77
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
140
+ for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
141
+ classes.push(classCandidate)
142
+ }
78
143
  })
79
144
  }
80
145
 
146
+ if (depth === 0) {
147
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
148
+ }
149
+
81
150
  return classes
82
151
  }
83
152
 
84
153
  function withIdentifiers(styles) {
85
154
  return parseStyles(styles).flatMap((node) => {
86
155
  let nodeMap = new Map()
87
- let candidates = extractCandidates(node)
156
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
88
157
 
89
- // If this isn't "on-demandable", assign it a universal candidate.
90
- if (candidates.length === 0) {
91
- return [['*', node]]
158
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
159
+ if (containsNonOnDemandableSelectors) {
160
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
92
161
  }
93
162
 
163
+ // However, it could be that it also contains "on-demandable" candidates.
164
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
165
+ // `@apply foo` for example.
94
166
  return candidates.map((c) => {
95
167
  if (!nodeMap.has(node)) {
96
168
  nodeMap.set(node, node)
@@ -100,62 +172,32 @@ function withIdentifiers(styles) {
100
172
  })
101
173
  }
102
174
 
103
- let matchingBrackets = new Map([
104
- ['{', '}'],
105
- ['[', ']'],
106
- ['(', ')'],
107
- ])
108
- let inverseMatchingBrackets = new Map(
109
- Array.from(matchingBrackets.entries()).map(([k, v]) => [v, k])
110
- )
111
-
112
- let quotes = new Set(['"', "'", '`'])
113
-
114
- // Arbitrary values must contain balanced brackets (), [] and {}. Escaped
115
- // values don't count, and brackets inside quotes also don't count.
116
- //
117
- // E.g.: w-[this-is]w-[weird-and-invalid]
118
- // E.g.: w-[this-is\\]w-\\[weird-but-valid]
119
- // E.g.: content-['this-is-also-valid]-weirdly-enough']
120
- function isValidArbitraryValue(value) {
121
- let stack = []
122
- let inQuotes = false
123
-
124
- for (let i = 0; i < value.length; i++) {
125
- let char = value[i]
126
-
127
- // Non-escaped quotes allow us to "allow" anything in between
128
- if (quotes.has(char) && value[i - 1] !== '\\') {
129
- inQuotes = !inQuotes
130
- }
131
-
132
- if (inQuotes) continue
133
- if (value[i - 1] === '\\') continue // Escaped
175
+ export function isValidVariantFormatString(format) {
176
+ return format.startsWith('@') || format.includes('&')
177
+ }
134
178
 
135
- if (matchingBrackets.has(char)) {
136
- stack.push(char)
137
- } else if (inverseMatchingBrackets.has(char)) {
138
- let inverse = inverseMatchingBrackets.get(char)
179
+ export function parseVariant(variant) {
180
+ variant = variant
181
+ .replace(/\n+/g, '')
182
+ .replace(/\s{1,}/g, ' ')
183
+ .trim()
139
184
 
140
- // Nothing to pop from, therefore it is unbalanced
141
- if (stack.length <= 0) {
142
- return false
185
+ let fns = parseVariantFormatString(variant)
186
+ .map((str) => {
187
+ if (!str.startsWith('@')) {
188
+ return ({ format }) => format(str)
143
189
  }
144
190
 
145
- // Popped value must match the inverse value, otherwise it is unbalanced
146
- if (stack.pop() !== inverse) {
147
- return false
148
- }
149
- }
150
- }
191
+ let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str)
192
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() }))
193
+ })
194
+ .reverse()
151
195
 
152
- // If there is still something on the stack, it is also unbalanced
153
- if (stack.length > 0) {
154
- return false
196
+ return (api) => {
197
+ for (let fn of fns) {
198
+ fn(api)
199
+ }
155
200
  }
156
-
157
- // All good, totally balanced!
158
- return true
159
201
  }
160
202
 
161
203
  function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
@@ -168,37 +210,36 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
168
210
  }
169
211
 
170
212
  function prefixIdentifier(identifier, options) {
171
- if (identifier === '*') {
172
- return '*'
213
+ if (identifier === sharedState.NOT_ON_DEMAND) {
214
+ return sharedState.NOT_ON_DEMAND
173
215
  }
174
216
 
175
217
  if (!options.respectPrefix) {
176
218
  return identifier
177
219
  }
178
220
 
179
- if (typeof context.tailwindConfig.prefix === 'function') {
180
- return prefixSelector(context.tailwindConfig.prefix, `.${identifier}`).substr(1)
181
- }
182
-
183
221
  return context.tailwindConfig.prefix + identifier
184
222
  }
185
223
 
186
- return {
187
- addVariant(variantName, variantFunctions, options = {}) {
188
- variantFunctions = [].concat(variantFunctions)
224
+ function resolveThemeValue(path, defaultValue, opts = {}) {
225
+ const [pathRoot, ...subPaths] = toPath(path)
226
+ const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
227
+ return transformThemeValue(pathRoot)(value, opts)
228
+ }
189
229
 
190
- insertInto(variantList, variantName, options)
191
- variantMap.set(variantName, variantFunctions)
192
- },
230
+ const theme = Object.assign(
231
+ (path, defaultValue = undefined) => resolveThemeValue(path, defaultValue),
232
+ {
233
+ withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, { opacityValue }),
234
+ }
235
+ )
236
+
237
+ let api = {
193
238
  postcss,
194
239
  prefix: applyConfiguredPrefix,
195
240
  e: escapeClassName,
196
241
  config: getConfigValue,
197
- theme(path, defaultValue) {
198
- const [pathRoot, ...subPaths] = toPath(path)
199
- const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
200
- return transformThemeValue(pathRoot)(value)
201
- },
242
+ theme,
202
243
  corePlugins: (path) => {
203
244
  if (Array.isArray(tailwindConfig.corePlugins)) {
204
245
  return tailwindConfig.corePlugins.includes(path)
@@ -210,21 +251,31 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
210
251
  // Preserved for backwards compatibility but not used in v3.0+
211
252
  return []
212
253
  },
213
- addUserCss(userCss) {
214
- for (let [identifier, rule] of withIdentifiers(userCss)) {
215
- let offset = offsets.user++
254
+ addBase(base) {
255
+ for (let [identifier, rule] of withIdentifiers(base)) {
256
+ let prefixedIdentifier = prefixIdentifier(identifier, {})
257
+ let offset = offsets.base++
216
258
 
217
- if (!context.candidateRuleMap.has(identifier)) {
218
- context.candidateRuleMap.set(identifier, [])
259
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
260
+ context.candidateRuleMap.set(prefixedIdentifier, [])
219
261
  }
220
262
 
221
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
263
+ context.candidateRuleMap
264
+ .get(prefixedIdentifier)
265
+ .push([{ sort: offset, layer: 'base' }, rule])
222
266
  }
223
267
  },
224
- addBase(base) {
225
- for (let [identifier, rule] of withIdentifiers(base)) {
268
+ /**
269
+ * @param {string} group
270
+ * @param {Record<string, string | string[]>} declarations
271
+ */
272
+ addDefaults(group, declarations) {
273
+ const groups = {
274
+ [`@defaults ${group}`]: declarations,
275
+ }
276
+
277
+ for (let [identifier, rule] of withIdentifiers(groups)) {
226
278
  let prefixedIdentifier = prefixIdentifier(identifier, {})
227
- let offset = offsets.base++
228
279
 
229
280
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
230
281
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -232,26 +283,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
232
283
 
233
284
  context.candidateRuleMap
234
285
  .get(prefixedIdentifier)
235
- .push([{ sort: offset, layer: 'base' }, rule])
286
+ .push([{ sort: offsets.base++, layer: 'defaults' }, rule])
236
287
  }
237
288
  },
238
289
  addComponents(components, options) {
239
290
  let defaultOptions = {
240
- variants: [],
241
291
  respectPrefix: true,
242
292
  respectImportant: false,
243
- respectVariants: true,
244
293
  }
245
294
 
246
- options = Object.assign(
247
- {},
248
- defaultOptions,
249
- Array.isArray(options) ? { variants: options } : options
250
- )
295
+ options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options)
251
296
 
252
297
  for (let [identifier, rule] of withIdentifiers(components)) {
253
298
  let prefixedIdentifier = prefixIdentifier(identifier, options)
254
- let offset = offsets.components++
255
299
 
256
300
  classList.add(prefixedIdentifier)
257
301
 
@@ -261,26 +305,19 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
261
305
 
262
306
  context.candidateRuleMap
263
307
  .get(prefixedIdentifier)
264
- .push([{ sort: offset, layer: 'components', options }, rule])
308
+ .push([{ sort: offsets.components++, layer: 'components', options }, rule])
265
309
  }
266
310
  },
267
311
  addUtilities(utilities, options) {
268
312
  let defaultOptions = {
269
- variants: [],
270
313
  respectPrefix: true,
271
314
  respectImportant: true,
272
- respectVariants: true,
273
315
  }
274
316
 
275
- options = Object.assign(
276
- {},
277
- defaultOptions,
278
- Array.isArray(options) ? { variants: options } : options
279
- )
317
+ options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options)
280
318
 
281
319
  for (let [identifier, rule] of withIdentifiers(utilities)) {
282
320
  let prefixedIdentifier = prefixIdentifier(identifier, options)
283
- let offset = offsets.utilities++
284
321
 
285
322
  classList.add(prefixedIdentifier)
286
323
 
@@ -290,15 +327,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
290
327
 
291
328
  context.candidateRuleMap
292
329
  .get(prefixedIdentifier)
293
- .push([{ sort: offset, layer: 'utilities', options }, rule])
330
+ .push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule])
294
331
  }
295
332
  },
296
333
  matchUtilities: function (utilities, options) {
297
334
  let defaultOptions = {
298
- variants: [],
299
335
  respectPrefix: true,
300
336
  respectImportant: true,
301
- respectVariants: true,
302
337
  }
303
338
 
304
339
  options = { ...defaultOptions, ...options }
@@ -314,7 +349,59 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
314
349
  function wrapped(modifier, { isOnlyPlugin }) {
315
350
  let { type = 'any' } = options
316
351
  type = [].concat(type)
317
- let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
352
+ let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
353
+
354
+ if (value === undefined) {
355
+ return []
356
+ }
357
+
358
+ if (!type.includes(coercedType) && !isOnlyPlugin) {
359
+ return []
360
+ }
361
+
362
+ if (!isValidArbitraryValue(value)) {
363
+ return []
364
+ }
365
+
366
+ let ruleSets = []
367
+ .concat(rule(value))
368
+ .filter(Boolean)
369
+ .map((declaration) => ({
370
+ [nameClass(identifier, modifier)]: declaration,
371
+ }))
372
+
373
+ return ruleSets
374
+ }
375
+
376
+ let withOffsets = [{ sort: offset, layer: 'utilities', options }, wrapped]
377
+
378
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
379
+ context.candidateRuleMap.set(prefixedIdentifier, [])
380
+ }
381
+
382
+ context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
383
+ }
384
+ },
385
+ matchComponents: function (components, options) {
386
+ let defaultOptions = {
387
+ respectPrefix: true,
388
+ respectImportant: false,
389
+ }
390
+
391
+ options = { ...defaultOptions, ...options }
392
+
393
+ let offset = offsets.components++
394
+
395
+ for (let identifier in components) {
396
+ let prefixedIdentifier = prefixIdentifier(identifier, options)
397
+ let rule = components[identifier]
398
+
399
+ classList.add([prefixedIdentifier, options])
400
+
401
+ function wrapped(modifier, { isOnlyPlugin }) {
402
+ let { type = 'any' } = options
403
+ type = [].concat(type)
404
+ let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
318
405
 
319
406
  if (value === undefined) {
320
407
  return []
@@ -338,24 +425,17 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
338
425
  return []
339
426
  }
340
427
 
341
- let includedRules = []
342
428
  let ruleSets = []
343
- .concat(
344
- rule(value, {
345
- includeRules(rules) {
346
- includedRules.push(...rules)
347
- },
348
- })
349
- )
429
+ .concat(rule(value))
350
430
  .filter(Boolean)
351
431
  .map((declaration) => ({
352
432
  [nameClass(identifier, modifier)]: declaration,
353
433
  }))
354
434
 
355
- return [...includedRules, ...ruleSets]
435
+ return ruleSets
356
436
  }
357
437
 
358
- let withOffsets = [{ sort: offset, layer: 'utilities', options }, wrapped]
438
+ let withOffsets = [{ sort: offset, layer: 'components', options }, wrapped]
359
439
 
360
440
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
361
441
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -364,7 +444,58 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
364
444
  context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
365
445
  }
366
446
  },
447
+ addVariant(variantName, variantFunctions, options = {}) {
448
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
449
+ if (typeof variantFunction !== 'string') {
450
+ // Safelist public API functions
451
+ return (api) => {
452
+ let { args, modifySelectors, container, separator, wrap, format } = api
453
+ let result = variantFunction(
454
+ Object.assign(
455
+ { modifySelectors, container, separator },
456
+ variantFunction[MATCH_VARIANT] && { args, wrap, format }
457
+ )
458
+ )
459
+
460
+ if (typeof result === 'string' && !isValidVariantFormatString(result)) {
461
+ throw new Error(
462
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
463
+ )
464
+ }
465
+
466
+ // result may be undefined with legacy variants that use APIs like `modifySelectors`
467
+ return result && parseVariant(result)(api)
468
+ }
469
+ }
470
+
471
+ if (!isValidVariantFormatString(variantFunction)) {
472
+ throw new Error(
473
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
474
+ )
475
+ }
476
+
477
+ return parseVariant(variantFunction)
478
+ })
479
+
480
+ insertInto(variantList, variantName, options)
481
+ variantMap.set(variantName, variantFunctions)
482
+ },
483
+ matchVariant: function (variants, options) {
484
+ for (let variant in variants) {
485
+ for (let [k, v] of Object.entries(options?.values ?? {})) {
486
+ api.addVariant(`${variant}-${k}`, variants[variant](v))
487
+ }
488
+
489
+ api.addVariant(
490
+ variant,
491
+ Object.assign(({ args }) => variants[variant](args), { [MATCH_VARIANT]: true }),
492
+ options
493
+ )
494
+ }
495
+ },
367
496
  }
497
+
498
+ return api
368
499
  }
369
500
 
370
501
  let fileModifiedMapCache = new WeakMap()
@@ -384,7 +515,14 @@ function trackModified(files, fileModifiedMap) {
384
515
  let parsed = url.parse(file)
385
516
  let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
386
517
  pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
387
- let newModified = fs.statSync(decodeURIComponent(pathname)).mtimeMs
518
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
519
+ if (!newModified) {
520
+ // It could happen that a file is passed in that doesn't exist. E.g.:
521
+ // postcss-cli will provide you a fake path when reading from stdin. This
522
+ // path then looks like /path-to-your-project/stdin In that case we just
523
+ // want to ignore it and don't track changes at all.
524
+ continue
525
+ }
388
526
 
389
527
  if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
390
528
  changed = true
@@ -444,20 +582,11 @@ function collectLayerPlugins(root) {
444
582
  }
445
583
  })
446
584
 
447
- root.walkRules((rule) => {
448
- // At this point it is safe to include all the left-over css from the
449
- // user's css file. This is because the `@tailwind` and `@layer` directives
450
- // will already be handled and will be removed from the css tree.
451
- layerPlugins.push(function ({ addUserCss }) {
452
- addUserCss(rule, { respectPrefix: false })
453
- })
454
- })
455
-
456
585
  return layerPlugins
457
586
  }
458
587
 
459
588
  function resolvePlugins(context, root) {
460
- let corePluginList = Object.entries(corePlugins)
589
+ let corePluginList = Object.entries({ ...variantPlugins, ...corePlugins })
461
590
  .map(([name, plugin]) => {
462
591
  if (!context.tailwindConfig.corePlugins.includes(name)) {
463
592
  return null
@@ -479,12 +608,18 @@ function resolvePlugins(context, root) {
479
608
 
480
609
  // TODO: This is a workaround for backwards compatibility, since custom variants
481
610
  // were historically sorted before screen/stackable variants.
482
- let beforeVariants = [corePlugins['pseudoElementVariants'], corePlugins['pseudoClassVariants']]
611
+ let beforeVariants = [
612
+ variantPlugins['pseudoElementVariants'],
613
+ variantPlugins['pseudoClassVariants'],
614
+ ]
483
615
  let afterVariants = [
484
- corePlugins['directionVariants'],
485
- corePlugins['reducedMotionVariants'],
486
- corePlugins['darkVariants'],
487
- corePlugins['screenVariants'],
616
+ variantPlugins['directionVariants'],
617
+ variantPlugins['reducedMotionVariants'],
618
+ variantPlugins['prefersContrastVariants'],
619
+ variantPlugins['darkVariants'],
620
+ variantPlugins['printVariant'],
621
+ variantPlugins['screenVariants'],
622
+ variantPlugins['orientationVariants'],
488
623
  ]
489
624
 
490
625
  return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
@@ -494,6 +629,7 @@ function registerPlugins(plugins, context) {
494
629
  let variantList = []
495
630
  let variantMap = new Map()
496
631
  let offsets = {
632
+ defaults: 0n,
497
633
  base: 0n,
498
634
  components: 0n,
499
635
  utilities: 0n,
@@ -521,20 +657,26 @@ function registerPlugins(plugins, context) {
521
657
 
522
658
  let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
523
659
  offsets.base,
660
+ offsets.defaults,
524
661
  offsets.components,
525
662
  offsets.utilities,
526
663
  offsets.user,
527
664
  ])
528
665
  let reservedBits = BigInt(highestOffset.toString(2).length)
529
666
 
667
+ // A number one less than the top range of the highest offset area
668
+ // so arbitrary properties are always sorted at the end.
669
+ context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
670
+
530
671
  context.layerOrder = {
531
- base: (1n << reservedBits) << 0n,
532
- components: (1n << reservedBits) << 1n,
533
- utilities: (1n << reservedBits) << 2n,
534
- user: (1n << reservedBits) << 3n,
672
+ defaults: (1n << reservedBits) << 0n,
673
+ base: (1n << reservedBits) << 1n,
674
+ components: (1n << reservedBits) << 2n,
675
+ utilities: (1n << reservedBits) << 3n,
676
+ user: (1n << reservedBits) << 4n,
535
677
  }
536
678
 
537
- reservedBits += 4n
679
+ reservedBits += 5n
538
680
 
539
681
  let offset = 0
540
682
  context.variantOrder = new Map(
@@ -559,105 +701,141 @@ function registerPlugins(plugins, context) {
559
701
  )
560
702
  }
561
703
 
562
- //
563
- let warnedAbout = new Set([])
564
- context.safelist = function () {
565
- let safelist = (context.tailwindConfig.safelist ?? []).filter(Boolean)
566
- if (safelist.length <= 0) return []
567
-
568
- let output = []
704
+ let safelist = (context.tailwindConfig.safelist ?? []).filter(Boolean)
705
+ if (safelist.length > 0) {
569
706
  let checks = []
570
707
 
571
708
  for (let value of safelist) {
572
709
  if (typeof value === 'string') {
573
- output.push(value)
710
+ context.changedContent.push({ content: value, extension: 'html' })
574
711
  continue
575
712
  }
576
713
 
577
714
  if (value instanceof RegExp) {
578
- if (!warnedAbout.has('root-regex')) {
579
- log.warn([
580
- // TODO: Improve this warning message
581
- 'RegExp in the safelist option is not supported.',
582
- 'Please use the object syntax instead: https://tailwindcss.com/docs/...',
583
- ])
584
- warnedAbout.add('root-regex')
585
- }
715
+ log.warn('root-regex', [
716
+ 'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
717
+ 'Update your `safelist` configuration to eliminate this warning.',
718
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
719
+ ])
586
720
  continue
587
721
  }
588
722
 
589
723
  checks.push(value)
590
724
  }
591
725
 
592
- if (checks.length <= 0) return output.map((value) => ({ raw: value, extension: 'html' }))
593
-
594
- let patternMatchingCount = new Map()
595
-
596
- for (let util of classList) {
597
- let utils = Array.isArray(util)
598
- ? (() => {
599
- let [utilName, options] = util
600
- return Object.keys(options?.values ?? {}).map((value) => formatClass(utilName, value))
601
- })()
602
- : [util]
603
-
604
- for (let util of utils) {
605
- for (let { pattern, variants = [] } of checks) {
606
- // RegExp with the /g flag are stateful, so let's reset the last
607
- // index pointer to reset the state.
608
- pattern.lastIndex = 0
609
-
610
- if (!patternMatchingCount.has(pattern)) {
611
- patternMatchingCount.set(pattern, 0)
612
- }
726
+ if (checks.length > 0) {
727
+ let patternMatchingCount = new Map()
728
+ let prefixLength = context.tailwindConfig.prefix.length
729
+
730
+ for (let util of classList) {
731
+ let utils = Array.isArray(util)
732
+ ? (() => {
733
+ let [utilName, options] = util
734
+ let values = Object.keys(options?.values ?? {})
735
+ let classes = values.map((value) => formatClass(utilName, value))
736
+
737
+ if (options?.supportsNegativeValues) {
738
+ // This is the normal negated version
739
+ // e.g. `-inset-1` or `-tw-inset-1`
740
+ classes = [...classes, ...classes.map((cls) => '-' + cls)]
741
+
742
+ // This is the negated version *after* the prefix
743
+ // e.g. `tw--inset-1`
744
+ // The prefix is already attached to util name
745
+ // So we add the negative after the prefix
746
+ classes = [
747
+ ...classes,
748
+ ...classes.map(
749
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
750
+ ),
751
+ ]
752
+ }
753
+
754
+ return classes
755
+ })()
756
+ : [util]
757
+
758
+ for (let util of utils) {
759
+ for (let { pattern, variants = [] } of checks) {
760
+ // RegExp with the /g flag are stateful, so let's reset the last
761
+ // index pointer to reset the state.
762
+ pattern.lastIndex = 0
763
+
764
+ if (!patternMatchingCount.has(pattern)) {
765
+ patternMatchingCount.set(pattern, 0)
766
+ }
613
767
 
614
- if (!pattern.test(util)) continue
768
+ if (!pattern.test(util)) continue
615
769
 
616
- patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1)
770
+ patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1)
617
771
 
618
- output.push(util)
619
- for (let variant of variants) {
620
- output.push(variant + context.tailwindConfig.separator + util)
772
+ context.changedContent.push({ content: util, extension: 'html' })
773
+ for (let variant of variants) {
774
+ context.changedContent.push({
775
+ content: variant + context.tailwindConfig.separator + util,
776
+ extension: 'html',
777
+ })
778
+ }
621
779
  }
622
780
  }
623
781
  }
624
- }
625
782
 
626
- for (let [regex, count] of patternMatchingCount.entries()) {
627
- if (count !== 0) continue
783
+ for (let [regex, count] of patternMatchingCount.entries()) {
784
+ if (count !== 0) continue
628
785
 
629
- log.warn([
630
- // TODO: Improve this warning message
631
- `You have a regex pattern in your "safelist" config (${regex}) that doesn't match any utilities.`,
632
- 'For more info, visit https://tailwindcss.com/docs/...',
633
- ])
786
+ log.warn([
787
+ `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
788
+ 'Fix this pattern or remove it from your `safelist` configuration.',
789
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
790
+ ])
791
+ }
634
792
  }
793
+ }
635
794
 
636
- return output.map((value) => ({ raw: value, extension: 'html' }))
795
+ // A list of utilities that are used by certain Tailwind CSS utilities but
796
+ // that don't exist on their own. This will result in them "not existing" and
797
+ // sorting could be weird since you still require them in order to make the
798
+ // host utitlies work properly. (Thanks Biology)
799
+ let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')])
800
+ context.getClassOrder = function getClassOrder(classes) {
801
+ let sortedClassNames = new Map()
802
+ for (let [sort, rule] of generateRules(new Set(classes), context)) {
803
+ if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue
804
+ sortedClassNames.set(rule.raws.tailwind.candidate, sort)
805
+ }
806
+
807
+ return classes.map((className) => {
808
+ let order = sortedClassNames.get(className) ?? null
809
+
810
+ if (order === null && parasiteUtilities.has(className)) {
811
+ // This will make sure that it is at the very beginning of the
812
+ // `components` layer which technically means 'before any
813
+ // components'.
814
+ order = context.layerOrder.components
815
+ }
816
+
817
+ return [className, order]
818
+ })
637
819
  }
638
820
 
639
- // Generate a list of strings for autocompletion purposes. Colors will have a
640
- // tuple with options, e.g.:
641
- // ['uppercase', 'lowercase', ['bg-red', { color: 'rgb(255 0 0)' }]]
642
- context.completions = function () {
643
- // TODO: Try and detect color from components?
644
- // TODO: Should we provide a simple "public api" file with functions?
821
+ // Generate a list of strings for autocompletion purposes, e.g.
822
+ // ['uppercase', 'lowercase', ...]
823
+ context.getClassList = function getClassList() {
645
824
  let output = []
646
825
 
647
826
  for (let util of classList) {
648
827
  if (Array.isArray(util)) {
649
828
  let [utilName, options] = util
650
- let isColor = [].concat(options.type).includes('color')
829
+ let negativeClasses = []
651
830
 
652
- if (isColor) {
653
- for (let [value, color] of Object.entries(options?.values ?? {})) {
654
- output.push([formatClass(utilName, value), { color }])
655
- }
656
- } else {
657
- for (let value of Object.keys(options?.values ?? {})) {
658
- output.push(formatClass(utilName, value))
831
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
832
+ output.push(formatClass(utilName, key))
833
+ if (options?.supportsNegativeValues && negateValue(value)) {
834
+ negativeClasses.push(formatClass(utilName, `-${key}`))
659
835
  }
660
836
  }
837
+
838
+ output.push(...negativeClasses)
661
839
  } else {
662
840
  output.push(util)
663
841
  }
@@ -717,6 +895,8 @@ export function getContext(
717
895
  existingContext = context
718
896
  }
719
897
 
898
+ let cssDidChange = hasContentChanged(sourcePath, root)
899
+
720
900
  // If there's already a context in the cache and we don't need to
721
901
  // reset the context, return the cached context.
722
902
  if (existingContext) {
@@ -724,7 +904,7 @@ export function getContext(
724
904
  [...contextDependencies],
725
905
  getFileModifiedMap(existingContext)
726
906
  )
727
- if (!contextDependenciesChanged) {
907
+ if (!contextDependenciesChanged && !cssDidChange) {
728
908
  return [existingContext, false]
729
909
  }
730
910
  }