]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: hydrate v-if
authordaiwei <daiwei521@126.com>
Thu, 24 Apr 2025 03:52:39 +0000 (11:52 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 24 Apr 2025 03:52:39 +0000 (11:52 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/apiCreateIf.ts

index ecb8d8202a4127471edfdfc8ad6e58423d91005a..b02f50177a40b722aaad9dc2273b8ecc87b2c136 100644 (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')
 
index 71bfa32d5d3fdbe07213ff6af3125da2c5f8dd25..6210e9221b18d3fe7c8f9b36f0c385f01d0e0372 100644 (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
 }