tailwindcss 0.0.0-insiders.f4635e0 → 0.0.0-insiders.f4a2ccd

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 (238) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +15 -7
  3. package/colors.d.ts +3 -0
  4. package/colors.js +2 -304
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultConfig.js +2 -4
  7. package/defaultTheme.d.ts +4 -0
  8. package/defaultTheme.js +2 -4
  9. package/lib/cli/build/deps.js +54 -0
  10. package/lib/cli/build/index.js +52 -0
  11. package/lib/cli/build/plugin.js +376 -0
  12. package/lib/cli/build/utils.js +78 -0
  13. package/lib/cli/build/watching.js +180 -0
  14. package/lib/cli/help/index.js +71 -0
  15. package/lib/cli/index.js +230 -0
  16. package/lib/cli/init/index.js +61 -0
  17. package/lib/cli-peer-dependencies.js +22 -14
  18. package/lib/cli.js +4 -762
  19. package/lib/corePluginList.js +182 -5
  20. package/lib/corePlugins.js +4109 -2952
  21. package/lib/css/preflight.css +24 -9
  22. package/lib/featureFlags.js +68 -50
  23. package/lib/index.js +5 -30
  24. package/lib/lib/cacheInvalidation.js +90 -0
  25. package/lib/lib/collapseAdjacentRules.js +52 -36
  26. package/lib/lib/collapseDuplicateDeclarations.js +83 -0
  27. package/lib/lib/content.js +177 -0
  28. package/lib/lib/defaultExtractor.js +241 -0
  29. package/lib/lib/detectNesting.js +39 -17
  30. package/lib/lib/evaluateTailwindFunctions.js +203 -161
  31. package/lib/lib/expandApplyAtRules.js +503 -221
  32. package/lib/lib/expandTailwindAtRules.js +255 -243
  33. package/lib/lib/findAtConfigPath.js +44 -0
  34. package/lib/lib/generateRules.js +831 -317
  35. package/lib/lib/getModuleDependencies.js +93 -49
  36. package/lib/lib/load-config.js +36 -0
  37. package/lib/lib/normalizeTailwindDirectives.js +79 -60
  38. package/lib/lib/offsets.js +304 -0
  39. package/lib/lib/partitionApplyAtRules.js +56 -0
  40. package/lib/lib/regex.js +60 -0
  41. package/lib/lib/remap-bitfield.js +87 -0
  42. package/lib/lib/resolveDefaultsAtRules.js +150 -94
  43. package/lib/lib/setupContextUtils.js +1197 -685
  44. package/lib/lib/setupTrackingContext.js +150 -177
  45. package/lib/lib/sharedState.js +70 -21
  46. package/lib/lib/substituteScreenAtRules.js +26 -28
  47. package/lib/oxide/cli/build/deps.js +81 -0
  48. package/lib/oxide/cli/build/index.js +51 -0
  49. package/lib/oxide/cli/build/plugin.js +373 -0
  50. package/lib/oxide/cli/build/utils.js +77 -0
  51. package/lib/oxide/cli/build/watching.js +177 -0
  52. package/lib/oxide/cli/help/index.js +70 -0
  53. package/lib/oxide/cli/index.js +214 -0
  54. package/lib/oxide/cli/init/index.js +50 -0
  55. package/lib/oxide/cli.js +5 -0
  56. package/lib/oxide/postcss-plugin.js +2 -0
  57. package/lib/plugin.js +98 -0
  58. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  59. package/lib/postcss-plugins/nesting/index.js +19 -0
  60. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  61. package/lib/processTailwindFeatures.js +58 -56
  62. package/lib/public/colors.js +353 -0
  63. package/lib/public/create-plugin.js +15 -0
  64. package/lib/public/default-config.js +16 -0
  65. package/lib/public/default-theme.js +16 -0
  66. package/lib/public/load-config.js +10 -0
  67. package/lib/public/resolve-config.js +22 -0
  68. package/lib/util/applyImportantSelector.js +22 -0
  69. package/lib/util/bigSign.js +7 -6
  70. package/lib/util/buildMediaQuery.js +21 -32
  71. package/lib/util/cloneDeep.js +16 -14
  72. package/lib/util/cloneNodes.js +29 -15
  73. package/lib/util/color.js +104 -66
  74. package/lib/util/configurePlugins.js +17 -15
  75. package/lib/util/createPlugin.js +23 -26
  76. package/lib/util/createUtilityPlugin.js +46 -46
  77. package/lib/util/dataTypes.js +266 -0
  78. package/lib/util/defaults.js +20 -15
  79. package/lib/util/escapeClassName.js +18 -17
  80. package/lib/util/escapeCommas.js +7 -6
  81. package/lib/util/flattenColorPalette.js +13 -12
  82. package/lib/util/formatVariantSelector.js +341 -0
  83. package/lib/util/getAllConfigs.js +44 -18
  84. package/lib/util/hashConfig.js +15 -12
  85. package/lib/util/isKeyframeRule.js +7 -6
  86. package/lib/util/isPlainObject.js +11 -11
  87. package/lib/util/isSyntacticallyValidPropertyValue.js +72 -0
  88. package/lib/util/log.js +52 -33
  89. package/lib/util/nameClass.js +35 -29
  90. package/lib/util/negateValue.js +31 -17
  91. package/lib/util/normalizeConfig.js +294 -0
  92. package/lib/util/normalizeScreens.js +170 -0
  93. package/lib/util/parseAnimationValue.js +85 -54
  94. package/lib/util/parseBoxShadowValue.js +84 -0
  95. package/lib/util/parseDependency.js +41 -70
  96. package/lib/util/parseGlob.js +34 -0
  97. package/lib/util/parseObjectStyles.js +30 -24
  98. package/lib/util/pluginUtils.js +234 -288
  99. package/lib/util/prefixSelector.js +33 -19
  100. package/lib/util/removeAlphaVariables.js +29 -0
  101. package/lib/util/resolveConfig.js +221 -256
  102. package/lib/util/resolveConfigPath.js +55 -48
  103. package/lib/util/responsive.js +18 -14
  104. package/lib/util/splitAtTopLevelOnly.js +49 -0
  105. package/lib/util/tap.js +8 -7
  106. package/lib/util/toColorValue.js +7 -6
  107. package/lib/util/toPath.js +27 -8
  108. package/lib/util/transformThemeValue.js +67 -28
  109. package/lib/util/validateConfig.js +24 -0
  110. package/lib/util/validateFormalSyntax.js +24 -0
  111. package/lib/util/withAlphaVariable.js +67 -57
  112. package/loadConfig.d.ts +4 -0
  113. package/loadConfig.js +2 -0
  114. package/nesting/index.js +2 -12
  115. package/package.json +69 -66
  116. package/peers/index.js +76328 -84169
  117. package/plugin.d.ts +11 -0
  118. package/plugin.js +1 -2
  119. package/resolveConfig.d.ts +12 -0
  120. package/resolveConfig.js +2 -7
  121. package/scripts/create-plugin-list.js +2 -2
  122. package/scripts/generate-types.js +105 -0
  123. package/scripts/release-channel.js +18 -0
  124. package/scripts/release-notes.js +21 -0
  125. package/scripts/swap-engines.js +40 -0
  126. package/scripts/type-utils.js +27 -0
  127. package/src/cli/build/deps.js +56 -0
  128. package/src/cli/build/index.js +49 -0
  129. package/src/cli/build/plugin.js +444 -0
  130. package/src/cli/build/utils.js +76 -0
  131. package/src/cli/build/watching.js +229 -0
  132. package/src/cli/help/index.js +70 -0
  133. package/src/cli/index.js +216 -0
  134. package/src/cli/init/index.js +79 -0
  135. package/src/cli-peer-dependencies.js +7 -1
  136. package/src/cli.js +4 -756
  137. package/src/corePluginList.js +1 -1
  138. package/src/corePlugins.js +2526 -1956
  139. package/src/css/preflight.css +24 -9
  140. package/src/featureFlags.js +33 -10
  141. package/src/index.js +4 -33
  142. package/src/lib/cacheInvalidation.js +52 -0
  143. package/src/lib/collapseAdjacentRules.js +21 -2
  144. package/src/lib/collapseDuplicateDeclarations.js +93 -0
  145. package/src/lib/content.js +208 -0
  146. package/src/lib/defaultExtractor.js +217 -0
  147. package/src/lib/detectNesting.js +30 -3
  148. package/src/lib/evaluateTailwindFunctions.js +84 -10
  149. package/src/lib/expandApplyAtRules.js +509 -153
  150. package/src/lib/expandTailwindAtRules.js +125 -104
  151. package/src/lib/findAtConfigPath.js +48 -0
  152. package/src/lib/generateRules.js +678 -70
  153. package/src/lib/getModuleDependencies.js +70 -30
  154. package/src/lib/load-config.ts +27 -0
  155. package/src/lib/normalizeTailwindDirectives.js +10 -3
  156. package/src/lib/offsets.js +373 -0
  157. package/src/lib/partitionApplyAtRules.js +52 -0
  158. package/src/lib/regex.js +74 -0
  159. package/src/lib/remap-bitfield.js +82 -0
  160. package/src/lib/resolveDefaultsAtRules.js +105 -47
  161. package/src/lib/setupContextUtils.js +848 -247
  162. package/src/lib/setupTrackingContext.js +53 -64
  163. package/src/lib/sharedState.js +60 -6
  164. package/src/lib/substituteScreenAtRules.js +6 -3
  165. package/src/oxide/cli/build/deps.ts +91 -0
  166. package/src/oxide/cli/build/index.ts +47 -0
  167. package/src/oxide/cli/build/plugin.ts +442 -0
  168. package/src/oxide/cli/build/utils.ts +74 -0
  169. package/src/oxide/cli/build/watching.ts +225 -0
  170. package/src/oxide/cli/help/index.ts +69 -0
  171. package/src/oxide/cli/index.ts +204 -0
  172. package/src/oxide/cli/init/index.ts +59 -0
  173. package/src/oxide/cli.ts +1 -0
  174. package/src/oxide/postcss-plugin.ts +1 -0
  175. package/src/plugin.js +107 -0
  176. package/src/postcss-plugins/nesting/README.md +42 -0
  177. package/src/postcss-plugins/nesting/index.js +13 -0
  178. package/src/postcss-plugins/nesting/plugin.js +80 -0
  179. package/src/processTailwindFeatures.js +18 -3
  180. package/src/public/colors.js +322 -0
  181. package/src/public/create-plugin.js +2 -0
  182. package/src/public/default-config.js +4 -0
  183. package/src/public/default-theme.js +4 -0
  184. package/src/public/load-config.js +2 -0
  185. package/src/public/resolve-config.js +7 -0
  186. package/src/util/applyImportantSelector.js +19 -0
  187. package/src/util/buildMediaQuery.js +14 -16
  188. package/src/util/cloneNodes.js +19 -2
  189. package/src/util/color.js +43 -11
  190. package/src/util/createUtilityPlugin.js +2 -11
  191. package/src/util/dataTypes.js +281 -0
  192. package/src/util/defaults.js +6 -0
  193. package/src/util/formatVariantSelector.js +412 -0
  194. package/src/util/getAllConfigs.js +21 -2
  195. package/src/util/isSyntacticallyValidPropertyValue.js +61 -0
  196. package/src/util/log.js +23 -22
  197. package/src/util/nameClass.js +6 -2
  198. package/src/util/negateValue.js +15 -5
  199. package/src/util/normalizeConfig.js +318 -0
  200. package/src/util/normalizeScreens.js +140 -0
  201. package/src/util/parseAnimationValue.js +7 -1
  202. package/src/util/parseBoxShadowValue.js +72 -0
  203. package/src/util/parseDependency.js +37 -38
  204. package/src/util/parseGlob.js +24 -0
  205. package/src/util/pluginUtils.js +219 -219
  206. package/src/util/prefixSelector.js +30 -13
  207. package/src/util/removeAlphaVariables.js +24 -0
  208. package/src/util/resolveConfig.js +86 -91
  209. package/src/util/resolveConfigPath.js +12 -1
  210. package/src/util/splitAtTopLevelOnly.js +52 -0
  211. package/src/util/toPath.js +23 -1
  212. package/src/util/transformThemeValue.js +33 -8
  213. package/src/util/validateConfig.js +13 -0
  214. package/src/util/validateFormalSyntax.js +34 -0
  215. package/src/util/withAlphaVariable.js +14 -9
  216. package/stubs/.gitignore +1 -0
  217. package/stubs/.prettierrc.json +6 -0
  218. package/stubs/{defaultConfig.stub.js → config.full.js} +332 -249
  219. package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -1
  220. package/stubs/postcss.config.js +6 -0
  221. package/stubs/tailwind.config.cjs +2 -0
  222. package/stubs/tailwind.config.js +2 -0
  223. package/stubs/tailwind.config.ts +3 -0
  224. package/types/config.d.ts +373 -0
  225. package/types/generated/.gitkeep +0 -0
  226. package/types/generated/colors.d.ts +298 -0
  227. package/types/generated/corePluginList.d.ts +1 -0
  228. package/types/generated/default-theme.d.ts +347 -0
  229. package/types/index.d.ts +7 -0
  230. package/CHANGELOG.md +0 -1742
  231. package/lib/constants.js +0 -37
  232. package/lib/lib/setupWatchingContext.js +0 -331
  233. package/nesting/plugin.js +0 -41
  234. package/scripts/install-integrations.js +0 -27
  235. package/scripts/rebuildFixtures.js +0 -68
  236. package/src/constants.js +0 -17
  237. package/src/lib/setupWatchingContext.js +0 -307
  238. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -1,13 +1,187 @@
