@@ -40,135 +40,182 @@ function extractApplyCandidates(params) {
40
40
return [ candidates , false ]
41
41
}
42
42
43
- function expandApplyAtRules ( context ) {
44
- return function processApply ( root ) {
45
- let applyCandidates = new Set ( )
43
+ function partitionApplyParents ( root ) {
44
+ let applyParents = new Set ( )
45
+
46
+ root . walkAtRules ( 'apply' , ( rule ) => {
47
+ applyParents . add ( rule . parent )
48
+ } )
49
+
50
+ for ( let rule of applyParents ) {
51
+ let nodeGroups = [ ]
52
+ let lastGroup = [ ]
53
+
54
+ for ( let node of rule . nodes ) {
55
+ if ( node . type === 'atrule' && node . name === 'apply' ) {
56
+ if ( lastGroup . length > 0 ) {
57
+ nodeGroups . push ( lastGroup )
58
+ lastGroup = [ ]
59
+ }
60
+ nodeGroups . push ( [ node ] )
61
+ } else {
62
+ lastGroup . push ( node )
63
+ }
64
+ }
46
65
47
- // Collect all @apply rules and candidates
48
- let applies = [ ]
49
- root . walkAtRules ( 'apply' , ( rule ) => {
50
- let [ candidates , important ] = extractApplyCandidates ( rule . params )
66
+ if ( lastGroup . length > 0 ) {
67
+ nodeGroups . push ( lastGroup )
68
+ }
51
69
52
- for ( let util of candidates ) {
53
- applyCandidates . add ( util )
54
- }
55
- applies . push ( rule )
56
- } )
57
-
58
- // Start the @apply process if we have rules with @apply in them
59
- if ( applies . length > 0 ) {
60
- // Fill up some caches!
61
- let applyClassCache = buildApplyCache ( applyCandidates , context )
62
-
63
- /**
64
- * When we have an apply like this:
65
- *
66
- * .abc {
67
- * @apply hover:font-bold;
68
- * }
69
- *
70
- * What we essentially will do is resolve to this:
71
- *
72
- * .abc {
73
- * @apply .hover\:font-bold:hover {
74
- * font-weight: 500;
75
- * }
76
- * }
77
- *
78
- * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
79
- * What happens in this function is that we prepend a `.` and escape the candidate.
80
- * This will result in `.hover\:font-bold`
81
- * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
82
- */
83
- // TODO: Should we use postcss-selector-parser for this instead?
84
- function replaceSelector ( selector , utilitySelectors , candidate ) {
85
- let needle = `.${ escapeClassName ( candidate ) } `
86
- let utilitySelectorsList = utilitySelectors . split ( / \s * , \s * / g)
87
-
88
- return selector
89
- . split ( / \s * , \s * / g)
90
- . map ( ( s ) => {
91
- let replaced = [ ]
92
-
93
- for ( let utilitySelector of utilitySelectorsList ) {
94
- let replacedSelector = utilitySelector . replace ( needle , s )
95
- if ( replacedSelector === utilitySelector ) {
96
- continue
97
- }
98
- replaced . push ( replacedSelector )
99
- }
100
- return replaced . join ( ', ' )
101
- } )
102
- . join ( ', ' )
103
- }
70
+ if ( nodeGroups . length === 1 ) {
71
+ continue
72
+ }
104
73
105
- /** @type {Map<import('postcss').Node, [string, boolean, import('postcss').Node[]][]> } */
106
- let perParentApplies = new Map ( )
74
+ for ( let group of [ ...nodeGroups ] . reverse ( ) ) {
75
+ let newParent = rule . clone ( { nodes : [ ] } )
76
+ newParent . append ( group )
77
+ rule . after ( newParent )
78
+ }
107
79
108
- // Collect all apply candidates and their rules
109
- for ( let apply of applies ) {
110
- let candidates = perParentApplies . get ( apply . parent ) || [ ]
80
+ rule . remove ( )
81
+ }
82
+ }
111
83
112
- perParentApplies . set ( apply . parent , candidates )
84
+ function processApply ( root , context ) {
85
+ let applyCandidates = new Set ( )
113
86
114
- let [ applyCandidates , important ] = extractApplyCandidates ( apply . params )
87
+ // Collect all @apply rules and candidates
88
+ let applies = [ ]
89
+ root . walkAtRules ( 'apply' , ( rule ) => {
90
+ let [ candidates ] = extractApplyCandidates ( rule . params )
115
91
116
- for ( let applyCandidate of applyCandidates ) {
117
- if ( ! applyClassCache . has ( applyCandidate ) ) {
118
- throw apply . error (
119
- `The \`${ applyCandidate } \` class does not exist. If \`${ applyCandidate } \` is a custom class, make sure it is defined within a \`@layer\` directive.`
120
- )
92
+ for ( let util of candidates ) {
93
+ applyCandidates . add ( util )
94
+ }
95
+ applies . push ( rule )
96
+ } )
97
+
98
+ // Start the @apply process if we have rules with @apply in them
99
+ if ( applies . length > 0 ) {
100
+ // Fill up some caches!
101
+ let applyClassCache = buildApplyCache ( applyCandidates , context )
102
+
103
+ /**
104
+ * When we have an apply like this:
105
+ *
106
+ * .abc {
107
+ * @apply hover:font-bold;
108
+ * }
109
+ *
110
+ * What we essentially will do is resolve to this:
111
+ *
112
+ * .abc {
113
+ * @apply .hover\:font-bold:hover {
114
+ * font-weight: 500;
115
+ * }
116
+ * }
117
+ *
118
+ * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
119
+ * What happens in this function is that we prepend a `.` and escape the candidate.
120
+ * This will result in `.hover\:font-bold`
121
+ * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
122
+ */
123
+ // TODO: Should we use postcss-selector-parser for this instead?
124
+ function replaceSelector ( selector , utilitySelectors , candidate ) {
125
+ let needle = `.${ escapeClassName ( candidate ) } `
126
+ let utilitySelectorsList = utilitySelectors . split ( / \s * , \s * / g)
127
+
128
+ return selector
129
+ . split ( / \s * , \s * / g)
130
+ . map ( ( s ) => {
131
+ let replaced = [ ]
132
+
133
+ for ( let utilitySelector of utilitySelectorsList ) {
134
+ let replacedSelector = utilitySelector . replace ( needle , s )
135
+ if ( replacedSelector === utilitySelector ) {
136
+ continue
137
+ }
138
+ replaced . push ( replacedSelector )
121
139
}
140
+ return replaced . join ( ', ' )
141
+ } )
142
+ . join ( ', ' )
143
+ }
144
+
145
+ /** @type {Map<import('postcss').Node, [string, boolean, import('postcss').Node[]][]> } */
146
+ let perParentApplies = new Map ( )
147
+
148
+ // Collect all apply candidates and their rules
149
+ for ( let apply of applies ) {
150
+ let candidates = perParentApplies . get ( apply . parent ) || [ ]
122
151
123
- let rules = applyClassCache . get ( applyCandidate )
152
+ perParentApplies . set ( apply . parent , candidates )
124
153
125
- candidates . push ( [ applyCandidate , important , rules ] )
154
+ let [ applyCandidates , important ] = extractApplyCandidates ( apply . params )
155
+
156
+ for ( let applyCandidate of applyCandidates ) {
157
+ if ( ! applyClassCache . has ( applyCandidate ) ) {
158
+ throw apply . error (
159
+ `The \`${ applyCandidate } \` class does not exist. If \`${ applyCandidate } \` is a custom class, make sure it is defined within a \`@layer\` directive.`
160
+ )
126
161
}
162
+
163
+ let rules = applyClassCache . get ( applyCandidate )
164
+
165
+ candidates . push ( [ applyCandidate , important , rules ] )
127
166
}
167
+ }
128
168
129
- for ( const [ parent , candidates ] of perParentApplies ) {
130
- let siblings = [ ]
169
+ for ( const [ parent , candidates ] of perParentApplies ) {
170
+ let siblings = [ ]
131
171
132
- for ( let [ applyCandidate , important , rules ] of candidates ) {
133
- for ( let [ meta , node ] of rules ) {
134
- let root = postcss . root ( { nodes : [ node . clone ( ) ] } )
135
- let canRewriteSelector =
136
- node . type !== 'atrule' || ( node . type === 'atrule' && node . name !== 'keyframes' )
172
+ for ( let [ applyCandidate , important , rules ] of candidates ) {
173
+ for ( let [ meta , node ] of rules ) {
174
+ let root = postcss . root ( { nodes : [ node . clone ( ) ] } )
175
+ let canRewriteSelector =
176
+ node . type !== 'atrule' || ( node . type === 'atrule' && node . name !== 'keyframes' )
137
177
138
- if ( canRewriteSelector ) {
139
- root . walkRules ( ( rule ) => {
140
- rule . selector = replaceSelector ( parent . selector , rule . selector , applyCandidate )
178
+ if ( canRewriteSelector ) {
179
+ root . walkRules ( ( rule ) => {
180
+ rule . selector = replaceSelector ( parent . selector , rule . selector , applyCandidate )
141
181
142
- rule . walkDecls ( ( d ) => {
143
- d . important = important
144
- } )
182
+ rule . walkDecls ( ( d ) => {
183
+ d . important = important
145
184
} )
146
- }
147
-
148
- siblings . push ( [ meta , root . nodes [ 0 ] ] )
185
+ } )
149
186
}
187
+
188
+ siblings . push ( [ meta , root . nodes [ 0 ] ] )
150
189
}
190
+ }
151
191
152
- // Inject the rules, sorted, correctly
153
- const nodes = siblings . sort ( ( [ a ] , [ z ] ) => bigSign ( a . sort - z . sort ) ) . map ( ( s ) => s [ 1 ] )
192
+ // Inject the rules, sorted, correctly
193
+ let nodes = siblings . sort ( ( [ a ] , [ z ] ) => bigSign ( a . sort - z . sort ) ) . map ( ( s ) => s [ 1 ] )
154
194
155
- // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
156
- parent . after ( nodes )
157
- }
195
+ // console.log(parent)
196
+ // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
197
+ parent . after ( nodes )
198
+ }
158
199
159
- for ( let apply of applies ) {
160
- // If there are left-over declarations, just remove the @apply
161
- if ( apply . parent . nodes . length > 1 ) {
162
- apply . remove ( )
163
- } else {
164
- // The node is empty, drop the full node
165
- apply . parent . remove ( )
166
- }
200
+ for ( let apply of applies ) {
201
+ // If there are left-over declarations, just remove the @apply
202
+ if ( apply . parent . nodes . length > 1 ) {
203
+ apply . remove ( )
204
+ } else {
205
+ // The node is empty, drop the full node
206
+ apply . parent . remove ( )
167
207
}
168
-
169
- // Do it again, in case we have other `@apply` rules
170
- processApply ( root )
171
208
}
209
+
210
+ // Do it again, in case we have other `@apply` rules
211
+ processApply ( root , context )
212
+ }
213
+ }
214
+
215
+ function expandApplyAtRules ( context ) {
216
+ return ( root ) => {
217
+ partitionApplyParents ( root )
218
+ processApply ( root , context )
172
219
}
173
220
}
174
221
0 commit comments