]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: update
authordaiwei <daiwei521@126.com>
Mon, 28 Apr 2025 07:32:23 +0000 (15:32 +0800)
committerdaiwei <daiwei521@126.com>
Mon, 28 Apr 2025 08:59:24 +0000 (16:59 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/apiCreateDynamicComponent.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/apiCreateIf.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/dom/hydration.ts
packages/runtime-vapor/src/dom/node.ts

index b0878e13919d439c0a4305cf653ce25cc7bc323c..879257e4d1206a2cd3b81b4a844e4c4b6747f5d0 100644 (file)
@@ -1963,8 +1963,56 @@ describe('Vapor Mode hydration', () => {
         data,
       )
 
-      expect(container.innerHTML).toMatchInlineSnapshot(
-        `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->hi</div>"`,
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `hi` +
+          `</div>`,
+      )
+
+      data.msg = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `bar` +
+          `</div>`,
+      )
+    })
+
+    test('mixed root slot and text node', async () => {
+      const data = reactive({
+        text: 'foo',
+        msg: 'hi',
+      })
+      const { container } = await testHydration(
+        `<template>
+          <components.Child>
+            <span>{{data.text}}</span>
+          </components.Child>
+        </template>`,
+        {
+          Child: `<template>{{data.text}}<slot/>{{data.msg}}</template>`,
+        },
+        data,
+      )
+
+      expect(container.innerHTML).toBe(
+        `<!--[-->` +
+          `foo` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `hi` +
+          `<!--]-->`,
+      )
+
+      data.msg = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<!--[-->` +
+          `foo` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `bar` +
+          `<!--]-->`,
       )
     })
 
@@ -1985,8 +2033,20 @@ describe('Vapor Mode hydration', () => {
         data,
       )
 
-      expect(container.innerHTML).toMatchInlineSnapshot(
-        `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}--><div>hi</div></div>"`,
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `<div>hi</div>` +
+          `</div>`,
+      )
+
+      data.msg = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+          `<div>bar</div>` +
+          `</div>`,
       )
     })
 
