@@ -5,6 +5,8 @@ import { resolveMatches } from './generateRules'
5
5
import bigSign from '../util/bigSign'
6
6
import escapeClassName from '../util/escapeClassName'
7
7
8
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]> } ApplyCache */
9
+
8
10
function extractClasses ( node ) {
9
11
let classes = new Set ( )
10
12
let container = postcss . root ( { nodes : [ node . clone ( ) ] } )
@@ -35,6 +37,131 @@ function prefix(context, selector) {
35
37
return typeof prefix === 'function' ? prefix ( selector ) : prefix + selector
36
38
}
37
39
40
+ function * pathToRoot ( node ) {
41
+ yield node
42
+ while ( node . parent ) {
43
+ yield node . parent
44
+ node = node . parent
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Only clone the node itself and not its children
50
+ *
51
+ * @param {* } node
52
+ * @param {* } overrides
53
+ * @returns
54
+ */
55
+ function shallowClone ( node , overrides = { } ) {
56
+ let children = node . nodes
57
+ node . nodes = [ ]
58
+
59
+ let tmp = node . clone ( overrides )
60
+
61
+ node . nodes = children
62
+
63
+ return tmp
64
+ }
65
+
66
+ /**
67
+ * Clone just the nodes all the way to the top that are required to represent
68
+ * this singular rule in the tree.
69
+ *
70
+ * For example, if we have CSS like this:
71
+ * ```css
72
+ * @media (min-width: 768px) {
73
+ * @supports (display: grid) {
74
+ * .foo {
75
+ * display: grid;
76
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
77
+ * }
78
+ * }
79
+ *
80
+ * @supports (backdrop-filter: blur(1px)) {
81
+ * .bar {
82
+ * backdrop-filter: blur(1px);
83
+ * }
84
+ * }
85
+ *
86
+ * .baz {
87
+ * color: orange;
88
+ * }
89
+ * }
90
+ * ```
91
+ *
92
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
93
+ *
94
+ * ```css
95
+ * @media (min-width: 768px) {
96
+ * @supports (backdrop-filter: blur(1px)) {
97
+ * .bar {
98
+ * backdrop-filter: blur(1px);
99
+ * }
100
+ * }
101
+ * }
102
+ * ```
103
+ *
104
+ * @param {import('postcss').Node } node
105
+ */
106
+ function nestedClone ( node ) {
107
+ for ( let parent of pathToRoot ( node ) ) {
108
+ if ( node === parent ) {
109
+ continue
110
+ }
111
+
112
+ if ( parent . type === 'root' ) {
113
+ break
114
+ }
115
+
116
+ node = shallowClone ( parent , {
117
+ nodes : [ node ] ,
118
+ } )
119
+ }
120
+
121
+ return node
122
+ }
123
+
124
+ /**
125
+ * @param {import('postcss').Root } root
126
+ */
127
+ function buildLocalApplyCache ( root , context ) {
128
+ /** @type {ApplyCache } */
129
+ let cache = new Map ( )
130
+
131
+ let highestOffset = context . layerOrder . user >> 4n
132
+
133
+ root . walkRules ( ( rule , idx ) => {
134
+ // Ignore rules generated by Tailwind
135
+ for ( let node of pathToRoot ( rule ) ) {
136
+ if ( node . raws . tailwind ?. layer !== undefined ) {
137
+ return
138
+ }
139
+ }
140
+
141
+ // Clone what's required to represent this singular rule in the tree
142
+ let container = nestedClone ( rule )
143
+
144
+ for ( let className of extractClasses ( rule ) ) {
145
+ let list = cache . get ( className ) || [ ]
146
+ cache . set ( className , list )
147
+
148
+ list . push ( [
149
+ {
150
+ layer : 'user' ,
151
+ sort : BigInt ( idx ) + highestOffset ,
152
+ important : false ,
153
+ } ,
154
+ container ,
155
+ ] )
156
+ }
157
+ } )
158
+
159
+ return cache
160
+ }
161
+
162
+ /**
163
+ * @returns {ApplyCache }
164
+ */
38
165
function buildApplyCache ( applyCandidates , context ) {
39
166
for ( let candidate of applyCandidates ) {
40
167
if ( context . notClassCache . has ( candidate ) || context . applyClassCache . has ( candidate ) ) {
@@ -62,6 +189,43 @@ function buildApplyCache(applyCandidates, context) {
62
189
return context . applyClassCache
63
190
}
64
191
192
+ /**
193
+ * Build a cache only when it's first used
194
+ *
195
+ * @param {() => ApplyCache } buildCacheFn
196
+ * @returns {ApplyCache }
197
+ */
198
+ function lazyCache ( buildCacheFn ) {
199
+ let cache = null
200
+
201
+ return {
202
+ get : ( name ) => {
203
+ cache = cache || buildCacheFn ( )
204
+
205
+ return cache . get ( name )
206
+ } ,
207
+ has : ( name ) => {
208
+ cache = cache || buildCacheFn ( )
209
+
210
+ return cache . has ( name )
211
+ } ,
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Take a series of multiple caches and merge
217
+ * them so they act like one large cache
218
+ *
219
+ * @param {ApplyCache[] } caches
220
+ * @returns {ApplyCache }
221
+ */
222
+ function combineCaches ( caches ) {
223
+ return {
224
+ get : ( name ) => caches . flatMap ( ( cache ) => cache . get ( name ) || [ ] ) ,
225
+ has : ( name ) => caches . some ( ( cache ) => cache . has ( name ) ) ,
226
+ }
227
+ }
228
+
65
229
function extractApplyCandidates ( params ) {
66
230
let candidates = params . split ( / [ \s \t \n ] + / g)
67
231
@@ -72,7 +236,7 @@ function extractApplyCandidates(params) {
72
236
return [ candidates , false ]
73
237
}
74
238
75
- function processApply ( root , context ) {
239
+ function processApply ( root , context , localCache ) {
76
240
let applyCandidates = new Set ( )
77
241
78
242
// Collect all @apply rules and candidates
@@ -90,7 +254,7 @@ function processApply(root, context) {
90
254
// Start the @apply process if we have rules with @apply in them
91
255
if ( applies . length > 0 ) {
92
256
// Fill up some caches!
93
- let applyClassCache = buildApplyCache ( applyCandidates , context )
257
+ let applyClassCache = combineCaches ( [ localCache , buildApplyCache ( applyCandidates , context ) ] )
94
258
95
259
/**
96
260
* When we have an apply like this:
@@ -302,12 +466,15 @@ function processApply(root, context) {
302
466
}
303
467
304
468
// Do it again, in case we have other `@apply` rules
305
- processApply ( root , context )
469
+ processApply ( root , context , localCache )
306
470
}
307
471
}
308
472
309
473
export default function expandApplyAtRules ( context ) {
310
474
return ( root ) => {
311
- processApply ( root , context )
475
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
476
+ let localCache = lazyCache ( ( ) => buildLocalApplyCache ( root , context ) )
477
+
478
+ processApply ( root , context , localCache )
312
479
}
313
480
}
0 commit comments