continue
}
- const elementIndex = Number(index) + offset
+ const elementIndex = index + offset
// p for "placeholder" variables that are meant for possible reuse by
// other access paths
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
}
} else {
- // offset is used to determine the child during hydration.
+ // child index is used to find the child during hydration.
// if offset is not 0, we need to specify the offset to skip the dynamic
// children and get the correct child.
- let childOffset = offset === 0 ? undefined : `${Math.abs(offset)}`
+ const asAnchor = children.some(child => child.anchor === id)
+ let childIndex =
+ offset === 0
+ ? undefined
+ : // if the current node is used as insertionAnchor, subtract 1 here
+ // this ensures that insertionAnchor points to the current node itself
+ // rather than its next sibling, since insertionAnchor is used as the
+ // hydration node
+ `${asAnchor ? index - 1 : index}`
+
if (elementIndex === 0) {
- pushBlock(...genCall(helper('child'), from, childOffset))
+ pushBlock(...genCall(helper('child'), from, childIndex))
} else {
// check if there's a node that we can reuse from
- let init = genCall(helper('child'), from, childOffset)
+ let init = genCall(helper('child'), from, childIndex)
if (elementIndex === 1) {
init = genCall(helper('next'), init)
} else if (elementIndex > 1) {
function processDynamicChildren(context: TransformContext<ElementNode>) {
let prevDynamics: IRDynamicInfo[] = []
let staticCount = 0
+ let prependCount = 0
const children = context.dynamic.children
for (const [index, child] of children.entries()) {
}
}
} else {
+ prependCount += prevDynamics.length
registerInsertion(prevDynamics, context, -1 /* prepend */)
}
prevDynamics = []
}
if (prevDynamics.length) {
- registerInsertion(prevDynamics, context, undefined)
- context.dynamic.dynamicChildOffset = staticCount
+ registerInsertion(prevDynamics, context)
+ context.dynamic.dynamicChildOffset = staticCount + prependCount
}
}
expect(container.innerHTML).toBe(`<div>foo</div><!--if--><!--if-->`)
})
+ test('mixed prepend and insertion anchor', async () => {
+ const data = reactive({
+ show: true,
+ foo: 'foo',
+ bar: 'bar',
+ qux: 'qux',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child/>
+ </template>`,
+ {
+ Child: `<template>
+ <span v-if="data.show">
+ <span v-if="data.show">{{data.foo}}</span>
+ <span v-if="data.show">{{data.bar}}</span>
+ <span>baz</span>
+ <span v-if="data.show">{{data.qux}}</span>
+ <span>quux</span>
+ </span>
+ </template>`,
+ },
+ data,
+ )
+ expect(container.innerHTML).toBe(
+ `<span>` +
+ `<span>foo</span><!--if-->` +
+ `<span>bar</span><!--if-->` +
+ `<span>baz</span>` +
+ `<span>qux</span><!--if-->` +
+ `<span>quux</span>` +
+ `</span><!--if-->`,
+ )
+
+ data.qux = 'qux1'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<span>` +
+ `<span>foo</span><!--if-->` +
+ `<span>bar</span><!--if-->` +
+ `<span>baz</span>` +
+ `<span>qux1</span><!--if-->` +
+ `<span>quux</span>` +
+ `</span><!--if-->`,
+ )
+
+ data.foo = 'foo1'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<span>` +
+ `<span>foo1</span><!--if-->` +
+ `<span>bar</span><!--if-->` +
+ `<span>baz</span>` +
+ `<span>qux1</span><!--if-->` +
+ `<span>quux</span>` +
+ `</span><!--if-->`,
+ )
+ })
+
test('v-if/else-if/else chain on component - switch branches', async () => {
const data = ref('a')
const { container } = await testHydration(
resetInsertionState,
} from './insertionState'
import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared'
-import { isHydrating } from './dom/hydration'
+import { advanceHydrationNode, isHydrating } from './dom/hydration'
import { DynamicFragment, type VaporFragment } from './fragment'
export function createDynamicComponent(
if (!isHydrating && _insertionParent) {
insert(frag, _insertionParent, _insertionAnchor)
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
return frag
}
import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import {
+ advanceHydrationNode,
currentHydrationNode,
isHydrating,
locateHydrationNode,
if (!isHydrating && _insertionParent) {
insert(frag, _insertionParent, _insertionAnchor)
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
return frag
import { IF_ANCHOR_LABEL } from '@vue/shared'
import { type Block, type BlockFn, insert } from './block'
-import { isHydrating } from './dom/hydration'
+import { advanceHydrationNode, isHydrating } from './dom/hydration'
import {
insertionAnchor,
insertionParent,
insert(frag, _insertionParent, _insertionAnchor)
}
+ // if _insertionAnchor is defined, insertionParent contains a static node
+ // that should be skipped during hydration.
+ // Advance to the next sibling node to bypass this static node.
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
+
return frag
}
import { createElement } from './dom/node'
import {
adoptTemplate,
+ advanceHydrationNode,
currentHydrationNode,
isHydrating,
locateHydrationNode,
if (_insertionParent) {
insert(frag, _insertionParent, _insertionAnchor)
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
return frag
}
frag.hydrate()
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
+
return frag as any
}
insert(el, _insertionParent, _insertionAnchor)
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
+
return el
}
insertionParent,
resetInsertionState,
} from './insertionState'
-import { isHydrating } from './dom/hydration'
+import { advanceHydrationNode, isHydrating } from './dom/hydration'
import { DynamicFragment } from './fragment'
export type RawSlots = Record<string, VaporSlot> & {
insert(fragment, _insertionParent, _insertionAnchor)
}
+ if (isHydrating && _insertionAnchor !== undefined) {
+ advanceHydrationNode(_insertionParent!)
+ }
+
return fragment
}
import {
__next,
__nthChild,
- _nthChild,
disableHydrationNodeLookup,
enableHydrationNodeLookup,
} from './node'
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
node =
childToHydrateMap.get(insertionParent) ||
- _nthChild(insertionParent, insertionParent.$dp || 0)
+ __nthChild(insertionParent, insertionParent.$dp || 0)
}
// locate slot fragment start anchor