@@ -2024,6 +2084,7 @@ describe('Vapor Mode hydration', () => {
           `<div>bar</div>` +
           `</div>`,
       )
+
       data.msg2 = 'hello'
       await nextTick()
       expect(container.innerHTML).toBe(
index e272f0d225a26230eb168239417a476a8644edef..7ae689a0aee74ff920ccd699b58bcaa27639032b 100644 (file)
@@ -22,7 +22,10 @@ export function createDynamicComponent(
   const _insertionAnchor = insertionAnchor
   if (!isHydrating) resetInsertionState()
 
-  const frag = new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
+  const frag =
+    isHydrating || __DEV__
+      ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
+      : new DynamicFragment()
   renderEffect(() => {
     const value = getter()
     frag.update(
index 704b01973d8d357210e171f2df0e7870f9957bb6..06a2bf752ef4fe80e976538fb8169e3e87250000 100644 (file)
@@ -16,11 +16,7 @@ import {
   isObject,
   isString,
 } from '@vue/shared'
-import {
-  createComment,
-  createTextNode,
-  findVaporFragmentAnchor,
-} from './dom/node'
+import { createComment, createTextNode } from './dom/node'
 import {
   type Block,
   VaporFragment,
@@ -34,6 +30,7 @@ import { renderEffect } from './renderEffect'
 import { VaporVForFlags } from '../../shared/src/vaporFlags'
 import {
   currentHydrationNode,
+  findVaporFragmentAnchor,
   isHydrating,
   locateHydrationNode,
 } from './dom/hydration'
index bec4a3504df51fddba144dbb7490dd7024cb12f9..3e370592b32d118e36736ea7ecee0ec77beaf263 100644 (file)
@@ -22,7 +22,10 @@ export function createIf(
   if (once) {
     frag = condition() ? b1() : b2 ? b2() : []
   } else {
-    frag = new DynamicFragment(IF_ANCHOR_LABEL)
+    frag =
+      isHydrating || __DEV__
+        ? new DynamicFragment(IF_ANCHOR_LABEL)
+        : new DynamicFragment()
     renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
   }
 
index 987949646266fa9530200578c978bfdf400d8198..bf77b9b094afc9086122fe3d004c54376ef765eb 100644 (file)
@@ -5,14 +5,11 @@ import {
   mountComponent,
   unmountComponent,
 } from './component'
-import {
-  createComment,
-  createTextNode,
-  findVaporFragmentAnchor,
-} from './dom/node'
+import { createComment, createTextNode } from './dom/node'
 import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
 import {
   currentHydrationNode,
+  findVaporFragmentAnchor,
   isComment,
   isHydrating,
   locateHydrationNode,
@@ -92,13 +89,11 @@ export class DynamicFragment extends VaporFragment {
   }
 
   hydrate(label: string): void {
-    // for v-if="false" the hydrationNode will be a empty comment node
-    // use it as anchor.
-    // otherwise, use the next sibling comment node as anchor
+    // for `v-if="false"` the node will be an empty comment node use it as the anchor.
+    // otherwise, find next sibling vapor fragment anchor
     if (isComment(currentHydrationNode!, '')) {
       this.anchor = currentHydrationNode
     } else {
-      // find next sibling dynamic fragment end anchor
       const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
       if (anchor) {
         this.anchor = anchor
index 093ed7fb082168a6a2e0e416350482bb05667221..2b2de60729cf2f3bcf9a83d56a99b597401ea345 100644 (file)
@@ -124,7 +124,10 @@ export function createSlot(
       fallback,
     )
   } else {
-    fragment = new DynamicFragment(SLOT_ANCHOR_LABEL)
+    fragment =
+      isHydrating || __DEV__
+        ? new DynamicFragment(SLOT_ANCHOR_LABEL)
+        : new DynamicFragment()
     const isDynamicName = isFunction(name)
     const renderSlot = () => {
       const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
index 9b2e0efa749337c41833352f15c4a4bff86c5b54..d82aa33f984ea5758e0aa324454bac0838408c66 100644 (file)
@@ -11,7 +11,7 @@ import {
   enableHydrationNodeLookup,
   next,
 } from './node'
-import { isVaporFragmentEndAnchor } from '@vue/shared'
+import { isDynamicAnchor, isVaporFragmentEndAnchor } from '@vue/shared'
 
 export let isHydrating = false
 export let currentHydrationNode: Node | null = null
@@ -191,3 +191,29 @@ export function locateEndAnchor(
   }
   return null
 }
+
+export function isNonHydrationNode(node: Node): boolean {
+  return (
+    // empty text nodes
+    isEmptyText(node) ||
+    // dynamic node anchors (<!--[[-->, <!--]]-->)
+    isDynamicAnchor(node) ||
+    // fragment end anchor (`<!--]-->`)
+    isComment(node, ']') ||
+    // vapor fragment end anchors
+    isVaporFragmentEndAnchor(node)
+  )
+}
+
+export function findVaporFragmentAnchor(
+  node: Node,
+  anchorLabel: string,
+): Comment | null {
+  let n = node.nextSibling
+  while (n) {
+    if (isComment(n, anchorLabel)) return n
+    n = n.nextSibling
+  }
+
+  return null
+}
index b1922944537ccd7bee586f59b80bac624f8d68ed..2384697ed0341c52911abf119a2f99e01d9e2030 100644 (file)
@@ -1,8 +1,7 @@
-import { isComment, isEmptyText, locateEndAnchor } from './hydration'
+import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
 import {
   DYNAMIC_END_ANCHOR_LABEL,
   DYNAMIC_START_ANCHOR_LABEL,
-  isDynamicAnchor,
   isVaporFragmentEndAnchor,
 } from '@vue/shared'
 
@@ -42,7 +41,7 @@ export function __child(node: ParentNode): Node {
    * _setInsertionState(n2, 0) -> slot fragment
    *
    * during hydration:
-   * const n2 = _template("<div><!--[-->slot<!--]--><!--slot-->Hi</div>")()
+   * const n2 = <div><!--[-->slot<!--]--><!--slot-->Hi</div> // server output
    * const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
    * _setInsertionState(n2, 0) -> slot fragment
    */
@@ -79,7 +78,19 @@ function _next(node: Node): Node {
 
 /*! #__NO_SIDE_EFFECTS__ */
 export function __next(node: Node): Node {
-  node = handleWrappedNode(node)
+  // process dynamic node (<!--[[-->...<!--]]-->) as a single node
+  if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) {
+    node = locateEndAnchor(
+      node,
+      DYNAMIC_START_ANCHOR_LABEL,
+      DYNAMIC_END_ANCHOR_LABEL,
+    )!
+  }
+
+  // process fragment (<!--[-->...<!--]-->) as a single node
+  else if (isComment(node, '[')) {
+    node = locateEndAnchor(node)!
+  }
 
   let n = node.nextSibling!
   while (n && isNonHydrationNode(n)) {
@@ -142,47 +153,3 @@ export function disableHydrationNodeLookup(): void {
   next.impl = _next
   nthChild.impl = _nthChild
 }
-
-function isNonHydrationNode(node: Node) {
-  return (
-    // empty text nodes, no need to hydrate
-    isEmptyText(node) ||
-    // dynamic node anchors (<!--[[-->, <!--]]-->)
-    isDynamicAnchor(node) ||
-    // fragment end anchor (`<!--]-->`)
-    isComment(node, ']') ||
-    // vapor fragment end anchors
-    isVaporFragmentEndAnchor(node)
-  )
-}
-
-export function findVaporFragmentAnchor(
-  node: Node,
-  anchorLabel: string,
-): Comment | null {
-  let n = node.nextSibling
-  while (n) {
-    if (isComment(n, anchorLabel)) return n
-    n = n.nextSibling
-  }
-
-  return null
-}
-
-function handleWrappedNode(node: Node): Node {
-  // process dynamic node (<!--[[-->...<!--]]-->) as a single one
-  if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) {
-    return locateEndAnchor(
-      node,
-      DYNAMIC_START_ANCHOR_LABEL,
-      DYNAMIC_END_ANCHOR_LABEL,
-    )!
-  }
-
-  // process fragment (<!--[-->...<!--]-->) as a single one
-  else if (isComment(node, '[')) {
-    return locateEndAnchor(node)!
-  }
-
-  return node
-}