wip: hydrate v-if

This commit is contained in:
daiwei 2025-04-24 11:52:39 +08:00
parent b5762b57ae
commit 38d4889de7
2 changed files with 122 additions and 4 deletions

View File

@ -1,5 +1,5 @@
import { createVaporSSRApp, delegateEvents } from '../src'
import { nextTick, ref } from '@vue/runtime-dom'
import { nextTick, reactive, ref } from '@vue/runtime-dom'
import { compileScript, parse } from '@vue/compiler-sfc'
import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
@ -50,8 +50,8 @@ function compile(
async function testHydration(
code: string,
components: Record<string, string> = {},
data: any = ref('foo'),
) {
const data = ref('foo')
const ssrComponents: any = {}
const clientComponents: any = {}
for (const key in components) {
@ -638,7 +638,100 @@ describe('Vapor Mode hydration', () => {
)
})
test.todo('if')
describe('if', () => {
test('basic toggle - true -> false', async () => {
const data = ref(true)
const { container } = await testHydration(
`<template>
<div v-if="data">foo</div>
</template>`,
undefined,
data,
)
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div>foo</div><!--if-->"`,
)
data.value = false
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(`"<!--if-->"`)
})
test('basic toggle - false -> true', async () => {
const data = ref(false)
const { container } = await testHydration(
`<template>
<div v-if="data">foo</div>
</template>`,
undefined,
data,
)
expect(container.innerHTML).toMatchInlineSnapshot(`"<!--if-->"`)
data.value = true
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div>foo</div><!--if-->"`,
)
})
test('v-if/else-if/else chain - switch branches', async () => {
const data = ref('a')
const { container } = await testHydration(
`<template>
<div v-if="data === 'a'">foo</div>
<div v-else-if="data === 'b'">bar</div>
<div v-else>baz</div>
</template>`,
undefined,
data,
)
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div>foo</div><!--if-->"`,
)
data.value = 'b'
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div>bar</div><!--if--><!--if-->"`,
)
data.value = 'c'
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div>baz</div><!--if--><!--if-->"`,
)
})
test('nested if', async () => {
const data = reactive({ outer: true, inner: true })
const { container } = await testHydration(
`<template>
<div v-if="data.outer">
<span>outer</span>
<div v-if="data.inner">inner</div>
</div>
</template>`,
undefined,
data,
)
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div><span>outer</span><div>inner</div><!--if--></div><!--if-->"`,
)
data.inner = false
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(
`"<div><span>outer</span><!--if--></div><!--if-->"`,
)
data.outer = false
await nextTick()
expect(container.innerHTML).toMatchInlineSnapshot(`"<!--if-->"`)
})
test.todo('on component', async () => {})
})
test.todo('for')

View File

@ -1,5 +1,10 @@
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
import { isHydrating, locateHydrationNode } from './dom/hydration'
import {
currentHydrationNode,
isComment,
isHydrating,
locateHydrationNode,
} from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState'
import { renderEffect } from './renderEffect'
@ -11,8 +16,10 @@ export function createIf(
): Block {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
let _currentHydrationNode
if (isHydrating) {
locateHydrationNode()
_currentHydrationNode = currentHydrationNode
}
let frag: Block
@ -27,5 +34,23 @@ export function createIf(
insert(frag, _insertionParent, _insertionAnchor)
}
// if the current hydration node is a comment, use it as an anchor
// otherwise need to insert the anchor node
// OR adjust ssr output to add anchor for v-if
else if (isHydrating && _currentHydrationNode) {
const parentNode = _currentHydrationNode.parentNode
if (parentNode) {
if (isComment(_currentHydrationNode, '')) {
if (__DEV__) _currentHydrationNode.data = 'if'
;(frag as DynamicFragment).anchor = _currentHydrationNode
} else {
parentNode.insertBefore(
(frag as DynamicFragment).anchor,
_currentHydrationNode.nextSibling,
)
}
}
}
return frag
}