Skip to content

Commit f54311d

Browse files
committed
wip: hydration
1 parent bba71be commit f54311d

File tree

12 files changed

+295
-114
lines changed

12 files changed

+295
-114
lines changed

packages/compiler-vapor/src/generators/template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export function genSelf(
2424
context: CodegenContext,
2525
): CodeFragment[] {
2626
const [frag, push] = buildCodeFragment()
27-
const { id, template, operation } = dynamic
27+
const { id, template, operation, dynamicChildOffset } = dynamic
2828

2929
if (id !== undefined && template !== undefined) {
30-
push(NEWLINE, `const n${id} = t${template}()`)
30+
push(NEWLINE, `const n${id} = t${template}(${dynamicChildOffset || ''})`)
3131
push(...genDirectivesForElement(id, context))
3232
}
3333

packages/compiler-vapor/src/ir/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ export interface IRDynamicInfo {
266266
children: IRDynamicInfo[]
267267
template?: number
268268
hasDynamicChild?: boolean
269+
dynamicChildOffset?: number
269270
operation?: OperationNode
270271
needsKey?: boolean
271272
}

packages/compiler-vapor/src/transforms/transformChildren.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const transformChildren: NodeTransform = (node, context) => {
5959

6060
function processDynamicChildren(context: TransformContext<ElementNode>) {
6161
let prevDynamics: IRDynamicInfo[] = []
62-
let hasStaticTemplate = false
62+
let staticCount = 0
6363
const children = context.dynamic.children
6464

6565
for (const [index, child] of children.entries()) {
@@ -69,7 +69,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
6969

7070
if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
7171
if (prevDynamics.length) {
72-
if (hasStaticTemplate) {
72+
if (staticCount) {
7373
// each dynamic child gets its own placeholder node.
7474
// this makes it easier to locate the corresponding node during hydration.
7575
for (let i = 0; i < prevDynamics.length; i++) {
@@ -92,12 +92,13 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
9292
}
9393
prevDynamics = []
9494
}
95-
hasStaticTemplate = true
95+
staticCount++
9696
}
9797
}
9898

9999
if (prevDynamics.length) {
100-
registerInsertion(prevDynamics, context)
100+
registerInsertion(prevDynamics, context, undefined)
101+
context.dynamic.dynamicChildOffset = staticCount
101102
}
102103
}
103104

packages/runtime-core/src/apiCreateApp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export interface VaporInteropInterface {
191191
transition: TransitionHooks,
192192
): void
193193
hydrate(node: Node, fn: () => void): void
194+
hydrateSlot(vnode: VNode, container: any): void
194195

195196
vdomMount: (
196197
component: ConcreteComponent,

packages/runtime-core/src/hydration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Comment as VComment,
66
type VNode,
77
type VNodeHook,
8+
VaporSlot,
89
createTextVNode,
910
createVNode,
1011
invokeVNodeHook,
@@ -276,6 +277,12 @@ export function createHydrationFunctions(
276277
)
277278
}
278279
break
280+
case VaporSlot:
281+
getVaporInterface(parentComponent, vnode).hydrateSlot(
282+
vnode,
283+
parentNode(node)!,
284+
)
285+
break
279286
default:
280287
if (shapeFlag & ShapeFlags.ELEMENT) {
281288
if (

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

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,34 @@ describe('Vapor Mode hydration', () => {
476476
)
477477
})
478478

479+
test('consecutive components with insertion parent', async () => {
480+
const data = reactive({ foo: 'foo', bar: 'bar' })
481+
const { container } = await testHydration(
482+
`<template>
483+
<div>
484+
<components.Child1/>
485+
<components.Child2/>
486+
</div>
487+
</template>
488+
`,
489+
{
490+
Child1: `<template><span>{{ data.foo }}</span></template>`,
491+
Child2: `<template><span>{{ data.bar }}</span></template>`,
492+
},
493+
data,
494+
)
495+
expect(container.innerHTML).toBe(
496+
`<div><span>foo</span><span>bar</span></div>`,
497+
)
498+
499+
data.foo = 'foo1'
500+
data.bar = 'bar1'
501+
await nextTick()
502+
expect(container.innerHTML).toBe(
503+
`<div><span>foo1</span><span>bar1</span></div>`,
504+
)
505+
})
506+
479507
test('nested consecutive components with anchor insertion', async () => {
480508
const { container, data } = await testHydration(
481509
`
@@ -1314,6 +1342,38 @@ describe('Vapor Mode hydration', () => {
13141342
)
13151343
})
13161344

1345+
test('consecutive component with insertion parent', async () => {
1346+
const data = reactive({
1347+
show: true,
1348+
foo: 'foo',
1349+
bar: 'bar',
1350+
})
1351+
const { container } = await testHydration(
1352+
`<template>
1353+
<div v-if="data.show">
1354+
<components.Child/>
1355+
<components.Child2/>
1356+
</div>
1357+
</template>`,
1358+
{
1359+
Child: `<template><span>{{data.foo}}</span></template>`,
1360+
Child2: `<template><span>{{data.bar}}</span></template>`,
1361+
},
1362+
data,
1363+
)
1364+
expect(container.innerHTML).toBe(
1365+
`<div>` +
1366+
`<span>foo</span>` +
1367+
`<span>bar</span>` +
1368+
`</div>` +
1369+
`<!--${anchorLabel}-->`,
1370+
)
1371+
1372+
data.show = false
1373+
await nextTick()
1374+
expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
1375+
})
1376+
13171377
test('consecutive v-if on component with anchor insertion', async () => {
13181378
const data = ref(true)
13191379
const { container } = await testHydration(
@@ -2354,6 +2414,31 @@ describe('Vapor Mode hydration', () => {
23542414
`</div>`,
23552415
)
23562416
})
2417+
2418+
test('slot fallback', async () => {
2419+
const data = reactive({
2420+
foo: 'foo',
2421+
})
2422+
const { container } = await testHydration(
2423+
`<template>
2424+
<components.Child>
2425+
</components.Child>
2426+
</template>`,
2427+
{
2428+
Child: `<template><slot><span>{{data.foo}}</span></slot></template>`,
2429+
},
2430+
data,
2431+
)
2432+
expect(container.innerHTML).toBe(
2433+
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
2434+
)
2435+
2436+
data.foo = 'bar'
2437+
await nextTick()
2438+
expect(container.innerHTML).toBe(
2439+
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
2440+
)
2441+
})
23572442
})
23582443

23592444
describe.todo('transition', async () => {
@@ -3912,6 +3997,76 @@ describe('VDOM hydration interop', () => {
39123997
expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
39133998
})
39143999

4000+
test('nested components (VDOM -> Vapor -> VDOM (with slot fallback))', async () => {
4001+
const data = ref(true)
4002+
const { container } = await testHydrationInterop(
4003+
`<script setup>const data = _data; const components = _components;</script>
4004+
<template>
4005+
<components.VaporChild/>
4006+
</template>`,
4007+
{
4008+
VaporChild: {
4009+
code: `<template><components.VdomChild/></template>`,
4010+
vapor: true,
4011+
},
4012+
VdomChild: {
4013+
code: `<script setup>const data = _data;</script>
4014+
<template><slot><span>{{data}}</span></slot></template>`,
4015+
vapor: false,
4016+
},
4017+
},
4018+
data,
4019+
)
4020+
4021+
expect(container.innerHTML).toMatchInlineSnapshot(
4022+
`"<!--[--><span>true</span><!--]--><!--slot-->"`,
4023+
)
4024+
4025+
data.value = false
4026+
await nextTick()
4027+
expect(container.innerHTML).toMatchInlineSnapshot(
4028+
`"<!--[--><span>false</span><!--]--><!--slot-->"`,
4029+
)
4030+
})
4031+
4032+
test('nested components (VDOM -> Vapor(with slot fallback) -> VDOM)', async () => {
4033+
const data = ref(true)
4034+
const { container } = await testHydrationInterop(
4035+
`<script setup>const data = _data; const components = _components;</script>
4036+
<template>
4037+
<components.VaporChild/>
4038+
</template>`,
4039+
{
4040+
VaporChild: {
4041+
code: `<template>
4042+
<components.VdomChild>
4043+
<template #default>
4044+
<span>{{data}} vapor fallback</span>
4045+
</template>
4046+
</components.VdomChild>
4047+
</template>`,
4048+
vapor: true,
4049+
},
4050+
VdomChild: {
4051+
code: `<script setup>const data = _data;</script>
4052+
<template><slot><span>vdom fallback</span></slot></template>`,
4053+
vapor: false,
4054+
},
4055+
},
4056+
data,
4057+
)
4058+
4059+
expect(container.innerHTML).toMatchInlineSnapshot(
4060+
`"<!--[--><span>true vapor fallback</span><!--]--><!--slot-->"`,
4061+
)
4062+
4063+
data.value = false
4064+
await nextTick()
4065+
expect(container.innerHTML).toMatchInlineSnapshot(
4066+
`"<!--[--><span>false vapor fallback</span><!--]--><!--slot-->"`,
4067+
)
4068+
})
4069+
39154070
test('vapor slot render vdom component', async () => {
39164071
const data = ref(true)
39174072
const { container } = await testHydrationInterop(

packages/runtime-vapor/src/apiCreateFor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export const createFor = (
8686
const _insertionParent = insertionParent
8787
const _insertionAnchor = insertionAnchor
8888
if (isHydrating) {
89-
locateHydrationNode(true)
89+
locateHydrationNode()
9090
} else {
9191
resetInsertionState()
9292
}

0 commit comments

Comments
 (0)