tailwindcss 3.1.7 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +12 -8
  2. package/lib/cli/build/deps.js +54 -0
  3. package/lib/cli/build/index.js +44 -0
  4. package/lib/cli/build/plugin.js +335 -0
  5. package/lib/cli/build/utils.js +78 -0
  6. package/lib/cli/build/watching.js +113 -0
  7. package/lib/cli/help/index.js +71 -0
  8. package/lib/cli/index.js +18 -0
  9. package/lib/cli/init/index.js +46 -0
  10. package/lib/cli/shared.js +12 -0
  11. package/lib/cli.js +11 -590
  12. package/lib/corePlugins.js +332 -108
  13. package/lib/css/preflight.css +5 -0
  14. package/lib/featureFlags.js +7 -4
  15. package/lib/index.js +6 -1
  16. package/lib/lib/content.js +167 -0
  17. package/lib/lib/defaultExtractor.js +15 -10
  18. package/lib/lib/detectNesting.js +2 -2
  19. package/lib/lib/evaluateTailwindFunctions.js +17 -1
  20. package/lib/lib/expandApplyAtRules.js +71 -37
  21. package/lib/lib/expandTailwindAtRules.js +10 -42
  22. package/lib/lib/findAtConfigPath.js +44 -0
  23. package/lib/lib/generateRules.js +181 -96
  24. package/lib/lib/normalizeTailwindDirectives.js +1 -1
  25. package/lib/lib/offsets.js +217 -0
  26. package/lib/lib/regex.js +1 -1
  27. package/lib/lib/setupContextUtils.js +339 -100
  28. package/lib/lib/setupTrackingContext.js +5 -39
  29. package/lib/lib/sharedState.js +2 -0
  30. package/lib/public/colors.js +1 -1
  31. package/lib/util/buildMediaQuery.js +6 -3
  32. package/lib/util/configurePlugins.js +1 -1
  33. package/lib/util/dataTypes.js +15 -19
  34. package/lib/util/formatVariantSelector.js +92 -8
  35. package/lib/util/getAllConfigs.js +14 -3
  36. package/lib/util/isValidArbitraryValue.js +1 -1
  37. package/lib/util/nameClass.js +3 -0
  38. package/lib/util/negateValue.js +15 -2
  39. package/lib/util/normalizeConfig.js +17 -3
  40. package/lib/util/normalizeScreens.js +100 -3
  41. package/lib/util/parseAnimationValue.js +1 -1
  42. package/lib/util/parseBoxShadowValue.js +1 -1
  43. package/lib/util/parseDependency.js +33 -54
  44. package/lib/util/parseGlob.js +34 -0
  45. package/lib/util/parseObjectStyles.js +1 -1
  46. package/lib/util/pluginUtils.js +86 -17
  47. package/lib/util/resolveConfig.js +3 -3
  48. package/lib/util/splitAtTopLevelOnly.js +31 -81
  49. package/lib/util/transformThemeValue.js +9 -2
  50. package/lib/util/validateConfig.js +1 -1
  51. package/lib/util/validateFormalSyntax.js +24 -0
  52. package/package.json +14 -12
  53. package/peers/.DS_Store +0 -0
  54. package/peers/.svgo.yml +75 -0
  55. package/peers/index.js +3690 -2274
  56. package/peers/orders/concentric-css.json +299 -0
  57. package/peers/orders/smacss.json +299 -0
  58. package/peers/orders/source.json +295 -0
  59. package/plugin.d.ts +3 -3
  60. package/scripts/release-channel.js +18 -0
  61. package/scripts/release-notes.js +21 -0
  62. package/src/.DS_Store +0 -0
  63. package/src/cli/build/deps.js +56 -0
  64. package/src/cli/build/index.js +45 -0
  65. package/src/cli/build/plugin.js +397 -0
  66. package/src/cli/build/utils.js +76 -0
  67. package/src/cli/build/watching.js +134 -0
  68. package/src/cli/help/index.js +70 -0
  69. package/src/cli/index.js +3 -0
  70. package/src/cli/init/index.js +50 -0
  71. package/src/cli/shared.js +5 -0
  72. package/src/cli.js +4 -696
  73. package/src/corePlugins.js +262 -39
  74. package/src/css/preflight.css +5 -0
  75. package/src/featureFlags.js +12 -2
  76. package/src/index.js +5 -0
  77. package/src/lib/content.js +205 -0
  78. package/src/lib/defaultExtractor.js +3 -0
  79. package/src/lib/evaluateTailwindFunctions.js +22 -1
  80. package/src/lib/expandApplyAtRules.js +76 -29
  81. package/src/lib/expandTailwindAtRules.js +8 -46
  82. package/src/lib/findAtConfigPath.js +48 -0
  83. package/src/lib/generateRules.js +224 -105
  84. package/src/lib/offsets.js +270 -0
  85. package/src/lib/setupContextUtils.js +376 -89
  86. package/src/lib/setupTrackingContext.js +4 -45
  87. package/src/lib/sharedState.js +2 -0
  88. package/src/util/buildMediaQuery.js +5 -3
  89. package/src/util/dataTypes.js +15 -17
  90. package/src/util/formatVariantSelector.js +113 -9
  91. package/src/util/getAllConfigs.js +14 -2
  92. package/src/util/nameClass.js +4 -0
  93. package/src/util/negateValue.js +10 -2
  94. package/src/util/normalizeConfig.js +22 -2
  95. package/src/util/normalizeScreens.js +99 -4
  96. package/src/util/parseBoxShadowValue.js +1 -1
  97. package/src/util/parseDependency.js +37 -42
  98. package/src/util/parseGlob.js +24 -0
  99. package/src/util/pluginUtils.js +90 -14
  100. package/src/util/resolveConfig.js +2 -2
  101. package/src/util/splitAtTopLevelOnly.js +23 -49
  102. package/src/util/transformThemeValue.js +9 -1
  103. package/src/util/validateFormalSyntax.js +34 -0
  104. package/stubs/defaultConfig.stub.js +19 -3
  105. package/tmp.css +11 -0
  106. package/tmp.dependency-graph.js +2 -0
  107. package/tmp.in.css +3 -0
  108. package/tmp.js +0 -0
  109. package/tmp.out.css +524 -0
  110. package/types/config.d.ts +47 -13
  111. package/types/generated/default-theme.d.ts +11 -0
  112. package/CHANGELOG.md +0 -2222
