mirror of https://github.com/vuejs/core.git
wip: handle mixed prepend and insertionAnchor
This commit is contained in:
parent
6606cbdb4b
commit
a9b911e254
|
@ -69,7 +69,7 @@ export function genChildren(
|
|||
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}`
|
||||
|
@ -82,15 +82,24 @@ export function genChildren(
|
|||
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) {
|
||||
|
|
|
@ -60,6 +60,7 @@ export const transformChildren: NodeTransform = (node, context) => {
|
|||
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()) {
|
||||
|
@ -88,6 +89,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
prependCount += prevDynamics.length
|
||||
registerInsertion(prevDynamics, context, -1 /* prepend */)
|
||||
}
|
||||
prevDynamics = []
|
||||
|
@ -97,8 +99,8 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
|||
}
|
||||
|
||||
if (prevDynamics.length) {
|
||||
registerInsertion(prevDynamics, context, undefined)
|
||||
context.dynamic.dynamicChildOffset = staticCount
|
||||
registerInsertion(prevDynamics, context)
|
||||
context.dynamic.dynamicChildOffset = staticCount + prependCount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1285,6 +1285,65 @@ describe('Vapor Mode hydration', () => {
|
|||
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(
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
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(
|
||||
|
@ -52,5 +52,8 @@ export function createDynamicComponent(
|
|||
if (!isHydrating && _insertionParent) {
|
||||
insert(frag, _insertionParent, _insertionAnchor)
|
||||
}
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
return frag
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import type { DynamicSlot } from './componentSlots'
|
|||
import { renderEffect } from './renderEffect'
|
||||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||
import {
|
||||
advanceHydrationNode,
|
||||
currentHydrationNode,
|
||||
isHydrating,
|
||||
locateHydrationNode,
|
||||
|
@ -468,6 +469,9 @@ export const createFor = (
|
|||
if (!isHydrating && _insertionParent) {
|
||||
insert(frag, _insertionParent, _insertionAnchor)
|
||||
}
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
|
||||
return frag
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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,
|
||||
|
@ -78,5 +78,12 @@ export function createIf(
|
|||
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
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ import { hmrReload, hmrRerender } from './hmr'
|
|||
import { createElement } from './dom/node'
|
||||
import {
|
||||
adoptTemplate,
|
||||
advanceHydrationNode,
|
||||
currentHydrationNode,
|
||||
isHydrating,
|
||||
locateHydrationNode,
|
||||
|
@ -182,6 +183,9 @@ export function createComponent(
|
|||
if (_insertionParent) {
|
||||
insert(frag, _insertionParent, _insertionAnchor)
|
||||
}
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
return frag
|
||||
}
|
||||
|
||||
|
@ -194,6 +198,10 @@ export function createComponent(
|
|||
frag.hydrate()
|
||||
}
|
||||
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
|
||||
return frag as any
|
||||
}
|
||||
|
||||
|
@ -587,6 +595,10 @@ export function createComponentWithFallback(
|
|||
insert(el, _insertionParent, _insertionAnchor)
|
||||
}
|
||||
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
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> & {
|
||||
|
@ -175,6 +175,10 @@ export function createSlot(
|
|||
insert(fragment, _insertionParent, _insertionAnchor)
|
||||
}
|
||||
|
||||
if (isHydrating && _insertionAnchor !== undefined) {
|
||||
advanceHydrationNode(_insertionParent!)
|
||||
}
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
import {
|
||||
__next,
|
||||
__nthChild,
|
||||
_nthChild,
|
||||
disableHydrationNodeLookup,
|
||||
enableHydrationNodeLookup,
|
||||
} from './node'
|
||||
|
@ -137,7 +136,7 @@ function locateHydrationNodeImpl(isFragment?: boolean): void {
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue