Skip to content

Commit 1ceea2f

Browse files
committed
change underlying data structure
This improves memory usage and performance. The idea is exactly the same though. But instead of using `push()` and `pop()` we just build up 2 objects where we can track the number and parent individually based on the `depth`. This means that we only allocate 2 objects (which we change over time) we then simply use `parents[depth]` and `offsets[depth]` to get the necessary data out. Additionally, we're only storing the index/offset and the parent now. Since the incoming ast is `T[]`, it means that we don't really have a parent. For this we can create a fake parent (which is never passed to `enter(…)`, `exit(…)` or used by `path()`)
1 parent acb27ef commit 1ceea2f

File tree

1 file changed

+30
-20
lines changed

1 file changed

+30
-20
lines changed

packages/tailwindcss/src/walk.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,36 +57,44 @@ function walkImplementation<T extends { nodes?: T[] }>(
5757
enter: (node: T, ctx: VisitContext<T>) => EnterResult<T> | void = () => WalkAction.Continue,
5858
exit: (node: T, ctx: VisitContext<T>) => ExitResult<T> | void = () => WalkAction.Continue,
5959
) {
60-
let stack: [nodes: T[], offset: number, parent: Parent<T> | null][] = [[ast, 0, null]]
60+
let surrogate = { nodes: ast } as Parent<T>
61+
62+
// Reduce memory usage by tracking 2 different objects instead of a single
63+
// stack data structure. We could use 2 arrays, but objects are faster in Bun.
64+
// In Node.js the 2 arrays or 2 objects have similar performance.
65+
//
66+
// Used indexing to prevent `push()` / `pop()` overhead.
67+
let offsets: Record<number, number> = { 0: 0 }
68+
let parents: Record<number, Parent<T>> = { 0: surrogate }
69+
70+
let depth = 0
71+
6172
let ctx: VisitContext<T> = {
6273
parent: null,
6374
depth: 0,
6475
path() {
6576
let path: T[] = []
6677

67-
for (let i = 1; i < stack.length; i++) {
68-
let parent = stack[i][2]
69-
if (parent) path.push(parent)
78+
for (let i = 1; i <= depth; i++) {
79+
path.push(parents[i])
7080
}
7181

7282
return path
7383
},
7484
}
7585

76-
while (stack.length > 0) {
77-
let depth = stack.length - 1
78-
let frame = stack[depth]
79-
let nodes = frame[0]
80-
let offset = frame[1]
81-
let parent = frame[2]
86+
while (depth >= 0) {
87+
let offset = offsets[depth]
88+
let parent = parents[depth]
89+
let nodes = parent.nodes
8290

8391
// Done with this level
8492
if (offset >= nodes.length) {
85-
stack.pop()
93+
depth--
8694
continue
8795
}
8896

89-
ctx.parent = parent
97+
ctx.parent = depth === 0 ? null : parent
9098
ctx.depth = depth
9199

92100
// Enter phase (offsets are positive)
@@ -96,19 +104,21 @@ function walkImplementation<T extends { nodes?: T[] }>(
96104

97105
switch (result.kind) {
98106
case WalkKind.Continue: {
107+
offsets[depth] = ~offset // Prepare for exit phase, same offset
108+
99109
if (node.nodes && node.nodes.length > 0) {
100-
stack.push([node.nodes, 0, node as Parent<T>])
110+
depth++
111+
offsets[depth] = 0
112+
parents[depth] = node as Parent<T>
101113
}
102-
103-
frame[1] = ~offset // Prepare for exit phase, same offset
104114
continue
105115
}
106116

107117
case WalkKind.Stop:
108118
return // Stop immediately
109119

110120
case WalkKind.Skip: {
111-
frame[1] = ~offset // Prepare for exit phase, same offset
121+
offsets[depth] = ~offset // Prepare for exit phase, same offset
112122
continue
113123
}
114124

@@ -124,7 +134,7 @@ function walkImplementation<T extends { nodes?: T[] }>(
124134

125135
case WalkKind.ReplaceSkip: {
126136
nodes.splice(offset, 1, ...result.nodes)
127-
frame[1] += result.nodes.length // Advance to next sibling past replacements
137+
offsets[depth] += result.nodes.length // Advance to next sibling past replacements
128138
continue
129139
}
130140

@@ -146,15 +156,15 @@ function walkImplementation<T extends { nodes?: T[] }>(
146156

147157
switch (result.kind) {
148158
case WalkKind.Continue:
149-
frame[1] = index + 1 // Advance to next sibling
159+
offsets[depth] = index + 1 // Advance to next sibling
150160
continue
151161

152162
case WalkKind.Stop:
153163
return // Stop immediately
154164

155165
case WalkKind.Replace: {
156166
nodes.splice(index, 1, ...result.nodes)
157-
frame[1] = index + result.nodes.length // Advance to next sibling past replacements
167+
offsets[depth] = index + result.nodes.length // Advance to next sibling past replacements
158168
continue
159169
}
160170

@@ -165,7 +175,7 @@ function walkImplementation<T extends { nodes?: T[] }>(
165175

166176
case WalkKind.ReplaceSkip: {
167177
nodes.splice(index, 1, ...result.nodes)
168-
frame[1] = index + result.nodes.length // Advance to next sibling past replacements
178+
offsets[depth] = index + result.nodes.length // Advance to next sibling past replacements
169179
continue
170180
}
171181

0 commit comments

Comments
 (0)