Skip to content

Commit 530e144

Browse files
committed
wip: hydration for slots
1 parent ca34d4a commit 530e144

File tree

5 files changed

+170
-15
lines changed

5 files changed

+170
-15
lines changed

packages/runtime-vapor/__tests__/hydration.spec.ts

+140-4
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,20 @@ describe('Vapor Mode hydration', () => {
15311531
`<span></span>` +
15321532
`</div>`,
15331533
)
1534+
1535+
data.value.splice(0, 1)
1536+
await nextTick()
1537+
expect(container.innerHTML).toBe(
1538+
`<div>` +
1539+
`<span></span>` +
1540+
`<!--[-->` +
1541+
`<span>b</span>` +
1542+
`<span>c</span>` +
1543+
`<span>d</span>` +
1544+
`<!--]-->` +
1545+
`<span></span>` +
1546+
`</div>`,
1547+
)
15341548
})
15351549

15361550
test('consecutive v-for with anchor insertion', async () => {
@@ -1583,13 +1597,135 @@ describe('Vapor Mode hydration', () => {
15831597
`<span></span>` +
15841598
`</div>`,
15851599
)
1600+
1601+
data.value.splice(0, 2)
1602+
await nextTick()
1603+
expect(container.innerHTML).toBe(
1604+
`<div>` +
1605+
`<span></span>` +
1606+
`<!--[-->` +
1607+
`<span>c</span>` +
1608+
`<span>d</span>` +
1609+
`<!--]-->` +
1610+
`<!--[-->` +
1611+
`<span>c</span>` +
1612+
`<span>d</span>` +
1613+
`<!--]-->` +
1614+
`<span></span>` +
1615+
`</div>`,
1616+
)
15861617
})
15871618

1588-
// TODO wait for slots hydration support
1589-
test.todo('v-for on component', async () => {})
1619+
test('v-for on component', async () => {
1620+
const { container, data } = await testHydration(
1621+
`<template>
1622+
<div>
1623+
<components.Child v-for="item in data" :key="item"/>
1624+
</div>
1625+
</template>`,
1626+
{
1627+
Child: `<template><div>comp</div></template>`,
1628+
},
1629+
ref(['a', 'b', 'c']),
1630+
)
15901631

1591-
// TODO wait for slots hydration support
1592-
test.todo('on fragment component', async () => {})
1632+
expect(container.innerHTML).toBe(
1633+
`<div>` +
1634+
`<!--[-->` +
1635+
`<div>comp</div>` +
1636+
`<div>comp</div>` +
1637+
`<div>comp</div>` +
1638+
`<!--]-->` +
1639+
`</div>`,
1640+
)
1641+
1642+
data.value.push('d')
1643+
await nextTick()
1644+
expect(container.innerHTML).toBe(
1645+
`<div>` +
1646+
`<!--[-->` +
1647+
`<div>comp</div>` +
1648+
`<div>comp</div>` +
1649+
`<div>comp</div>` +
1650+
`<div>comp</div>` +
1651+
`<!--]-->` +
1652+
`</div>`,
1653+
)
1654+
})
1655+
1656+
test('v-for on component with slots', async () => {
1657+
const { container, data } = await testHydration(
1658+
`<template>
1659+
<div>
1660+
<components.Child v-for="item in data" :key="item">
1661+
<span>{{ item }}</span>
1662+
</components.Child>
1663+
</div>
1664+
</template>`,
1665+
{
1666+
Child: `<template><slot/></template>`,
1667+
},
1668+
ref(['a', 'b', 'c']),
1669+
)
1670+
expect(container.innerHTML).toBe(
1671+
`<div>` +
1672+
`<!--[-->` +
1673+
`<!--[--><span>a</span><!--]--><!--slot-->` +
1674+
`<!--[--><span>b</span><!--]--><!--slot-->` +
1675+
`<!--[--><span>c</span><!--]--><!--slot-->` +
1676+
`<!--]-->` +
1677+
`</div>`,
1678+
)
1679+
1680+
data.value.push('d')
1681+
await nextTick()
1682+
expect(container.innerHTML).toBe(
1683+
`<div>` +
1684+
`<!--[-->` +
1685+
`<!--[--><span>a</span><!--]--><!--slot-->` +
1686+
`<!--[--><span>b</span><!--]--><!--slot-->` +
1687+
`<!--[--><span>c</span><!--]--><!--slot-->` +
1688+
`<span>d</span><!--slot-->` +
1689+
`<!--]-->` +
1690+
`</div>`,
1691+
)
1692+
})
1693+
1694+
test('on fragment component', async () => {
1695+
const { container, data } = await testHydration(
1696+
`<template>
1697+
<div>
1698+
<components.Child v-for="item in data" :key="item"/>
1699+
</div>
1700+
</template>`,
1701+
{
1702+
Child: `<template><div>foo</div>-bar-</template>`,
1703+
},
1704+
ref(['a', 'b', 'c']),
1705+
)
1706+
expect(container.innerHTML).toBe(
1707+
`<div>` +
1708+
`<!--[-->` +
1709+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1710+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1711+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1712+
`<!--]-->` +
1713+
`</div>`,
1714+
)
1715+
1716+
data.value.push('d')
1717+
await nextTick()
1718+
expect(container.innerHTML).toBe(
1719+
`<div>` +
1720+
`<!--[-->` +
1721+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1722+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1723+
`<!--[--><div>foo</div>-bar-<!--]-->` +
1724+
`<div>foo</div>-bar-` +
1725+
`<!--]-->` +
1726+
`</div>`,
1727+
)
1728+
})
15931729

15941730
// TODO wait for vapor TransitionGroup support
15951731
// v-for inside TransitionGroup does not render as a fragment

packages/runtime-vapor/src/block.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isArray, isVaporFragmentEndAnchor } from '@vue/shared'
1+
import { isArray } from '@vue/shared'
22
import {
33
type VaporComponentInstance,
44
isVaporComponent,
@@ -100,10 +100,10 @@ export class DynamicFragment extends VaporFragment {
100100
} else {
101101
// find next sibling dynamic fragment end anchor
102102
const anchor = nextVaporFragmentAnchor(currentHydrationNode!, label)!
103-
if (anchor && isVaporFragmentEndAnchor(anchor)) {
103+
if (anchor) {
104104
this.anchor = anchor
105105
} else if (__DEV__) {
106-
// TODO warning
106+
// TODO warning, should not happen
107107
warn(`DynamicFragment anchor not found...`)
108108
}
109109
}

packages/runtime-vapor/src/componentSlots.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
1+
import {
2+
EMPTY_OBJ,
3+
NO,
4+
SLOT_ANCHOR_LABEL,
5+
hasOwn,
6+
isArray,
7+
isFunction,
8+
} from '@vue/shared'
29
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
310
import { rawPropsProxyHandlers } from './componentProps'
411
import { currentInstance, isRef } from '@vue/runtime-dom'
512
import type { LooseRawProps, VaporComponentInstance } from './component'
613
import { renderEffect } from './renderEffect'
7-
import { insertionAnchor, insertionParent } from './insertionState'
14+
import {
15+
insertionAnchor,
16+
insertionParent,
17+
resetInsertionState,
18+
} from './insertionState'
819
import { isHydrating, locateHydrationNode } from './dom/hydration'
920

1021
export type RawSlots = Record<string, VaporSlot> & {
@@ -96,6 +107,8 @@ export function createSlot(
96107
const _insertionAnchor = insertionAnchor
97108
if (isHydrating) {
98109
locateHydrationNode()
110+
} else {
111+
resetInsertionState
99112
}
100113

101114
const instance = currentInstance as VaporComponentInstance
@@ -115,7 +128,7 @@ export function createSlot(
115128
fallback,
116129
)
117130
} else {
118-
fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
131+
fragment = new DynamicFragment(SLOT_ANCHOR_LABEL)
119132
const isDynamicName = isFunction(name)
120133
const renderSlot = () => {
121134
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)

packages/runtime-vapor/src/dom/hydration.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,16 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
9494
} else {
9595
node = insertionParent ? insertionParent.lastChild : currentHydrationNode
9696

97+
// if current node is fragment start anchor, find the next one
98+
if (node && isComment(node, '[')) {
99+
node = node.nextSibling
100+
}
97101
// if the last child is a vapor fragment end anchor, find the previous one
98-
if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
99-
let previous = node.previousSibling
100-
if (previous) node = previous
102+
else if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
103+
node = node.previousSibling
104+
if (__DEV__ && !node) {
105+
// TODO warning, should not happen
106+
}
101107
}
102108

103109
if (node && isComment(node, ']')) {

packages/server-renderer/src/helpers/ssrRenderSlot.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type SSRBufferItem,
66
renderVNodeChildren,
77
} from '../render'
8-
import { isArray } from '@vue/shared'
8+
import { SLOT_ANCHOR_LABEL, isArray } from '@vue/shared'
99

1010
const { ensureValidVNode } = ssrUtils
1111

@@ -37,7 +37,7 @@ export function ssrRenderSlot(
3737
parentComponent,
3838
slotScopeId,
3939
)
40-
push(`<!--]-->`)
40+
push(`<!--]--><!--${SLOT_ANCHOR_LABEL}-->`)
4141
}
4242

4343
export function ssrRenderSlotInner(

0 commit comments

Comments
 (0)