mirror of https://github.com/vuejs/core.git
wip: v-for hydration
This commit is contained in:
parent
aad75fd7c4
commit
e6e016016f
|
@ -15,7 +15,7 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--for--><!--]-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</ul>\`)
|
||||
_push(\`<!--for--></ul>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -52,6 +52,7 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--for-->\`)
|
||||
if (false) {
|
||||
_push(\`<div></div>\`)
|
||||
_push(\`<!--if-->\`)
|
||||
|
@ -75,7 +76,7 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</ul>\`)
|
||||
_push(\`<!--for--></ul>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -97,7 +98,7 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</\${_ctx.someTag}>\`)
|
||||
_push(\`<!--for--></\${_ctx.someTag}>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -119,9 +120,11 @@ describe('transition-group', () => {
|
|||
_ssrRenderList(10, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--for-->\`)
|
||||
_ssrRenderList(10, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--for-->\`)
|
||||
if (_ctx.ok) {
|
||||
_push(\`<div>ok</div>\`)
|
||||
_push(\`<!--if-->\`)
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
processChildrenAsStatement,
|
||||
} from '../ssrCodegenTransform'
|
||||
import { SSR_RENDER_LIST } from '../runtimeHelpers'
|
||||
import { FOR_ANCHOR_LABEL } from '@vue/shared'
|
||||
|
||||
// Plugin for the first transform pass, which simply constructs the AST node
|
||||
export const ssrTransformFor: NodeTransform =
|
||||
|
@ -48,5 +49,8 @@ export function ssrProcessFor(
|
|||
)
|
||||
if (!disableNestedFragments) {
|
||||
context.pushStringPart(`<!--]-->`)
|
||||
} else {
|
||||
// add anchor for non-fragment v-for
|
||||
context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1123,6 +1123,73 @@ describe('Vapor Mode hydration', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('on fragment component', async () => {
|
||||
runWithEnv(isProd, async () => {
|
||||
const data = ref(true)
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<components.Child v-if="data"/>
|
||||
</div>
|
||||
</template>`,
|
||||
{
|
||||
Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[--><div>true</div>-true-<!--]-->` +
|
||||
`<!--if-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` + `<!--[--><!--]-->` + `<!--${anchorLabel}-->` + `</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('on fragment component with anchor insertion', async () => {
|
||||
runWithEnv(isProd, async () => {
|
||||
const data = ref(true)
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<span/>
|
||||
<components.Child v-if="data"/>
|
||||
<span/>
|
||||
</div>
|
||||
</template>`,
|
||||
{
|
||||
Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[--><div>true</div>-true-<!--]-->` +
|
||||
`<!--if-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[--><!--]-->` +
|
||||
`<!--${anchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('consecutive v-if on fragment component with anchor insertion', async () => {
|
||||
runWithEnv(isProd, async () => {
|
||||
const data = ref(true)
|
||||
|
@ -1311,7 +1378,168 @@ describe('Vapor Mode hydration', () => {
|
|||
}
|
||||
})
|
||||
|
||||
test.todo('for')
|
||||
describe('for', () => {
|
||||
test('basic v-for', async () => {
|
||||
const { container, data } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<span v-for="item in data" :key="item">{{ item }}</span>
|
||||
</div>
|
||||
</template>`,
|
||||
undefined,
|
||||
ref(['a', 'b', 'c']),
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('v-for with text node', async () => {
|
||||
const { container, data } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<span v-for="item in data" :key="item">{{ item }}</span>
|
||||
</div>
|
||||
</template>`,
|
||||
undefined,
|
||||
ref(['a', 'b', 'c']),
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div><!--[--><span>a</span><span>b</span><span>c</span><!--]--></div>`,
|
||||
)
|
||||
|
||||
data.value.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div><!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]--></div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('v-for with anchor insertion', async () => {
|
||||
const { container, data } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<span/>
|
||||
<span v-for="item in data" :key="item">{{ item }}</span>
|
||||
<span/>
|
||||
</div>
|
||||
</template>`,
|
||||
undefined,
|
||||
ref(['a', 'b', 'c']),
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('consecutive v-for with anchor insertion', async () => {
|
||||
const { container, data } = await testHydration(
|
||||
`<template>
|
||||
<div>
|
||||
<span/>
|
||||
<span v-for="item in data" :key="item">{{ item }}</span>
|
||||
<span v-for="item in data" :key="item">{{ item }}</span>
|
||||
<span/>
|
||||
</div>
|
||||
</template>`,
|
||||
undefined,
|
||||
ref(['a', 'b', 'c']),
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--[[-->` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]]-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--[[-->` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]]-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
// TODO wait for slots hydration support
|
||||
test.todo('v-for on component', async () => {})
|
||||
|
||||
// TODO wait for slots hydration support
|
||||
test.todo('on fragment component', async () => {})
|
||||
|
||||
// TODO wait for vapor TransitionGroup support
|
||||
// v-for inside TransitionGroup does not render as a fragment
|
||||
test.todo('v-for in TransitionGroup', async () => {})
|
||||
})
|
||||
|
||||
test.todo('slots')
|
||||
|
||||
|
|
|
@ -9,8 +9,14 @@ import {
|
|||
shallowRef,
|
||||
toReactive,
|
||||
} from '@vue/reactivity'
|
||||
import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
||||
import { createComment, createTextNode } from './dom/node'
|
||||
import {
|
||||
FOR_ANCHOR_LABEL,
|
||||
getSequence,
|
||||
isArray,
|
||||
isObject,
|
||||
isString,
|
||||
} from '@vue/shared'
|
||||
import { createComment, createTextNode, nextSiblingAnchor } from './dom/node'
|
||||
import {
|
||||
type Block,
|
||||
VaporFragment,
|
||||
|
@ -22,8 +28,17 @@ import { currentInstance, isVaporComponent } from './component'
|
|||
import type { DynamicSlot } from './componentSlots'
|
||||
import { renderEffect } from './renderEffect'
|
||||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||
import { insertionAnchor, insertionParent } from './insertionState'
|
||||
import {
|
||||
currentHydrationNode,
|
||||
isComment,
|
||||
isHydrating,
|
||||
locateHydrationNode,
|
||||
} from './dom/hydration'
|
||||
import {
|
||||
insertionAnchor,
|
||||
insertionParent,
|
||||
resetInsertionState,
|
||||
} from './insertionState'
|
||||
|
||||
class ForBlock extends VaporFragment {
|
||||
scope: EffectScope | undefined
|
||||
|
@ -71,15 +86,24 @@ export const createFor = (
|
|||
const _insertionParent = insertionParent
|
||||
const _insertionAnchor = insertionAnchor
|
||||
if (isHydrating) {
|
||||
locateHydrationNode()
|
||||
locateHydrationNode(true)
|
||||
} else {
|
||||
resetInsertionState()
|
||||
}
|
||||
|
||||
let isMounted = false
|
||||
let oldBlocks: ForBlock[] = []
|
||||
let newBlocks: ForBlock[]
|
||||
let parent: ParentNode | undefined | null
|
||||
// TODO handle this in hydration
|
||||
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
||||
const parentAnchor = isHydrating
|
||||
? // Use fragment end anchor if available, otherwise use the specific for anchor.
|
||||
nextSiblingAnchor(
|
||||
currentHydrationNode!,
|
||||
isComment(currentHydrationNode!, '[') ? ']' : FOR_ANCHOR_LABEL,
|
||||
)!
|
||||
: __DEV__
|
||||
? createComment('for')
|
||||
: createTextNode()
|
||||
const frag = new VaporFragment(oldBlocks)
|
||||
const instance = currentInstance!
|
||||
const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
disableHydrationNodeLookup,
|
||||
enableHydrationNodeLookup,
|
||||
next,
|
||||
prev,
|
||||
} from './node'
|
||||
import { isDynamicFragmentEndAnchor } from '@vue/shared'
|
||||
|
||||
|
@ -98,7 +97,7 @@ function locateHydrationNodeImpl(isFragment?: boolean) {
|
|||
// if the last child is a comment, it is the anchor for the fragment
|
||||
// so it need to find the previous node
|
||||
if (isFragment && node && isDynamicFragmentEndAnchor(node)) {
|
||||
let previous = prev(node)
|
||||
let previous = node.previousSibling //prev(node)
|
||||
if (previous) node = previous
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ export function disableHydrationNodeLookup(): void {
|
|||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
// TODO check if this is still needed
|
||||
export function prev(node: Node): Node | null {
|
||||
// process dynamic node (<!--[[-->...<!--]]-->) as a single one
|
||||
if (isComment(node, DYNAMIC_END_ANCHOR_LABEL)) {
|
||||
|
@ -145,6 +146,9 @@ export function nextSiblingAnchor(
|
|||
anchorLabel: string,
|
||||
): Comment | null {
|
||||
node = handleWrappedNode(node)
|
||||
if (isComment(node, anchorLabel)) {
|
||||
return node as Comment
|
||||
}
|
||||
|
||||
let n = node.nextSibling
|
||||
while (n) {
|
||||
|
|
Loading…
Reference in New Issue