// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: children transform > children & sibling references 1`] = `
-"import { next as _next, createTextNode as _createTextNode, insert as _insert, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
+"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';
const t0 = _template("<div><p></p> <!><p></p></div>", true)
export function render(_ctx) {
const n4 = t0()
- const n0 = n4.firstChild
- const n3 = _next(n0, 2)
- const n2 = n3.nextSibling
+ const n0 = _child(n4)
+ const n3 = _nextn(n0, 2)
+ const n2 = _next(n3)
const n1 = _createTextNode(() => [_ctx.second, " ", _ctx.third, " "])
_insert(n1, n4, n3)
_renderEffect(() => {
return n4
}"
`;
+
+exports[`compiler: children transform > efficient traversal 1`] = `
+"import { child as _child, next as _next, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div><div>x</div><div><span></span></div><div><span></span></div><div><span></span></div></div>", true)
+
+export function render(_ctx) {
+ const n3 = t0()
+ const _n0 = _next(_child(n3))
+ const n0 = _child(_n0)
+ const _n1 = _next(_n0)
+ const n1 = _child(_n1)
+ const _n2 = _next(_n1)
+ const n2 = _child(_n2)
+ _renderEffect(() => {
+ const _msg = _ctx.msg
+
+ _setText(n0, _msg)
+ _setText(n1, _msg)
+ _setText(n2, _msg)
+ })
+ return n3
+}"
+`;
const n4 = t0()
_renderEffect(() => _setText(n4, _for_item1.value+_for_item0.value))
return n4
- })
+ }, null, null, null, true)
_insert(n2, n5)
return n5
})
`;
exports[`compiler: v-once > basic 1`] = `
-"import { createTextNode as _createTextNode, setClass as _setClass, prepend as _prepend, template as _template } from 'vue';
+"import { child as _child, createTextNode as _createTextNode, setClass as _setClass, prepend as _prepend, template as _template } from 'vue';
const t0 = _template("<div><span></span></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n2 = t0()
- const n1 = n2.firstChild
+ const n1 = _child(n2)
const n0 = _createTextNode([_ctx.msg, " "])
_setClass(n1, _ctx.clz)
_prepend(n2, n0)
`;
exports[`compiler: v-once > on nested plain element 1`] = `
-"import { setProp as _setProp, template as _template } from 'vue';
+"import { child as _child, setProp as _setProp, template as _template } from 'vue';
const t0 = _template("<div><div></div></div>", true)
export function render(_ctx) {
const n1 = t0()
- const n0 = n1.firstChild
+ const n0 = _child(n1)
_setProp(n0, "id", _ctx.foo)
return n1
}"
})
describe('compiler: children transform', () => {
- test.todo('basic')
-
test('children & sibling references', () => {
const { code, helpers } = compileWithElementTransform(
`<div>
'template',
])
})
+
+ test('efficient traversal', () => {
+ const { code } = compileWithElementTransform(
+ `<div>
+ <div>x</div>
+ <div><span>{{ msg }}</span></div>
+ <div><span>{{ msg }}</span></div>
+ <div><span>{{ msg }}</span></div>
+ </div>`,
+ )
+ expect(code).toMatchSnapshot()
+ })
})
}
for (const child of dynamic.children) {
- push(...genChildren(child, context, child.id!))
+ push(...genChildren(child, context, `n${child.id!}`))
}
push(...genOperations(operation, context))
export function genChildren(
dynamic: IRDynamicInfo,
context: CodegenContext,
- from: number,
- paths: number[] = [],
+ from: string,
+ path: number[] = [],
+ knownPaths: [id: string, path: number[]][] = [],
): CodeFragment[] {
const { helper } = context
const [frag, push] = buildCodeFragment()
push(...genDirectivesForElement(id, context))
}
- let prev: [id: number, elementIndex: number] | undefined
+ let prev: [variable: string, elementIndex: number] | undefined
for (const [index, child] of children.entries()) {
if (child.flags & DynamicFlag.NON_TEMPLATE) {
offset--
: child.id
: undefined
- const elementIndex = Number(index) + offset
- const newPaths = [...paths, elementIndex]
-
- if (id === undefined) {
- push(...genChildren(child, context, from, newPaths))
+ if (id === undefined && !child.hasDynamicChild) {
+ const { id, template } = child
+ if (id !== undefined && template !== undefined) {
+ push(NEWLINE, `const n${id} = t${template}()`)
+ push(...genDirectivesForElement(id, context))
+ }
continue
}
- push(NEWLINE, `const n${id} = `)
+ const elementIndex = Number(index) + offset
+ const newPath = [...path, elementIndex]
+
+ const variable = id === undefined ? `_n${context.block.tempId++}` : `n${id}`
+ push(NEWLINE, `const ${variable} = `)
+
if (prev) {
const offset = elementIndex - prev[1]
if (offset === 1) {
- push(`n${prev[0]}.nextSibling`)
+ push(...genCall(helper('next'), prev[0]))
} else {
- push(...genCall(helper('next'), `n${prev[0]}`, String(offset)))
+ push(...genCall(helper('nextn'), prev[0], String(offset)))
}
} else {
- if (newPaths.length === 1 && newPaths[0] === 0) {
- push(`n${from}.firstChild`)
+ if (newPath.length === 1 && newPath[0] === 0) {
+ push(...genCall(helper('child'), from))
} else {
- push(
- ...genCall(helper('children'), `n${from}`, ...newPaths.map(String)),
- )
+ // check if there's a node that we can reuse from
+ let resolvedFrom = from
+ let resolvedPath = newPath
+ let skipFirstChild = false
+ outer: for (const [from, path] of knownPaths) {
+ const l = path.length
+ const tail = newPath.slice(l)
+ for (let i = 0; i < l; i++) {
+ const parentSeg = path[i]
+ const thisSeg = newPath[i]
+ if (parentSeg !== thisSeg) {
+ if (i === l - 1) {
+ // last bit is reusable
+ resolvedFrom = from
+ resolvedPath = [thisSeg - parentSeg, ...tail]
+ skipFirstChild = true
+ break outer
+ }
+ break
+ } else if (i === l - 1) {
+ // full overlap
+ resolvedFrom = from
+ resolvedPath = tail
+ break outer
+ }
+ }
+ }
+ let init
+ for (const i of resolvedPath) {
+ init = init
+ ? genCall(helper('child'), init)
+ : skipFirstChild
+ ? resolvedFrom
+ : genCall(helper('child'), resolvedFrom)
+ if (i === 1) {
+ init = genCall(helper('next'), init)
+ } else if (i > 1) {
+ init = genCall(helper('nextn'), init, String(i))
+ }
+ }
+ push(...init!)
}
}
- push(...genDirectivesForElement(id, context))
- prev = [id, elementIndex]
- push(...genChildren(child, context, id, []))
+ if (id !== undefined) {
+ push(...genDirectivesForElement(id, context))
+ }
+ knownPaths.unshift([variable, newPath])
+ prev = [variable, elementIndex]
+ push(...genChildren(child, context, variable))
}
return frag
type: IRNodeTypes.BLOCK
node: RootNode | TemplateChildNode
dynamic: IRDynamicInfo
+ tempId: number
effect: IREffect[]
operation: OperationNode[]
expressions: SimpleExpressionNode[]
anchor?: number
children: IRDynamicInfo[]
template?: number
+ hasDynamicChild?: boolean
}
export interface IREffect {
const childContext = context.create(child, i)
transformNode(childContext)
+ const childDynamic = childContext.dynamic
+
if (isFragment) {
childContext.reference()
childContext.registerTemplate()
if (
- !(childContext.dynamic.flags & DynamicFlag.NON_TEMPLATE) ||
- childContext.dynamic.flags & DynamicFlag.INSERT
+ !(childDynamic.flags & DynamicFlag.NON_TEMPLATE) ||
+ childDynamic.flags & DynamicFlag.INSERT
) {
context.block.returns.push(childContext.dynamic.id!)
}
context.childrenTemplate.push(childContext.template)
}
- context.dynamic.children[i] = childContext.dynamic
+ if (
+ childDynamic.hasDynamicChild ||
+ childDynamic.id !== undefined ||
+ childDynamic.flags & DynamicFlag.NON_TEMPLATE ||
+ childDynamic.flags & DynamicFlag.INSERT
+ ) {
+ context.dynamic.hasDynamicChild = true
+ }
+
+ context.dynamic.children[i] = childDynamic
}
if (!isFragment) {
operation: [],
returns: [],
expressions: [],
+ tempId: 0,
})
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {
-import { children, next, template } from '../../src/dom/template'
+import { children, next, nextn, template } from '../../src/dom/template'
describe('api: template', () => {
test('create element', () => {
const t = template('<div><span></span><b></b><p></p></div>')
const root = t()
const span = children(root, 0)
- const b = next(span, 1)
+ const b = next(span)
expect(span).toBe(root.childNodes[0])
expect(b).toBe(root.childNodes[1])
- expect(next(span, 2)).toBe(root.childNodes[2])
- expect(next(b, 1)).toBe(root.childNodes[2])
+ expect(nextn(span, 2)).toBe(root.childNodes[2])
+ expect(next(b)).toBe(root.childNodes[2])
})
})
return node
}
-/*! #__NO_SIDE_EFFECTS__ */
-export function next(node: Node, offset: number): Node {
+export function child(node: ParentNode): Node {
+ return node.firstChild!
+}
+
+export function next(node: Node): Node {
+ return node.nextSibling!
+}
+
+export function nextn(node: Node, offset: number = 1): Node {
for (let i = 0; i < offset; i++) {
node = node.nextSibling!
}
export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots'
-export { template, children, next } from './dom/template'
+export { template, children, child, next, nextn } from './dom/template'
export { createTextNode } from './dom/node'
export {
setText,