@@ -0,0 +1,205 @@
1
+ // @ts-check
2
+
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import isGlob from 'is-glob'
6
+ import fastGlob from 'fast-glob'
7
+ import normalizePath from 'normalize-path'
8
+ import { parseGlob } from '../util/parseGlob'
9
+ import { env } from './sharedState'
10
+
11
+ /** @typedef {import('../../types/config.js').RawFile} RawFile */
12
+ /** @typedef {import('../../types/config.js').FilePath} FilePath */
13
+
14
+ /**
15
+ * @typedef {object} ContentPath
16
+ * @property {string} original
17
+ * @property {string} base
18
+ * @property {string | null} glob
19
+ * @property {boolean} ignore
20
+ * @property {string} pattern
21
+ */
22
+
23
+ /**
24
+ * Turn a list of content paths (absolute or not; glob or not) into a list of
25
+ * absolute file paths that exist on the filesystem
26
+ *
27
+ * If there are symlinks in the path then multiple paths will be returned
28
+ * one for the symlink and one for the actual file
29
+ *
30
+ * @param {*} context
31
+ * @param {import('tailwindcss').Config} tailwindConfig
32
+ * @returns {ContentPath[]}
33
+ */
34
+ export function parseCandidateFiles(context, tailwindConfig) {
35
+ let files = tailwindConfig.content.files
36
+
37
+ // Normalize the file globs
38
+ files = files.filter((filePath) => typeof filePath === 'string')
39
+ files = files.map(normalizePath)
40
+
41
+ // Split into included and excluded globs
42
+ let tasks = fastGlob.generateTasks(files)
43
+
44
+ /** @type {ContentPath[]} */
45
+ let included = []
46
+
47
+ /** @type {ContentPath[]} */
48
+ let excluded = []
49
+
50
+ for (const task of tasks) {
51
+ included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
52
+ excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
53
+ }
54
+
55
+ let paths = [...included, ...excluded]
56
+
57
+ // Resolve paths relative to the config file or cwd
58
+ paths = resolveRelativePaths(context, paths)
59
+
60
+ // Resolve symlinks if possible
61
+ paths = paths.flatMap(resolvePathSymlinks)
62
+
63
+ // Update cached patterns
64
+ paths = paths.map(resolveGlobPattern)
65
+
66
+ return paths
67
+ }
68
+
69
+ /**
70
+ *
71
+ * @param {string} filePath
72
+ * @param {boolean} ignore
73
+ * @returns {ContentPath}
74
+ */
75
+ function parseFilePath(filePath, ignore) {
76
+ let contentPath = {
77
+ original: filePath,
78
+ base: filePath,
79
+ ignore,
80
+ pattern: filePath,
81
+ glob: null,
82
+ }
83
+
84
+ if (isGlob(filePath)) {
85
+ Object.assign(contentPath, parseGlob(filePath))
86
+ }
87
+
88
+ return contentPath
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param {ContentPath} contentPath
94
+ * @returns {ContentPath}
95
+ */
96
+ function resolveGlobPattern(contentPath) {
97
+ contentPath.pattern = contentPath.glob
98
+ ? `${contentPath.base}/${contentPath.glob}`
99
+ : contentPath.base
100
+
101
+ contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
102
+
103
+ // This is required for Windows support to properly pick up Glob paths.
104
+ // Afaik, this technically shouldn't be needed but there's probably
105
+ // some internal, direct path matching with a normalized path in
106
+ // a package which can't handle mixed directory separators
107
+ contentPath.pattern = normalizePath(contentPath.pattern)
108
+
109
+ return contentPath
110
+ }
111
+
112
+ /**
113
+ * Resolve each path relative to the config file (when possible) if the experimental flag is enabled
114
+ * Otherwise, resolve relative to the current working directory
115
+ *
116
+ * @param {any} context
117
+ * @param {ContentPath[]} contentPaths
118
+ * @returns {ContentPath[]}
119
+ */
120
+ function resolveRelativePaths(context, contentPaths) {
121
+ let resolveFrom = []
122
+
123
+ // Resolve base paths relative to the config file (when possible) if the experimental flag is enabled
124
+ if (context.userConfigPath && context.tailwindConfig.content.relative) {
125
+ resolveFrom = [path.dirname(context.userConfigPath)]
126
+ }
127
+
128
+ return contentPaths.map((contentPath) => {
129
+ contentPath.base = path.resolve(...resolveFrom, contentPath.base)
130
+
131
+ return contentPath
132
+ })
133
+ }
134
+
135
+ /**
136
+ * Resolve the symlink for the base directory / file in each path
137
+ * These are added as additional dependencies to watch for changes because
138
+ * some tools (like webpack) will only watch the actual file or directory
139
+ * but not the symlink itself even in projects that use monorepos.
140
+ *
141
+ * @param {ContentPath} contentPath
142
+ * @returns {ContentPath[]}
143
+ */
144
+ function resolvePathSymlinks(contentPath) {
145
+ let paths = [contentPath]
146
+
147
+ try {
148
+ let resolvedPath = fs.realpathSync(contentPath.base)
149
+ if (resolvedPath !== contentPath.base) {
150
+ paths.push({
151
+ ...contentPath,
152
+ base: resolvedPath,
153
+ })
154
+ }
155
+ } catch {
156
+ // TODO: log this?
157
+ }
158
+
159
+ return paths
160
+ }
161
+
162
+ /**
163
+ * @param {any} context
164
+ * @param {ContentPath[]} candidateFiles
165
+ * @param {Map<string, number>} fileModifiedMap
166
+ * @returns {{ content: string, extension: string }[]}
167
+ */
168
+ export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
169
+ let changedContent = context.tailwindConfig.content.files
170
+ .filter((item) => typeof item.raw === 'string')
171
+ .map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
172
+
173
+ for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) {
174
+ let content = fs.readFileSync(changedFile, 'utf8')
175
+ let extension = path.extname(changedFile).slice(1)
176
+ changedContent.push({ content, extension })
177
+ }
178
+
179
+ return changedContent
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param {ContentPath[]} candidateFiles
185
+ * @param {Map<string, number>} fileModifiedMap
186
+ * @returns {Set<string>}
187
+ */
188
+ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
189
+ let paths = candidateFiles.map((contentPath) => contentPath.pattern)
190
+
191
+ let changedFiles = new Set()
192
+ env.DEBUG && console.time('Finding changed files')
193
+ let files = fastGlob.sync(paths, { absolute: true })
194
+ for (let file of files) {
195
+ let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
196
+ let modified = fs.statSync(file).mtimeMs
197
+
198
+ if (modified > prevModified) {
199
+ changedFiles.add(file)
200
+ fileModifiedMap.set(file, modified)
201
+ }
202
+ }
203
+ env.DEBUG && console.timeEnd('Finding changed files')
204
+ return changedFiles
205
+ }
@@ -71,6 +71,9 @@ function* buildRegExps(context) {
71
71
  let variantPatterns = [
72
72
  // Without quotes
73
73
  regex.any([
74
+ // This is here to provide special support for the `@` variant
75
+ regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
76
+
74
77
  regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
75
78
  regex.pattern([/[^\s"'`\[\\]+/, separator]),
76
79
  ]),
@@ -7,6 +7,7 @@ import buildMediaQuery from '../util/buildMediaQuery'
7
7
  import { toPath } from '../util/toPath'
8
8
  import { withAlphaValue } from '../util/withAlphaVariable'
9
9
  import { parseColorFormat } from '../util/pluginUtils'
10
+ import log from '../util/log'
10
11
 
11
12
  function isObject(input) {
12
13
  return typeof input === 'object' && input !== null
@@ -196,7 +197,9 @@ function resolvePath(config, path, defaultValue) {
196
197
  return results.find((result) => result.isValid) ?? results[0]
197
198
  }
198
199
 
199
- export default function ({ tailwindConfig: config }) {
200
+ export default function (context) {
201
+ let config = context.tailwindConfig
202
+
200
203
  let functions = {
201
204
  theme: (node, path, ...defaultValue) => {
202
205
  let { isValid, value, error, alpha } = resolvePath(
@@ -206,6 +209,24 @@ export default function ({ tailwindConfig: config }) {
206
209
  )
207
210
 
208
211
  if (!isValid) {
212
+ let parentNode = node.parent
213
+ let candidate = parentNode?.raws.tailwind?.candidate
214
+
215
+ if (parentNode && candidate !== undefined) {
216
+ // Remove this utility from any caches
217
+ context.markInvalidUtilityNode(parentNode)
218
+
219
+ // Remove the CSS node from the markup
220
+ parentNode.remove()
221
+
222
+ // Show a warning
223
+ log.warn('invalid-theme-key-in-class', [
224
+ `The utility \`${candidate}\` contains an invalid theme value and was not generated.`,
225
+ ])
226
+
227
+ return
228
+ }
229
+
209
230
  throw node.error(error)
210
231
  }
211
232
 
@@ -2,7 +2,6 @@ import postcss from 'postcss'
2
2
  import parser from 'postcss-selector-parser'
3
3
 
4
4
  import { resolveMatches } from './generateRules'
5
- import bigSign from '../util/bigSign'
6
5
  import escapeClassName from '../util/escapeClassName'
7
6
 
8
7
  /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
@@ -34,13 +33,13 @@ function extractClasses(node) {
34
33
  return Object.assign(classes, { groups: normalizedGroups })
35
34
  }
36
35
 
37
- let selectorExtractor = parser((root) => root.nodes.map((node) => node.toString()))
36
+ let selectorExtractor = parser()
38
37
 
39
38
  /**
40
39
  * @param {string} ruleSelectors
41
40
  */
42
41
  function extractSelectors(ruleSelectors) {
43
- return selectorExtractor.transformSync(ruleSelectors)
42
+ return selectorExtractor.astSync(ruleSelectors)
44
43
  }
45
44
 
46
45
  function extractBaseCandidates(candidates, separator) {
@@ -149,9 +148,7 @@ function buildLocalApplyCache(root, context) {
149
148
  /** @type {ApplyCache} */
150
149
  let cache = new Map()
151
150
 
152
- let highestOffset = context.layerOrder.user >> 4n
153
-
154
- root.walkRules((rule, idx) => {
151
+ root.walkRules((rule) => {
155
152
  // Ignore rules generated by Tailwind
156
153
  for (let node of pathToRoot(rule)) {
157
154
  if (node.raws.tailwind?.layer !== undefined) {
@@ -161,6 +158,7 @@ function buildLocalApplyCache(root, context) {
161
158
 
162
159
  // Clone what's required to represent this singular rule in the tree
163
160
  let container = nestedClone(rule)
161
+ let sort = context.offsets.create('user')
164
162
 
165
163
  for (let className of extractClasses(rule)) {
166
164
  let list = cache.get(className) || []
@@ -169,7 +167,7 @@ function buildLocalApplyCache(root, context) {
169
167
  list.push([
170
168
  {
171
169
  layer: 'user',
172
- sort: BigInt(idx) + highestOffset,
170
+ sort,
173
171
  important: false,
174
172
  },
175
173
  container,
@@ -299,30 +297,77 @@ function processApply(root, context, localCache) {
299
297
  * What happens in this function is that we prepend a `.` and escape the candidate.
300
298
  * This will result in `.hover\:font-bold`
301
299
  * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
300
+ *
301
+ * @param {string} selector
302
+ * @param {string} utilitySelectors
303
+ * @param {string} candidate
302
304
  */
303
- // TODO: Should we use postcss-selector-parser for this instead?
304
305
  function replaceSelector(selector, utilitySelectors, candidate) {
305
- let needle = `.${escapeClassName(candidate)}`
306
- let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
306
+ let selectorList = extractSelectors(selector)
307
307
  let utilitySelectorsList = extractSelectors(utilitySelectors)
308
+ let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
309
+ let candidateClass = candidateList.nodes[0].nodes[0]
310
+
311
+ selectorList.each((sel) => {
312
+ /** @type {Set<import('postcss-selector-parser').Selector>} */
313
+ let replaced = new Set()
308
314
 
309
- return extractSelectors(selector)
310
- .map((s) => {
311
- let replaced = []
315
+ utilitySelectorsList.each((utilitySelector) => {
316
+ let hasReplaced = false
317
+ utilitySelector = utilitySelector.clone()
312
318
 
313
- for (let utilitySelector of utilitySelectorsList) {
314
- let replacedSelector = utilitySelector
315
- for (const needle of needles) {
316
- replacedSelector = replacedSelector.replace(needle, s)
319
+ utilitySelector.walkClasses((node) => {
320
+ if (node.value !== candidateClass.value) {
321
+ return
317
322
  }
318
- if (replacedSelector === utilitySelector) {
319
- continue
323
+
324
+ // Don't replace multiple instances of the same class
325
+ // This is theoretically correct but only partially
326
+ // We'd need to generate every possible permutation of the replacement
327
+ // For example with `.foo + .foo { … }` and `section { @apply foo; }`
328
+ // We'd need to generate all of these:
329
+ // - `.foo + .foo`
330
+ // - `.foo + section`
331
+ // - `section + .foo`
332
+ // - `section + section`
333
+ if (hasReplaced) {
334
+ return
320
335
  }
321
- replaced.push(replacedSelector)
322
- }
323
- return replaced.join(', ')
336
+
337
+ // Since you can only `@apply` class names this is sufficient
338
+ // We want to replace the matched class name with the selector the user is using
339
+ // Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
340
+ node.replaceWith(...sel.nodes.map((node) => node.clone()))
341
+
342
+ // Record that we did something and we want to use this new selector
343
+ replaced.add(utilitySelector)
344
+
345
+ hasReplaced = true
346
+ })
324
347
  })
325
- .join(', ')
348
+
349
+ // Sort tag names before class names
350
+ // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
351
+ for (const sel of replaced) {
352
+ sel.sort((a, b) => {
353
+ if (a.type === 'tag' && b.type === 'class') {
354
+ return -1
355
+ } else if (a.type === 'class' && b.type === 'tag') {
356
+ return 1
357
+ } else if (a.type === 'class' && b.type === 'pseudo') {
358
+ return -1
359
+ } else if (a.type === 'pseudo' && b.type === 'class') {
360
+ return 1
361
+ }
362
+
363
+ return sel.index(a) - sel.index(b)
364
+ })
365
+ }
366
+
367
+ sel.replaceWith(...replaced)
368
+ })
369
+
370
+ return selectorList.toString()
326
371
  }
327
372
 
328
373
  let perParentApplies = new Map()
@@ -499,17 +544,19 @@ function processApply(root, context, localCache) {
499
544
  })
500
545
  }
501
546
 
547
+ // It could be that the node we were inserted was removed because the class didn't match
548
+ // If that was the *only* rule in the parent, then we have nothing add so we skip it
549
+ if (!root.nodes[0]) {
550
+ continue
551
+ }
552
+
502
553
  // Insert it
503
- siblings.push([
504
- // Ensure that when we are sorting, that we take the layer order into account
505
- { ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
506
- root.nodes[0],
507
- ])
554
+ siblings.push([meta.sort, root.nodes[0]])
508
555
  }
509
556
  }
510
557
 
511
558
  // Inject the rules, sorted, correctly
512
- let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
559
+ let nodes = context.offsets.sort(siblings).map((s) => s[1])
513
560
 
514
561
  // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
515
562
  parent.after(nodes)
@@ -1,7 +1,6 @@
1
1
  import LRU from 'quick-lru'
2
2
  import * as sharedState from './sharedState'
3
3
  import { generateRules } from './generateRules'
4
- import bigSign from '../util/bigSign'
5
4
  import log from '../util/log'
6
5
  import cloneNodes from '../util/cloneNodes'
7
6
  import { defaultExtractor } from './defaultExtractor'
@@ -74,8 +73,13 @@ function getClassCandidates(content, extractor, candidates, seen) {
74
73
  }
75
74
  }
76
75
 
76
+ /**
77
+ *
78
+ * @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
79
+ * @param {*} context
80
+ */
77
81
  function buildStylesheet(rules, context) {
78
- let sortedRules = rules.sort(([a], [z]) => bigSign(a - z))
82
+ let sortedRules = context.offsets.sort(rules)
79
83
 
80
84
  let returnValue = {
81
85
  base: new Set(),
@@ -83,48 +87,10 @@ function buildStylesheet(rules, context) {
83
87
  components: new Set(),
84
88
  utilities: new Set(),
85
89
  variants: new Set(),
86
-
87
- // All the CSS that is not Tailwind related can be put in this bucket. This
88
- // will make it easier to later use this information when we want to
89
- // `@apply` for example. The main reason we do this here is because we
90
- // still need to make sure the order is correct. Last but not least, we
91
- // will make sure to always re-inject this section into the css, even if
92
- // certain rules were not used. This means that it will look like a no-op
93
- // from the user's perspective, but we gathered all the useful information
94
- // we need.
95
- user: new Set(),
96
90
  }
97
91
 
98
92
  for (let [sort, rule] of sortedRules) {
99
- if (sort >= context.minimumScreen) {
100
- returnValue.variants.add(rule)
101
- continue
102
- }
103
-
104
- if (sort & context.layerOrder.base) {
105
- returnValue.base.add(rule)
106
- continue
107
- }
108
-
109
- if (sort & context.layerOrder.defaults) {
110
- returnValue.defaults.add(rule)
111
- continue
112
- }
113
-
114
- if (sort & context.layerOrder.components) {
115
- returnValue.components.add(rule)
116
- continue
117
- }
118
-
119
- if (sort & context.layerOrder.utilities) {
120
- returnValue.utilities.add(rule)
121
- continue
122
- }
123
-
124
- if (sort & context.layerOrder.user) {
125
- returnValue.user.add(rule)
126
- continue
127
- }
93
+ returnValue[sort.layer].add(rule)
128
94
  }
129
95
 
130
96
  return returnValue
@@ -177,16 +143,12 @@ export default function expandTailwindAtRules(context) {
177
143
  let classCacheCount = context.classCache.size
178
144
 
179
145
  env.DEBUG && console.time('Generate rules')
180
- let rules = generateRules(candidates, context)
146
+ generateRules(candidates, context)
181
147
  env.DEBUG && console.timeEnd('Generate rules')
182
148
 
183
149
  // We only ever add to the classCache, so if it didn't grow, there is nothing new.
184
150
  env.DEBUG && console.time('Build stylesheet')
185
151
  if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
186
- for (let rule of rules) {
187
- context.ruleCache.add(rule)
188
- }
189
-
190
152
  context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
191
153
  }
192
154
  env.DEBUG && console.timeEnd('Build stylesheet')
@@ -0,0 +1,48 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * Find the @config at-rule in the given CSS AST and return the relative path to the config file
6
+ *
7
+ * @param {import('postcss').Root} root
8
+ * @param {import('postcss').Result} result
9
+ */
10
+ export function findAtConfigPath(root, result) {
11
+ let configPath = null
12
+ let relativeTo = null
13
+
14
+ root.walkAtRules('config', (rule) => {
15
+ relativeTo = rule.source?.input.file ?? result.opts.from ?? null
16
+
17
+ if (relativeTo === null) {
18
+ throw rule.error(
19
+ 'The `@config` directive cannot be used without setting `from` in your PostCSS config.'
20
+ )
21
+ }
22
+
23
+ if (configPath) {
24
+ throw rule.error('Only one `@config` directive is allowed per file.')
25
+ }
26
+
27
+ let matches = rule.params.match(/(['"])(.*?)\1/)
28
+ if (!matches) {
29
+ throw rule.error('A path is required when using the `@config` directive.')
30
+ }
31
+
32
+ let inputPath = matches[2]
33
+ if (path.isAbsolute(inputPath)) {
34
+ throw rule.error('The `@config` directive cannot be used with an absolute path.')
35
+ }
36
+
37
+ configPath = path.resolve(path.dirname(relativeTo), inputPath)
38
+ if (!fs.existsSync(configPath)) {
39
+ throw rule.error(
40
+ `The config file at "${inputPath}" does not exist. Make sure the path is correct and the file exists.`
41
+ )
42
+ }
43
+
44
+ rule.remove()
45
+ })
46
+
47
+ return configPath ? configPath : null
48
+ }