Skip to content

Commit 0270028

Browse files
committed
perf: use index-based recursion to avoid array allocations
Refactor getGroupRecursive to use index-based traversal instead of array slicing, and eliminate array mutation from shift(). Previous implementation issues: - classParts.slice(1) created a new array on every recursive call, causing O(n) allocations for deep class name lookups - classParts.shift() mutated the array and moved all elements, causing O(n) element movement and potential V8 deoptimization Optimizations: - Added startIndex parameter to getGroupRecursive() to track position without array slicing - Replaced shift() with index offset calculation, eliminating array mutation - Only slice array when building classRest string for validators (less frequent path), and optimize with early check for startIndex === 0 This maintains monomorphic call sites (important for V8 optimization) while significantly reducing memory allocations during class group lookups. Benchmark results show ~1.6% improvement on 'collection without cache' benchmark (when combined with fast path optimization).
1 parent 8ffbd67 commit 0270028

File tree

1 file changed

+12
-9
lines changed

1 file changed

+12
-9
lines changed

src/lib/class-group-utils.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,9 @@ export const createClassGroupUtils = (config: AnyConfig) => {
5757
}
5858

5959
const classParts = className.split(CLASS_PART_SEPARATOR)
60-
// Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and remove it from classParts.
61-
if (classParts[0] === '' && classParts.length > 1) {
62-
classParts.shift()
63-
}
64-
return getGroupRecursive(classParts, classMap)
60+
// Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and skip it.
61+
const startIndex = classParts[0] === '' && classParts.length > 1 ? 1 : 0
62+
return getGroupRecursive(classParts, startIndex, classMap)
6563
}
6664

6765
const getConflictingClassGroupIds = (
@@ -87,18 +85,19 @@ export const createClassGroupUtils = (config: AnyConfig) => {
8785

8886
const getGroupRecursive = (
8987
classParts: string[],
88+
startIndex: number,
9089
classPartObject: ClassPartObject,
9190
): AnyClassGroupIds | undefined => {
92-
const classPathsLength = classParts.length
91+
const classPathsLength = classParts.length - startIndex
9392
if (classPathsLength === 0) {
9493
return classPartObject.classGroupId
9594
}
9695

97-
const currentClassPart = classParts[0]!
96+
const currentClassPart = classParts[startIndex]!
9897
const nextClassPartObject = classPartObject.nextPart.get(currentClassPart)
9998

10099
if (nextClassPartObject) {
101-
const result = getGroupRecursive(classParts.slice(1), nextClassPartObject)
100+
const result = getGroupRecursive(classParts, startIndex + 1, nextClassPartObject)
102101
if (result) return result
103102
}
104103

@@ -107,7 +106,11 @@ const getGroupRecursive = (
107106
return undefined
108107
}
109108

110-
const classRest = classParts.join(CLASS_PART_SEPARATOR)
109+
// Build classRest string efficiently by joining from startIndex onwards
110+
const classRest =
111+
startIndex === 0
112+
? classParts.join(CLASS_PART_SEPARATOR)
113+
: classParts.slice(startIndex).join(CLASS_PART_SEPARATOR)
111114
const validatorsLength = validators.length
112115

113116
for (let i = 0; i < validatorsLength; i++) {

0 commit comments

Comments
 (0)