_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
- _push(\`<!--]-->\`)
+ _push(\`<!--for--><!--]-->\`)
}"
`)
})
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
- _push(\`</ul>\`)
+ _push(\`<!--for--></ul>\`)
}"
`)
})
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
+ _push(\`<!--for-->\`)
if (false) {
_push(\`<div></div>\`)
_push(\`<!--if-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
- _push(\`</ul>\`)
+ _push(\`<!--for--></ul>\`)
}"
`)
})
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
- _push(\`</\${_ctx.someTag}>\`)
+ _push(\`<!--for--></\${_ctx.someTag}>\`)
}"
`)
})
_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-->\`)
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 =
)
if (!disableNestedFragments) {
context.pushStringPart(`<!--]-->`)
+ } else {
+ // add anchor for non-fragment v-for
+ context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
}
}
})
})
+ 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)
}
})
- 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')
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,
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
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
disableHydrationNodeLookup,
enableHydrationNodeLookup,
next,
- prev,
} from './node'
import { isDynamicFragmentEndAnchor } from '@vue/shared'
// 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
}
}
/*! #__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)) {
anchorLabel: string,
): Comment | null {
node = handleWrappedNode(node)
+ if (isComment(node, anchorLabel)) {
+ return node as Comment
+ }
let n = node.nextSibling
while (n) {