Skip to content

Commit 6e5fc73

Browse files
committed
make cloneAstNode faster
Turns out that if you keep the exact same structure instead of a spread, it's even faster! ``` cloneAstNode() - src/ast.bench.ts > Cloning AST nodes 1.15x faster than cloneAstNode (with spread) 33.54x faster than structuredClone() ```
1 parent 1a9abfe commit 6e5fc73

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

packages/tailwindcss/src/ast.bench.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { bench, describe } from 'vitest'
2-
import { cloneAstNode, toCss } from './ast'
2+
import { cloneAstNode, toCss, type AstNode } from './ast'
33
import * as CSS from './css-parser'
44

55
const css = String.raw
@@ -34,7 +34,31 @@ describe('Cloning AST nodes', () => {
3434
ast.map(cloneAstNode)
3535
})
3636

37+
bench('cloneAstNode (with spread)', () => {
38+
ast.map(cloneAstNodeSpread)
39+
})
40+
3741
bench('structuredClone()', () => {
3842
structuredClone(ast)
3943
})
4044
})
45+
46+
function cloneAstNodeSpread<T extends AstNode>(node: T): T {
47+
switch (node.kind) {
48+
case 'rule':
49+
case 'at-rule':
50+
case 'at-root':
51+
return { ...node, nodes: node.nodes.map(cloneAstNodeSpread) }
52+
53+
case 'context':
54+
return { ...node, context: { ...node.context }, nodes: node.nodes.map(cloneAstNodeSpread) }
55+
56+
case 'declaration':
57+
case 'comment':
58+
return { ...node }
59+
60+
default:
61+
node satisfies never
62+
throw new Error(`Unknown node kind: ${(node as any).kind}`)
63+
}
64+
}

packages/tailwindcss/src/ast.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,58 @@ export function atRoot(nodes: AstNode[]): AtRoot {
125125
export function cloneAstNode<T extends AstNode>(node: T): T {
126126
switch (node.kind) {
127127
case 'rule':
128+
return {
129+
kind: node.kind,
130+
selector: node.selector,
131+
nodes: node.nodes.map(cloneAstNode),
132+
src: node.src,
133+
dst: node.dst,
134+
} satisfies StyleRule as T
135+
128136
case 'at-rule':
137+
return {
138+
kind: node.kind,
139+
name: node.name,
140+
params: node.params,
141+
nodes: node.nodes.map(cloneAstNode),
142+
src: node.src,
143+
dst: node.dst,
144+
} satisfies AtRule as T
145+
129146
case 'at-root':
130-
return { ...node, nodes: node.nodes.map(cloneAstNode) }
147+
return {
148+
kind: node.kind,
149+
nodes: node.nodes.map(cloneAstNode),
150+
src: node.src,
151+
dst: node.dst,
152+
} satisfies AtRoot as T
131153

132154
case 'context':
133-
return { ...node, context: { ...node.context }, nodes: node.nodes.map(cloneAstNode) }
155+
return {
156+
kind: node.kind,
157+
context: { ...node.context },
158+
nodes: node.nodes.map(cloneAstNode),
159+
src: node.src,
160+
dst: node.dst,
161+
} satisfies Context as T
134162

135163
case 'declaration':
164+
return {
165+
kind: node.kind,
166+
property: node.property,
167+
value: node.value,
168+
important: node.important,
169+
src: node.src,
170+
dst: node.dst,
171+
} satisfies Declaration as T
172+
136173
case 'comment':
137-
return { ...node }
174+
return {
175+
kind: node.kind,
176+
value: node.value,
177+
src: node.src,
178+
dst: node.dst,
179+
} satisfies Comment as T
138180

139181
default:
140182
node satisfies never

0 commit comments

Comments
 (0)