1
1
  import postcss from 'postcss'
2
+ import parser from 'postcss-selector-parser'
3
+
2
4
  import { resolveMatches } from './generateRules'
3
- import bigSign from '../util/bigSign'
4
5
  import escapeClassName from '../util/escapeClassName'
6
+ import { applyImportantSelector } from '../util/applyImportantSelector'
7
+
8
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
9
+
10
+ function extractClasses(node) {
11
+ /** @type {Map<string, Set<string>>} */
12
+ let groups = new Map()
13
+
14
+ let container = postcss.root({ nodes: [node.clone()] })
15
+
16
+ container.walkRules((rule) => {
17
+ parser((selectors) => {
18
+ selectors.walkClasses((classSelector) => {
19
+ let parentSelector = classSelector.parent.toString()
20
+
21
+ let classes = groups.get(parentSelector)
22
+ if (!classes) {
23
+ groups.set(parentSelector, (classes = new Set()))
24
+ }
25
+
26
+ classes.add(classSelector.value)
27
+ })
28
+ }).processSync(rule.selector)
29
+ })
30
+
31
+ let normalizedGroups = Array.from(groups.values(), (classes) => Array.from(classes))
32
+ let classes = normalizedGroups.flat()
33
+
34
+ return Object.assign(classes, { groups: normalizedGroups })
35
+ }
36
+
37
+ let selectorExtractor = parser()
38
+
39
+ /**
40
+ * @param {string} ruleSelectors
41
+ */
42
+ function extractSelectors(ruleSelectors) {
43
+ return selectorExtractor.astSync(ruleSelectors)
44
+ }
45
+
46
+ function extractBaseCandidates(candidates, separator) {
47
+ let baseClasses = new Set()
48
+
49
+ for (let candidate of candidates) {
50
+ baseClasses.add(candidate.split(separator).pop())
51
+ }
52
+
53
+ return Array.from(baseClasses)
54
+ }
5
55
 
