Skip to content

Commit a2fa0db

Browse files
committed
wip(vapor): improve node traversal codegen
1 parent 528705f commit a2fa0db

File tree

12 files changed

+143
-41
lines changed

12 files changed

+143
-41
lines changed
Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

33
exports[`compiler: children transform > children & sibling references 1`] = `
4-
"import { next as _next, createTextNode as _createTextNode, insert as _insert, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
4+
"import { child as _child, nextn as _nextn, next as _next, createTextNode as _createTextNode, insert as _insert, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
55
const t0 = _template("<div><p></p> <!><p></p></div>", true)
66
77
export function render(_ctx) {
88
const n4 = t0()
9-
const n0 = n4.firstChild
10-
const n3 = _next(n0, 2)
11-
const n2 = n3.nextSibling
9+
const n0 = _child(n4)
10+
const n3 = _nextn(n0, 2)
11+
const n2 = _next(n3)
1212
const n1 = _createTextNode(() => [_ctx.second, " ", _ctx.third, " "])
1313
_insert(n1, n4, n3)
1414
_renderEffect(() => {
@@ -18,3 +18,26 @@ export function render(_ctx) {
1818
return n4
1919
}"
2020
`;
21+
22+
exports[`compiler: children transform > efficient traversal 1`] = `
23+
"import { child as _child, next as _next, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
24+
const t0 = _template("<div><div>x</div><div><span></span></div><div><span></span></div><div><span></span></div></div>", true)
25+
26+
export function render(_ctx) {
27+
const n3 = t0()
28+
const _n0 = _next(_child(n3))
29+
const n0 = _child(_n0)
30+
const _n1 = _next(_n0)
31+
const n1 = _child(_n1)
32+
const _n2 = _next(_n1)
33+
const n2 = _child(_n2)
34+
_renderEffect(() => {
35+
const _msg = _ctx.msg
36+
37+
_setText(n0, _msg)
38+
_setText(n1, _msg)
39+
_setText(n2, _msg)
40+
})
41+
return n3
42+
}"
43+
`;

packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function render(_ctx) {
7373
const n4 = t0()
7474
_renderEffect(() => _setText(n4, _for_item1.value+_for_item0.value))
7575
return n4
76-
})
76+
}, null, null, null, true)
7777
_insert(n2, n5)
7878
return n5
7979
})

packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ export function render(_ctx) {
1212
`;
1313

1414
exports[`compiler: v-once > basic 1`] = `
15-
"import { createTextNode as _createTextNode, setClass as _setClass, prepend as _prepend, template as _template } from 'vue';
15+
"import { child as _child, createTextNode as _createTextNode, setClass as _setClass, prepend as _prepend, template as _template } from 'vue';
1616
const t0 = _template("<div><span></span></div>", true)
1717
1818
export function render(_ctx, $props, $emit, $attrs, $slots) {
1919
const n2 = t0()
20-
const n1 = n2.firstChild
20+
const n1 = _child(n2)
2121
const n0 = _createTextNode([_ctx.msg, " "])
2222
_setClass(n1, _ctx.clz)
2323
_prepend(n2, n0)
@@ -49,12 +49,12 @@ export function render(_ctx) {
4949
`;
5050

5151
exports[`compiler: v-once > on nested plain element 1`] = `
52-
"import { setProp as _setProp, template as _template } from 'vue';
52+
"import { child as _child, setProp as _setProp, template as _template } from 'vue';
5353
const t0 = _template("<div><div></div></div>", true)
5454
5555
export function render(_ctx) {
5656
const n1 = t0()
57-
const n0 = n1.firstChild
57+
const n0 = _child(n1)
5858
_setProp(n0, "id", _ctx.foo)
5959
return n1
6060
}"

packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ const compileWithElementTransform = makeCompile({
1616
})
1717

1818
describe('compiler: children transform', () => {
19-
test.todo('basic')
20-
2119
test('children & sibling references', () => {
2220
const { code, helpers } = compileWithElementTransform(
2321
`<div>
@@ -36,4 +34,16 @@ describe('compiler: children transform', () => {
3634
'template',
3735
])
3836
})
37+
38+
test('efficient traversal', () => {
39+
const { code } = compileWithElementTransform(
40+
`<div>
41+
<div>x</div>
42+
<div><span>{{ msg }}</span></div>
43+
<div><span>{{ msg }}</span></div>
44+
<div><span>{{ msg }}</span></div>
45+
</div>`,
46+
)
47+
expect(code).toMatchSnapshot()
48+
})
3949
})

packages/compiler-vapor/src/generators/block.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function genBlockContent(
4949
}
5050

5151
for (const child of dynamic.children) {
52-
push(...genChildren(child, context, child.id!))
52+
push(...genChildren(child, context, `n${child.id!}`))
5353
}
5454

5555
push(...genOperations(operation, context))

packages/compiler-vapor/src/generators/template.ts

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export function genTemplates(
2121
export function genChildren(
2222
dynamic: IRDynamicInfo,
2323
context: CodegenContext,
24-
from: number,
25-
paths: number[] = [],
24+
from: string,
25+
path: number[] = [],
26+
knownPaths: [id: string, path: number[]][] = [],
2627
): CodeFragment[] {
2728
const { helper } = context
2829
const [frag, push] = buildCodeFragment()
@@ -34,7 +35,7 @@ export function genChildren(
3435
push(...genDirectivesForElement(id, context))
3536
}
3637

37-
let prev: [id: number, elementIndex: number] | undefined
38+
let prev: [variable: string, elementIndex: number] | undefined
3839
for (const [index, child] of children.entries()) {
3940
if (child.flags & DynamicFlag.NON_TEMPLATE) {
4041
offset--
@@ -47,34 +48,81 @@ export function genChildren(
4748
: child.id
4849
: undefined
4950

50-
const elementIndex = Number(index) + offset
51-
const newPaths = [...paths, elementIndex]
52-
53-
if (id === undefined) {
54-
push(...genChildren(child, context, from, newPaths))
51+
if (id === undefined && !child.hasDynamicChild) {
52+
const { id, template } = child
53+
if (id !== undefined && template !== undefined) {
54+
push(NEWLINE, `const n${id} = t${template}()`)
55+
push(...genDirectivesForElement(id, context))
56+
}
5557
continue
5658
}
5759

58-
push(NEWLINE, `const n${id} = `)
60+
const elementIndex = Number(index) + offset
61+
const newPath = [...path, elementIndex]
62+
63+
const variable = id === undefined ? `_n${context.block.tempId++}` : `n${id}`
64+
push(NEWLINE, `const ${variable} = `)
65+
5966
if (prev) {
6067
const offset = elementIndex - prev[1]
6168
if (offset === 1) {
62-
push(`n${prev[0]}.nextSibling`)
69+
push(...genCall(helper('next'), prev[0]))
6370
} else {
64-
push(...genCall(helper('next'), `n${prev[0]}`, String(offset)))
71+
push(...genCall(helper('nextn'), prev[0], String(offset)))
6572
}
6673
} else {
67-
if (newPaths.length === 1 && newPaths[0] === 0) {
68-
push(`n${from}.firstChild`)
74+
if (newPath.length === 1 && newPath[0] === 0) {
75+
push(...genCall(helper('child'), from))
6976
} else {
70-
push(
71-
...genCall(helper('children'), `n${from}`, ...newPaths.map(String)),
72-
)
77+
// check if there's a node that we can reuse from
78+
let resolvedFrom = from
79+
let resolvedPath = newPath
80+
let skipFirstChild = false
81+
outer: for (const [from, path] of knownPaths) {
82+
const l = path.length
83+
const tail = newPath.slice(l)
84+
for (let i = 0; i < l; i++) {
85+
const parentSeg = path[i]
86+
const thisSeg = newPath[i]
87+
if (parentSeg !== thisSeg) {
88+
if (i === l - 1) {
89+
// last bit is reusable
90+
resolvedFrom = from
91+
resolvedPath = [thisSeg - parentSeg, ...tail]
92+
skipFirstChild = true
93+
break outer
94+
}
95+
break
96+
} else if (i === l - 1) {
97+
// full overlap
98+
resolvedFrom = from
99+
resolvedPath = tail
100+
break outer
101+
}
102+
}
103+
}
104+
let init
105+
for (const i of resolvedPath) {
106+
init = init
107+
? genCall(helper('child'), init)
108+
: skipFirstChild
109+
? resolvedFrom
110+
: genCall(helper('child'), resolvedFrom)
111+
if (i === 1) {
112+
init = genCall(helper('next'), init)
113+
} else if (i > 1) {
114+
init = genCall(helper('nextn'), init, String(i))
115+
}
116+
}
117+
push(...init!)
73118
}
74119
}
75-
push(...genDirectivesForElement(id, context))
76-
prev = [id, elementIndex]
77-
push(...genChildren(child, context, id, []))
120+
if (id !== undefined) {
121+
push(...genDirectivesForElement(id, context))
122+
}
123+
knownPaths.unshift([variable, newPath])
124+
prev = [variable, elementIndex]
125+
push(...genChildren(child, context, variable))
78126
}
79127

80128
return frag

packages/compiler-vapor/src/ir/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface BlockIRNode extends BaseIRNode {
4848
type: IRNodeTypes.BLOCK
4949
node: RootNode | TemplateChildNode
5050
dynamic: IRDynamicInfo
51+
tempId: number
5152
effect: IREffect[]
5253
operation: OperationNode[]
5354
expressions: SimpleExpressionNode[]
@@ -249,6 +250,7 @@ export interface IRDynamicInfo {
249250
anchor?: number
250251
children: IRDynamicInfo[]
251252
template?: number
253+
hasDynamicChild?: boolean
252254
}
253255

254256
export interface IREffect {

packages/compiler-vapor/src/transforms/transformChildren.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,32 @@ export const transformChildren: NodeTransform = (node, context) => {
1919
const childContext = context.create(child, i)
2020
transformNode(childContext)
2121

22+
const childDynamic = childContext.dynamic
23+
2224
if (isFragment) {
2325
childContext.reference()
2426
childContext.registerTemplate()
2527

2628
if (
27-
!(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
28-
childContext.dynamic.flags & DynamicFlag.INSERT
29+
!(childDynamic.flags & DynamicFlag.NON_TEMPLATE) ||
30+
childDynamic.flags & DynamicFlag.INSERT
2931
) {
3032
context.block.returns.push(childContext.dynamic.id!)
3133
}
3234
} else {
3335
context.childrenTemplate.push(childContext.template)
3436
}
3537

36-
context.dynamic.children[i] = childContext.dynamic
38+
if (
39+
childDynamic.hasDynamicChild ||
40+
childDynamic.id !== undefined ||
41+
childDynamic.flags & DynamicFlag.NON_TEMPLATE ||
42+
childDynamic.flags & DynamicFlag.INSERT
43+
) {
44+
context.dynamic.hasDynamicChild = true
45+
}
46+
47+
context.dynamic.children[i] = childDynamic
3748
}
3849

3950
if (!isFragment) {

packages/compiler-vapor/src/transforms/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({
3030
operation: [],
3131
returns: [],
3232
expressions: [],
33+
tempId: 0,
3334
})
3435

3536
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {

packages/runtime-vapor/__tests__/dom/template.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { children, next, template } from '../../src/dom/template'
1+
import { children, next, nextn, template } from '../../src/dom/template'
22

33
describe('api: template', () => {
44
test('create element', () => {
@@ -32,11 +32,11 @@ describe('api: template', () => {
3232
const t = template('<div><span></span><b></b><p></p></div>')
3333
const root = t()
3434
const span = children(root, 0)
35-
const b = next(span, 1)
35+
const b = next(span)
3636

3737
expect(span).toBe(root.childNodes[0])
3838
expect(b).toBe(root.childNodes[1])
39-
expect(next(span, 2)).toBe(root.childNodes[2])
40-
expect(next(b, 1)).toBe(root.childNodes[2])
39+
expect(nextn(span, 2)).toBe(root.childNodes[2])
40+
expect(next(b)).toBe(root.childNodes[2])
4141
})
4242
})

0 commit comments

Comments
 (0)