@@ -8,6 +8,7 @@ import substituteResponsiveAtRules from '../lib/substituteResponsiveAtRules'
8
8
import convertLayerAtRulesToControlComments from '../lib/convertLayerAtRulesToControlComments'
9
9
import substituteScreenAtRules from '../lib/substituteScreenAtRules'
10
10
import prefixSelector from '../util/prefixSelector'
11
+ import { useMemo } from '../util/useMemo'
11
12
12
13
function hasAtRule ( css , atRule ) {
13
14
let foundAtRule = false
@@ -20,35 +21,48 @@ function hasAtRule(css, atRule) {
20
21
return foundAtRule
21
22
}
22
23
24
+ function cloneWithoutChildren ( node ) {
25
+ if ( node . type === 'atrule' ) {
26
+ return postcss . atRule ( { name : node . name , params : node . params } )
27
+ }
28
+
29
+ if ( node . type === 'rule' ) {
30
+ return postcss . rule ( { name : node . name , selectors : node . selectors } )
31
+ }
32
+
33
+ const clone = node . clone ( )
34
+ clone . removeAll ( )
35
+ return clone
36
+ }
37
+
38
+ const tailwindApplyPlaceholder = selectorParser . attribute ( {
39
+ attribute : '__TAILWIND-APPLY-PLACEHOLDER__' ,
40
+ } )
41
+
23
42
function generateRulesFromApply ( { rule, utilityName : className , classPosition } , replaceWith ) {
24
- const processedSelectors = rule . selectors . map ( selector => {
25
- const processor = selectorParser ( selectors => {
26
- let i = 0
27
- selectors . walkClasses ( c => {
28
- if ( c . value === className && classPosition === i ) {
29
- c . replaceWith ( selectorParser . attribute ( { attribute : '__TAILWIND-APPLY-PLACEHOLDER__' } ) )
30
- }
31
- i ++
32
- } )
43
+ const parser = selectorParser ( selectors => {
44
+ let i = 0
45
+ selectors . walkClasses ( c => {
46
+ if ( classPosition === i ++ && c . value === className ) {
47
+ c . replaceWith ( tailwindApplyPlaceholder )
48
+ }
33
49
} )
50
+ } )
34
51
52
+ const processedSelectors = rule . selectors . map ( selector => {
35
53
// You could argue we should make this replacement at the AST level, but if we believe
36
54
// the placeholder string is safe from collisions then it is safe to do this is a simple
37
55
// string replacement, and much, much faster.
38
- const processedSelector = processor
39
- . processSync ( selector )
40
- . replace ( '[__TAILWIND-APPLY-PLACEHOLDER__]' , replaceWith )
41
-
42
- return processedSelector
56
+ return parser . processSync ( selector ) . replace ( '[__TAILWIND-APPLY-PLACEHOLDER__]' , replaceWith )
43
57
} )
44
58
45
59
const cloned = rule . clone ( )
46
60
let current = cloned
47
61
let parent = rule . parent
48
62
49
63
while ( parent && parent . type !== 'root' ) {
50
- const parentClone = parent . clone ( )
51
- parentClone . removeAll ( )
64
+ const parentClone = cloneWithoutChildren ( parent )
65
+
52
66
parentClone . append ( current )
53
67
current . parent = parentClone
54
68
current = parentClone
@@ -59,19 +73,21 @@ function generateRulesFromApply({ rule, utilityName: className, classPosition },
59
73
return current
60
74
}
61
75
62
- function extractUtilityNames ( selector ) {
63
- const processor = selectorParser ( selectors => {
64
- let classes = [ ]
76
+ const extractUtilityNamesParser = selectorParser ( selectors => {
77
+ let classes = [ ]
78
+ selectors . walkClasses ( c => classes . push ( c . value ) )
79
+ return classes
80
+ } )
65
81
66
- selectors . walkClasses ( c => {
67
- classes . push ( c )
68
- } )
69
-
70
- return classes . map ( c => c . value )
71
- } )
82
+ const extractUtilityNames = useMemo (
83
+ selector => extractUtilityNamesParser . transformSync ( selector ) ,
84
+ selector => selector
85
+ )
72
86
73
- return processor . transformSync ( selector )
74
- }
87
+ const cloneRuleWithParent = useMemo (
88
+ rule => rule . clone ( { parent : rule . parent } ) ,
89
+ rule => rule
90
+ )
75
91
76
92
function buildUtilityMap ( css ) {
77
93
let index = 0
@@ -89,8 +105,9 @@ function buildUtilityMap(css) {
89
105
index,
90
106
utilityName,
91
107
classPosition : i ,
92
- rule : rule . clone ( { parent : rule . parent } ) ,
93
- containsApply : hasAtRule ( rule , 'apply' ) ,
108
+ get rule ( ) {
109
+ return cloneRuleWithParent ( rule )
110
+ } ,
94
111
} )
95
112
index ++
96
113
} )
@@ -136,15 +153,11 @@ function mergeAdjacentRules(initialRule, rulesToInsert) {
136
153
137
154
function makeExtractUtilityRules ( css , config ) {
138
155
const utilityMap = buildUtilityMap ( css )
139
- const orderUtilityMap = _ . fromPairs (
140
- _ . flatMap ( _ . toPairs ( utilityMap ) , ( [ _utilityName , utilities ] ) => {
141
- return utilities . map ( utility => {
142
- return [ utility . index , utility ]
143
- } )
144
- } )
145
- )
146
- return function ( utilityNames , rule ) {
147
- return _ . flatMap ( utilityNames , utilityName => {
156
+
157
+ return function extractUtilityRules ( utilityNames , rule ) {
158
+ const combined = [ ]
159
+
160
+ utilityNames . forEach ( utilityName => {
148
161
if ( utilityMap [ utilityName ] === undefined ) {
149
162
// Look for prefixed utility in case the user has goofed
150
163
const prefixedUtility = prefixSelector ( config . prefix , `.${ utilityName } ` ) . slice ( 1 )
@@ -160,17 +173,18 @@ function makeExtractUtilityRules(css, config) {
160
173
{ word : utilityName }
161
174
)
162
175
}
163
- return utilityMap [ utilityName ] . map ( ( { index } ) => index )
176
+
177
+ combined . push ( ...utilityMap [ utilityName ] )
164
178
} )
165
- . sort ( ( a , b ) => a - b )
166
- . map ( i => orderUtilityMap [ i ] )
179
+
180
+ return combined . sort ( ( a , b ) => a . index - b . index )
167
181
}
168
182
}
169
183
170
184
function processApplyAtRules ( css , lookupTree , config ) {
171
185
const extractUtilityRules = makeExtractUtilityRules ( lookupTree , config )
172
186
173
- while ( hasAtRule ( css , 'apply' ) ) {
187
+ do {
174
188
css . walkRules ( rule => {
175
189
const applyRules = [ ]
176
190
@@ -229,13 +243,29 @@ function processApplyAtRules(css, lookupTree, config) {
229
243
rule . remove ( )
230
244
}
231
245
} )
232
- }
246
+
247
+ // We already know that we have at least 1 @apply rule. Otherwise this
248
+ // function would not have been called. Therefore we can execute this code
249
+ // at least once. This also means that in the best case scenario we only
250
+ // call this 2 times, instead of 3 times.
251
+ // 1st time -> before we call this function
252
+ // 2nd time -> when we check if we have to do this loop again (because do {} while (check))
253
+ // .. instead of
254
+ // 1st time -> before we call this function
255
+ // 2nd time -> when we check the first time (because while (check) do {})
256
+ // 3rd time -> when we re-check to see if we should do this loop again
257
+ } while ( hasAtRule ( css , 'apply' ) )
233
258
234
259
return css
235
260
}
236
261
237
262
export default function applyComplexClasses ( config , getProcessedPlugins ) {
238
263
return function ( css ) {
264
+ // We can stop already when we don't have any @apply rules. Vue users: you're welcome!
265
+ if ( ! hasAtRule ( css , 'apply' ) ) {
266
+ return css
267
+ }
268
+
239
269
// Tree already contains @tailwind rules, don't prepend default Tailwind tree
240
270
if ( hasAtRule ( css , 'tailwind' ) ) {
241
271
return processApplyAtRules ( css , css , config )
@@ -261,7 +291,7 @@ export default function applyComplexClasses(config, getProcessedPlugins) {
261
291
)
262
292
. then ( result => {
263
293
// Prepend Tailwind's generated classes to the tree so they are available for `@apply`
264
- const lookupTree = _ . tap ( css . clone ( ) , tree => tree . prepend ( result . root ) )
294
+ const lookupTree = _ . tap ( result . root , tree => tree . append ( css . clone ( ) ) )
265
295
return processApplyAtRules ( css , lookupTree , config )
266
296
} )
267
297
}
0 commit comments