6
56
  function prefix(context, selector) {
7
57
  let prefix = context.tailwindConfig.prefix
8
58
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
9
59
  }
10
60
 
61
+ function* pathToRoot(node) {
62
+ yield node
63
+ while (node.parent) {
64
+ yield node.parent
65
+ node = node.parent
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Only clone the node itself and not its children
71
+ *
72
+ * @param {*} node
73
+ * @param {*} overrides
74
+ * @returns
75
+ */
76
+ function shallowClone(node, overrides = {}) {
77
+ let children = node.nodes
78
+ node.nodes = []
79
+
80
+ let tmp = node.clone(overrides)
81
+
82
+ node.nodes = children
83
+
84
+ return tmp
85
+ }
86
+
87
+ /**
88
+ * Clone just the nodes all the way to the top that are required to represent
89
+ * this singular rule in the tree.
90
+ *
91
+ * For example, if we have CSS like this:
92
+ * ```css
93
+ * @media (min-width: 768px) {
94
+ * @supports (display: grid) {
95
+ * .foo {
96
+ * display: grid;
97
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
98
+ * }
99
+ * }
100
+ *
101
+ * @supports (backdrop-filter: blur(1px)) {
102
+ * .bar {
103
+ * backdrop-filter: blur(1px);
104
+ * }
105
+ * }
106
+ *
107
+ * .baz {
108
+ * color: orange;
109
+ * }
110
+ * }
111
+ * ```
112
+ *
113
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
114
+ *
115
+ * ```css
116
+ * @media (min-width: 768px) {
117
+ * @supports (backdrop-filter: blur(1px)) {
118
+ * .bar {
119
+ * backdrop-filter: blur(1px);
120
+ * }
121
+ * }
122
+ * }
123
+ * ```
124
+ *
125
+ * @param {import('postcss').Node} node
126
+ */
127
+ function nestedClone(node) {
128
+ for (let parent of pathToRoot(node)) {
129
+ if (node === parent) {
130
+ continue
131
+ }
132
+
133
+ if (parent.type === 'root') {
134
+ break
135
+ }
136
+
137
+ node = shallowClone(parent, {
138
+ nodes: [node],
139
+ })
140
+ }
141
+
142
+ return node
143
+ }
144
+
145
+ /**
146
+ * @param {import('postcss').Root} root
147
+ */
148
+ function buildLocalApplyCache(root, context) {
149
+ /** @type {ApplyCache} */
150
+ let cache = new Map()
151
+
152
+ root.walkRules((rule) => {
153
+ // Ignore rules generated by Tailwind
154
+ for (let node of pathToRoot(rule)) {
155
+ if (node.raws.tailwind?.layer !== undefined) {
156
+ return
157
+ }
158
+ }
159
+
160
+ // Clone what's required to represent this singular rule in the tree
161
+ let container = nestedClone(rule)
162
+ let sort = context.offsets.create('user')
163
+
164
+ for (let className of extractClasses(rule)) {
165
+ let list = cache.get(className) || []
166
+ cache.set(className, list)
167
+
168
+ list.push([
169
+ {
170
+ layer: 'user',
171
+ sort,
172
+ important: false,
173
+ },
174
+ container,
175
+ ])
176
+ }
177
+ })
178
+
179
+ return cache
180
+ }
181
+
182
+ /**
183
+ * @returns {ApplyCache}
184
+ */
11
185
  function buildApplyCache(applyCandidates, context) {
12
186
  for (let candidate of applyCandidates) {
13
187
  if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
@@ -35,6 +209,43 @@ function buildApplyCache(applyCandidates, context) {
35
209
  return context.applyClassCache
36
210
  }
37
211
 
212
+ /**
213
+ * Build a cache only when it's first used
214
+ *
215
+ * @param {() => ApplyCache} buildCacheFn
216
+ * @returns {ApplyCache}
217
+ */
218
+ function lazyCache(buildCacheFn) {
219
+ let cache = null
220
+
221
+ return {
222
+ get: (name) => {
223
+ cache = cache || buildCacheFn()
224
+
225
+ return cache.get(name)
226
+ },
227
+ has: (name) => {
228
+ cache = cache || buildCacheFn()
229
+
230
+ return cache.has(name)
231
+ },
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Take a series of multiple caches and merge
237
+ * them so they act like one large cache
238
+ *
239
+ * @param {ApplyCache[]} caches
240
+ * @returns {ApplyCache}
241
+ */
242
+ function combineCaches(caches) {
243
+ return {
244
+ get: (name) => caches.flatMap((cache) => cache.get(name) || []),
245
+ has: (name) => caches.some((cache) => cache.has(name)),
246
+ }
247
+ }
248
+
38
249
  function extractApplyCandidates(params) {
39
250
  let candidates = params.split(/[\s\t\n]+/g)
40
251
 
@@ -45,48 +256,7 @@ function extractApplyCandidates(params) {
45
256
  return [candidates, false]
46
257
  }
47
258
 
48
- function partitionApplyParents(root) {
49
- let applyParents = new Set()
50
-
51
- root.walkAtRules('apply', (rule) => {
52
- applyParents.add(rule.parent)
53
- })
54
-
55
- for (let rule of applyParents) {
56
- let nodeGroups = []
57
- let lastGroup = []
58
-
59
- for (let node of rule.nodes) {
60
- if (node.type === 'atrule' && node.name === 'apply') {
61
- if (lastGroup.length > 0) {
62
- nodeGroups.push(lastGroup)
63
- lastGroup = []
64
- }
65
- nodeGroups.push([node])
66
- } else {
67
- lastGroup.push(node)
68
- }
69
- }
70
-
71
- if (lastGroup.length > 0) {
72
- nodeGroups.push(lastGroup)
73
- }
74
-
75
- if (nodeGroups.length === 1) {
76
- continue
77
- }
78
-
79
- for (let group of [...nodeGroups].reverse()) {
80
- let newParent = rule.clone({ nodes: [] })
81
- newParent.append(group)
82
- rule.after(newParent)
83
- }
84
-
85
- rule.remove()
86
- }
87
- }
88
-
89
- function processApply(root, context) {
259
+ function processApply(root, context, localCache) {
90
260
  let applyCandidates = new Set()
91
261
 
92
262
  // Collect all @apply rules and candidates
@@ -102,150 +272,336 @@ function processApply(root, context) {
102
272
  })
103
273
 
104
274
  // Start the @apply process if we have rules with @apply in them
105
- if (applies.length > 0) {
106
- // Fill up some caches!
107
- let applyClassCache = buildApplyCache(applyCandidates, context)
108
-
109
- /**
110
- * When we have an apply like this:
111
- *
112
- * .abc {
113
- * @apply hover:font-bold;
114
- * }
115
- *
116
- * What we essentially will do is resolve to this:
117
- *
118
- * .abc {
119
- * @apply .hover\:font-bold:hover {
120
- * font-weight: 500;
121
- * }
122
- * }
123
- *
124
- * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
125
- * What happens in this function is that we prepend a `.` and escape the candidate.
126
- * This will result in `.hover\:font-bold`
127
- * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
128
- */
129
- // TODO: Should we use postcss-selector-parser for this instead?
130
- function replaceSelector(selector, utilitySelectors, candidate) {
131
- let needle = `.${escapeClassName(candidate)}`
132
- let utilitySelectorsList = utilitySelectors.split(/\s*,\s*/g)
133
-
134
- return selector
135
- .split(/\s*,\s*/g)
136
- .map((s) => {
137
- let replaced = []
138
-
139
- for (let utilitySelector of utilitySelectorsList) {
140
- let replacedSelector = utilitySelector.replace(needle, s)
141
- if (replacedSelector === utilitySelector) {
142
- continue
143
- }
144
- replaced.push(replacedSelector)
275
+ if (applies.length === 0) {
276
+ return
277
+ }
278
+
279
+ // Fill up some caches!
280
+ let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
281
+
282
+ /**
283
+ * When we have an apply like this:
284
+ *
285
+ * .abc {
286
+ * @apply hover:font-bold;
287
+ * }
288
+ *
289
+ * What we essentially will do is resolve to this:
290
+ *
291
+ * .abc {
292
+ * @apply .hover\:font-bold:hover {
293
+ * font-weight: 500;
294
+ * }
295
+ * }
296
+ *
297
+ * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
298
+ * What happens in this function is that we prepend a `.` and escape the candidate.
299
+ * This will result in `.hover\:font-bold`
300
+ * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
301
+ *
302
+ * @param {string} selector
303
+ * @param {string} utilitySelectors
304
+ * @param {string} candidate
305
+ */
306
+ function replaceSelector(selector, utilitySelectors, candidate) {
307
+ let selectorList = extractSelectors(selector)
308
+ let utilitySelectorsList = extractSelectors(utilitySelectors)
309
+ let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
310
+ let candidateClass = candidateList.nodes[0].nodes[0]
311
+
312
+ selectorList.each((sel) => {
313
+ /** @type {Set<import('postcss-selector-parser').Selector>} */
314
+ let replaced = new Set()
315
+
316
+ utilitySelectorsList.each((utilitySelector) => {
317
+ let hasReplaced = false
318
+ utilitySelector = utilitySelector.clone()
319
+
320
+ utilitySelector.walkClasses((node) => {
321
+ if (node.value !== candidateClass.value) {
322
+ return
145
323
  }
146
- return replaced.join(', ')
147
- })
148
- .join(', ')
149
- }
150
324
 
151
- let perParentApplies = new Map()
325
+ // Don't replace multiple instances of the same class
326
+ // This is theoretically correct but only partially
327
+ // We'd need to generate every possible permutation of the replacement
328
+ // For example with `.foo + .foo { … }` and `section { @apply foo; }`
329
+ // We'd need to generate all of these:
330
+ // - `.foo + .foo`
331
+ // - `.foo + section`
332
+ // - `section + .foo`
333
+ // - `section + section`
334
+ if (hasReplaced) {
335
+ return
336
+ }
152
337
 
153
- // Collect all apply candidates and their rules
154
- for (let apply of applies) {
155
- let candidates = perParentApplies.get(apply.parent) || []
338
+ // Since you can only `@apply` class names this is sufficient
339
+ // We want to replace the matched class name with the selector the user is using
340
+ // Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
341
+ node.replaceWith(...sel.nodes.map((node) => node.clone()))
156
342
 
157
- perParentApplies.set(apply.parent, candidates)
343
+ // Record that we did something and we want to use this new selector
344
+ replaced.add(utilitySelector)
158
345
 
159
- let [applyCandidates, important] = extractApplyCandidates(apply.params)
346
+ hasReplaced = true
347
+ })
348
+ })
349
+
350
+ // Sort tag names before class names (but only sort each group (separated by a combinator)
351
+ // separately and not in total)
352
+ // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
353
+ for (let sel of replaced) {
354
+ let groups = [[]]
355
+ for (let node of sel.nodes) {
356
+ if (node.type === 'combinator') {
357
+ groups.push(node)
358
+ groups.push([])
359
+ } else {
360
+ let last = groups[groups.length - 1]
361
+ last.push(node)
362
+ }
363
+ }
160
364
 
161
- if (apply.parent.type === 'atrule') {
162
- if (apply.parent.name === 'screen') {
163
- const screenType = apply.parent.params
365
+ sel.nodes = []
366
+
367
+ for (let group of groups) {
368
+ if (Array.isArray(group)) {
369
+ group.sort((a, b) => {
370
+ if (a.type === 'tag' && b.type === 'class') {
371
+ return -1
372
+ } else if (a.type === 'class' && b.type === 'tag') {
373
+ return 1
374
+ } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
375
+ return -1
376
+ } else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
377
+ return 1
378
+ }
379
+
380
+ return 0
381
+ })
382
+ }
164
383
 
165
- throw apply.error(
166
- `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
167
- .map((c) => `${screenType}:${c}`)
168
- .join(' ')} instead.`
169
- )
384
+ sel.nodes = sel.nodes.concat(group)
170
385
  }
386
+ }
387
+
388
+ sel.replaceWith(...replaced)
389
+ })
390
+
391
+ return selectorList.toString()
392
+ }
393
+
394
+ let perParentApplies = new Map()
395
+
396
+ // Collect all apply candidates and their rules
397
+ for (let apply of applies) {
398
+ let [candidates] = perParentApplies.get(apply.parent) || [[], apply.source]
399
+
400
+ perParentApplies.set(apply.parent, [candidates, apply.source])
401
+
402
+ let [applyCandidates, important] = extractApplyCandidates(apply.params)
403
+
404
+ if (apply.parent.type === 'atrule') {
405
+ if (apply.parent.name === 'screen') {
406
+ let screenType = apply.parent.params
171
407
 
172
408
  throw apply.error(
173
- `@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
409
+ `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
410
+ .map((c) => `${screenType}:${c}`)
411
+ .join(' ')} instead.`
174
412
  )
175
413
  }
176
414
 
177
- for (let applyCandidate of applyCandidates) {
178
- if (!applyClassCache.has(applyCandidate)) {
179
- if (applyCandidate === prefix(context, 'group')) {
180
- // TODO: Link to specific documentation page with error code.
181
- throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
182
- }
415
+ throw apply.error(
416
+ `@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
417
+ )
418
+ }
419
+
420
+ for (let applyCandidate of applyCandidates) {
421
+ if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
422
+ // TODO: Link to specific documentation page with error code.
423
+ throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
424
+ }
183
425
 
184
- throw apply.error(
185
- `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
426
+ if (!applyClassCache.has(applyCandidate)) {
427
+ throw apply.error(
428
+ `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
429
+ )
430
+ }
431
+
432
+ let rules = applyClassCache.get(applyCandidate)
433
+
434
+ candidates.push([applyCandidate, important, rules])
435
+ }
436
+ }
437
+
438
+ for (let [parent, [candidates, atApplySource]] of perParentApplies) {
439
+ let siblings = []
440
+
441
+ for (let [applyCandidate, important, rules] of candidates) {
442
+ let potentialApplyCandidates = [
443
+ applyCandidate,
444
+ ...extractBaseCandidates([applyCandidate], context.tailwindConfig.separator),
445
+ ]
446
+
447
+ for (let [meta, node] of rules) {
448
+ let parentClasses = extractClasses(parent)
449
+ let nodeClasses = extractClasses(node)
450
+
451
+ // When we encounter a rule like `.dark .a, .b { … }` we only want to be left with `[.dark, .a]` if the base applyCandidate is `.a` or with `[.b]` if the base applyCandidate is `.b`
452
+ // So we've split them into groups
453
+ nodeClasses = nodeClasses.groups
454
+ .filter((classList) =>
455
+ classList.some((className) => potentialApplyCandidates.includes(className))
456
+ )
457
+ .flat()
458
+
459
+ // Add base utility classes from the @apply node to the list of
460
+ // classes to check whether it intersects and therefore results in a
461
+ // circular dependency or not.
462
+ //
463
+ // E.g.:
464
+ // .foo {
465
+ // @apply hover:a; // This applies "a" but with a modifier
466
+ // }
467
+ //
468
+ // We only have to do that with base classes of the `node`, not of the `parent`
469
+ // E.g.:
470
+ // .hover\:foo {
471
+ // @apply bar;
472
+ // }
473
+ // .bar {
474
+ // @apply foo;
475
+ // }
476
+ //
477
+ // This should not result in a circular dependency because we are
478
+ // just applying `.foo` and the rule above is `.hover\:foo` which is
479
+ // unrelated. However, if we were to apply `hover:foo` then we _did_
480
+ // have to include this one.
481
+ nodeClasses = nodeClasses.concat(
482
+ extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
483
+ )
484
+
485
+ let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
486
+ if (intersects) {
487
+ throw node.error(
488
+ `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
186
489
  )
187
490
  }
188
491
 
189
- let rules = applyClassCache.get(applyCandidate)
492
+ let root = postcss.root({ nodes: [node.clone()] })
190
493
 
191
- candidates.push([applyCandidate, important, rules])
192
- }
193
- }
494
+ // Make sure every node in the entire tree points back at the @apply rule that generated it
495
+ root.walk((node) => {
496
+ node.source = atApplySource
497
+ })
498
+
499
+ let canRewriteSelector =
500
+ node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
501
+
502
+ if (canRewriteSelector) {
503
+ root.walkRules((rule) => {
504
+ // Let's imagine you have the following structure:
505
+ //
506
+ // .foo {
507
+ // @apply bar;
508
+ // }
509
+ //
510
+ // @supports (a: b) {
511
+ // .bar {
512
+ // color: blue
513
+ // }
514
+ //
515
+ // .something-unrelated {}
516
+ // }
517
+ //
518
+ // In this case we want to apply `.bar` but it happens to be in
519
+ // an atrule node. We clone that node instead of the nested one
520
+ // because we still want that @supports rule to be there once we
521
+ // applied everything.
522
+ //
523
+ // However it happens to be that the `.something-unrelated` is
524
+ // also in that same shared @supports atrule. This is not good,
525
+ // and this should not be there. The good part is that this is
526
+ // a clone already and it can be safely removed. The question is
527
+ // how do we know we can remove it. Basically what we can do is
528
+ // match it against the applyCandidate that you want to apply. If
529
+ // it doesn't match the we can safely delete it.
530
+ //
531
+ // If we didn't do this, then the `replaceSelector` function
532
+ // would have replaced this with something that didn't exist and
533
+ // therefore it removed the selector altogether. In this specific
534
+ // case it would result in `{}` instead of `.something-unrelated {}`
535
+ if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
536
+ rule.remove()
537
+ return
538
+ }
539
+
540
+ // Strip the important selector from the parent selector if at the beginning
541
+ let importantSelector =
542
+ typeof context.tailwindConfig.important === 'string'
543
+ ? context.tailwindConfig.important
544
+ : null
194
545
 
195
- for (const [parent, candidates] of perParentApplies) {
196
- let siblings = []
546
+ // We only want to move the "important" selector if this is a Tailwind-generated utility
547
+ // We do *not* want to do this for user CSS that happens to be structured the same
548
+ let isGenerated = parent.raws.tailwind !== undefined
197
549
 
198
- for (let [applyCandidate, important, rules] of candidates) {
199
- for (let [meta, node] of rules) {
200
- let root = postcss.root({ nodes: [node.clone()] })
201
- let canRewriteSelector =
202
- node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
550
+ let parentSelector =
551
+ isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0
552
+ ? parent.selector.slice(importantSelector.length)
553
+ : parent.selector
203
554
 
204
- if (canRewriteSelector) {
205
- root.walkRules((rule) => {
206
- rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)
555
+ rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
207
556
 
208
- rule.walkDecls((d) => {
209
- d.important = meta.important || important
210
- })
557
+ // And then re-add it if it was removed
558
+ if (importantSelector && parentSelector !== parent.selector) {
559
+ rule.selector = applyImportantSelector(rule.selector, importantSelector)
560
+ }
561
+
562
+ rule.walkDecls((d) => {
563
+ d.important = meta.important || important
211
564
  })
212
- }
565
+ })
566
+ }
213
567
 
214
- // Insert it
215
- siblings.push([
216
- // Ensure that when we are sorting, that we take the layer order into account
217
- { ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
218
- root.nodes[0],
219
- ])
568
+ // It could be that the node we were inserted was removed because the class didn't match
569
+ // If that was the *only* rule in the parent, then we have nothing add so we skip it
570
+ if (!root.nodes[0]) {
571
+ continue
220
572
  }
573
+
574
+ // Insert it
575
+ siblings.push([meta.sort, root.nodes[0]])
221
576
  }
577
+ }
222
578
 
223
- // Inject the rules, sorted, correctly
224
- let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
579
+ // Inject the rules, sorted, correctly
580
+ let nodes = context.offsets.sort(siblings).map((s) => s[1])
225
581
 
226
- // console.log(parent)
227
- // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
228
- parent.after(nodes)
229
- }
582
+ // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
583
+ parent.after(nodes)
584
+ }
230
585
 
231
- for (let apply of applies) {
232
- // If there are left-over declarations, just remove the @apply
233
- if (apply.parent.nodes.length > 1) {
234
- apply.remove()
235
- } else {
236
- // The node is empty, drop the full node
237
- apply.parent.remove()
238
- }
586
+ for (let apply of applies) {
587
+ // If there are left-over declarations, just remove the @apply
588
+ if (apply.parent.nodes.length > 1) {
589
+ apply.remove()
590
+ } else {
591
+ // The node is empty, drop the full node
592
+ apply.parent.remove()
239
593
  }
240
-
241
- // Do it again, in case we have other `@apply` rules
242
- processApply(root, context)
243
594
  }
595
+
596
+ // Do it again, in case we have other `@apply` rules
597
+ processApply(root, context, localCache)
244
598
  }
245
599
 
246
600
  export default function expandApplyAtRules(context) {
247
601
  return (root) => {
248
- partitionApplyParents(root)
249
- processApply(root, context)
602
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
603
+ let localCache = lazyCache(() => buildLocalApplyCache(root, context))
604
+
605
+ processApply(root, context, localCache)
250
606
  }
251
607
  }