Skip to content

Commit fbc0f2f

Browse files
authored
Improve @apply performance (#3718)
* Split the buildUtilityMap function to memoize the static part * fix tests
1 parent f9d54f0 commit fbc0f2f

File tree

1 file changed

+65
-19
lines changed

1 file changed

+65
-19
lines changed

src/lib/substituteClassApplyAtRules.js

+65-19
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ const cloneRuleWithParent = useMemo(
102102
(rule) => rule
103103
)
104104

105-
function buildUtilityMap(css, lookupTree) {
106-
let index = 0
105+
function buildCssUtilityMap(css, startIndex) {
106+
let index = startIndex
107107
const utilityMap = {}
108108

109109
function handle(getRule, rule) {
@@ -124,16 +124,6 @@ function buildUtilityMap(css, lookupTree) {
124124
})
125125
}
126126

127-
// Lookup tree is the big lookup tree, making the rule lazy allows us to save
128-
// some memory because we don't need everything.
129-
lookupTree.walkRules(
130-
handle.bind(null, (rule) => ({
131-
get rule() {
132-
return cloneRuleWithParent(rule)
133-
},
134-
}))
135-
)
136-
137127
// This is the end user's css. This might contain rules that we want to
138128
// apply. We want immediate copies of everything in case that we have user
139129
// defined classes that are recursively applied. Down below we are modifying
@@ -145,6 +135,44 @@ function buildUtilityMap(css, lookupTree) {
145135
return utilityMap
146136
}
147137

138+
const buildLookupTreeUtilityMap = useMemo(
139+
(lookupTree) => {
140+
let index = 0
141+
const utilityMap = {}
142+
143+
function handle(getRule, rule) {
144+
const utilityNames = extractUtilityNames(rule.selector)
145+
146+
utilityNames.forEach((utilityName, i) => {
147+
if (utilityMap[utilityName] === undefined) {
148+
utilityMap[utilityName] = []
149+
}
150+
151+
utilityMap[utilityName].push({
152+
index,
153+
utilityName,
154+
classPosition: i,
155+
...getRule(rule),
156+
})
157+
index++
158+
})
159+
}
160+
161+
// Lookup tree is the big lookup tree, making the rule lazy allows us to save
162+
// some memory because we don't need everything.
163+
lookupTree.walkRules(
164+
handle.bind(null, (rule) => ({
165+
get rule() {
166+
return cloneRuleWithParent(rule)
167+
},
168+
}))
169+
)
170+
171+
return utilityMap
172+
},
173+
(tree) => tree
174+
)
175+
148176
function mergeAdjacentRules(initialRule, rulesToInsert) {
149177
let previousRule = initialRule
150178

@@ -181,23 +209,41 @@ function mergeAdjacentRules(initialRule, rulesToInsert) {
181209
}
182210

183211
function makeExtractUtilityRules(css, lookupTree, config) {
184-
const utilityMap = buildUtilityMap(css, lookupTree)
212+
const lookupTreeUtilityMap = buildLookupTreeUtilityMap(lookupTree)
213+
const lookupTreeUtilityMapKeys = Object.keys(lookupTreeUtilityMap)
214+
const utilityMap = buildCssUtilityMap(css, lookupTreeUtilityMapKeys.length)
215+
216+
function getUtility(utilityName) {
217+
const utility = []
218+
if (lookupTreeUtilityMap[utilityName]) {
219+
utility.push(...lookupTreeUtilityMap[utilityName])
220+
}
221+
if (utilityMap[utilityName]) {
222+
utility.push(...utilityMap[utilityName])
223+
}
224+
if (utility.length > 0) return utility
225+
}
185226

186227
return function extractUtilityRules(utilityNames, rule) {
187228
const combined = []
188229

189230
utilityNames.forEach((utilityName) => {
190-
if (utilityMap[utilityName] === undefined) {
231+
const utility = getUtility(utilityName)
232+
if (utility === undefined) {
191233
// Look for prefixed utility in case the user has goofed
192-
const prefixedUtility = prefixSelector(config.prefix, `.${utilityName}`).slice(1)
234+
const prefixedUtilityName = prefixSelector(config.prefix, `.${utilityName}`).slice(1)
193235

194-
if (utilityMap[prefixedUtility] !== undefined) {
236+
const prefixedUtility = getUtility(prefixedUtilityName)
237+
if (prefixedUtility !== undefined) {
195238
throw rule.error(
196-
`The \`${utilityName}\` class does not exist, but \`${prefixedUtility}\` does. Did you forget the prefix?`
239+
`The \`${utilityName}\` class does not exist, but \`${prefixedUtilityName}\` does. Did you forget the prefix?`
197240
)
198241
}
199242

200-
const suggestedClass = didYouMean(utilityName, Object.keys(utilityMap))
243+
const suggestedClass = didYouMean(
244+
utilityName,
245+
Object.keys(utilityMap).concat(lookupTreeUtilityMapKeys)
246+
)
201247
const suggestionMessage = suggestedClass ? `, but \`${suggestedClass}\` does` : ''
202248

203249
throw rule.error(
@@ -206,7 +252,7 @@ function makeExtractUtilityRules(css, lookupTree, config) {
206252
)
207253
}
208254

209-
combined.push(...utilityMap[utilityName])
255+
combined.push(...utility)
210256
})
211257

212258
return combined.sort((a, b) => a.index - b.index)

0 commit comments

Comments
 (0)