From 38d4889de75da36e1d501ff702193829c81fa8f4 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 24 Apr 2025 11:52:39 +0800 Subject: [PATCH] wip: hydrate v-if --- .../runtime-vapor/__tests__/hydration.spec.ts | 99 ++++++++++++++++++- packages/runtime-vapor/src/apiCreateIf.ts | 27 ++++- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index ecb8d8202..b02f50177 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -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 = {}, + 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( + ``, + undefined, + data, + ) + expect(container.innerHTML).toMatchInlineSnapshot( + `"
foo
"`, + ) + + data.value = false + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot(`""`) + }) + + test('basic toggle - false -> true', async () => { + const data = ref(false) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toMatchInlineSnapshot(`""`) + + data.value = true + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot( + `"
foo
"`, + ) + }) + + test('v-if/else-if/else chain - switch branches', async () => { + const data = ref('a') + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toMatchInlineSnapshot( + `"
foo
"`, + ) + + data.value = 'b' + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot( + `"
bar
"`, + ) + + data.value = 'c' + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot( + `"
baz
"`, + ) + }) + + test('nested if', async () => { + const data = reactive({ outer: true, inner: true }) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toMatchInlineSnapshot( + `"
outer
inner
"`, + ) + + data.inner = false + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot( + `"
outer
"`, + ) + + data.outer = false + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot(`""`) + }) + + test.todo('on component', async () => {}) + }) test.todo('for') diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index 71bfa32d5..6210e9221 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -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 }