Procházet zdrojové kódy

fix(runtime-vapor): skip teleport ranges for logical hydration siblings (#14832)

edison před 4 týdny
rodič
revize
ec58870be4

+ 25 - 0
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -5156,6 +5156,31 @@ describe('Vapor Mode hydration', () => {
       )
     })
 
+    test('disabled teleport range should count as one logical child during hydration', async () => {
+      const data = ref({ msg: 'after' })
+
+      const { container } = await mountWithHydration(
+        '<div><!--teleport start--><span>teleported</span><!--teleport end--><p>after</p></div>',
+        `<div>
+          <teleport :to="undefined" :disabled="true">
+            <span>teleported</span>
+          </teleport>
+          <p>{{data.msg}}</p>
+        </div>`,
+        data,
+      )
+
+      expect(container.innerHTML).toBe(
+        '<div><!--teleport start--><span>teleported</span><!--teleport end--><p>after</p></div>',
+      )
+
+      data.value.msg = 'updated'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        '<div><!--teleport start--><span>teleported</span><!--teleport end--><p>updated</p></div>',
+      )
+    })
+
     test('enabled teleport with null target', async () => {
       const { container } = await mountWithHydration(
         '<!--teleport start--><!--teleport end-->',

+ 2 - 2
packages/runtime-vapor/src/apiCreateFor.ts

@@ -34,8 +34,8 @@ import {
   isComment,
   isHydrating,
   locateHydrationBoundaryClose,
-  locateNextNode,
   markHydrationAnchor,
+  nextLogicalSibling,
   setCurrentHydrationNode,
 } from './dom/hydration'
 import {
@@ -452,7 +452,7 @@ export const createFor = (
             nextNode = markHydrationAnchor(currentHydrationNode!)
             setCurrentHydrationNode(nextNode)
           } else {
-            nextNode = locateNextNode(currentHydrationNode!)
+            nextNode = nextLogicalSibling(currentHydrationNode!)
           }
           mount(source, i)
           if (nextNode) setCurrentHydrationNode(nextNode)

+ 3 - 3
packages/runtime-vapor/src/component.ts

@@ -96,8 +96,8 @@ import {
   isComment,
   isHydrating,
   locateEndAnchor,
-  locateNextNode,
   markHydrationAnchor,
+  nextLogicalSibling,
   setCurrentHydrationNode,
   withDeferredHydrationBoundary,
 } from './dom/hydration'
@@ -893,7 +893,7 @@ export function createComponentWithFallback(
         return node as any as HTMLElement
       }
 
-      const nextAnchor = locateNextNode(currentHydrationNode)
+      const nextAnchor = nextLogicalSibling(currentHydrationNode)
       if (nextAnchor && isReusableNullComponentAnchor(nextAnchor)) {
         // Keep the cursor on the stale SSR node before `nextAnchor` so the
         // owning DynamicFragment can trim that range on hydrate exit and then
@@ -967,7 +967,7 @@ export function createPlainElement(
   if (rawSlots) {
     let nextNode: Node | null = null
     if (isHydrating) {
-      nextNode = locateNextNode(el)
+      nextNode = nextLogicalSibling(el)
       setCurrentHydrationNode(el.firstChild)
     }
     if (rawSlots.$) {

+ 2 - 2
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -50,8 +50,8 @@ import {
   cleanupHydrationTail,
   currentHydrationNode,
   isHydrating,
-  locateNextNode,
   markHydrationAnchor,
+  nextLogicalSibling,
   setCurrentHydrationNode,
 } from '../dom/hydration'
 
@@ -208,7 +208,7 @@ const VaporTransitionGroupImpl = defineVaporComponent({
         ensureForHydrationAnchorResolver()
         prevForHydrationContainer = currentForHydrationContainer
         currentForHydrationContainer = container
-        nextNode = locateNextNode(container)
+        nextNode = nextLogicalSibling(container)
         setCurrentHydrationNode(container.firstChild || container)
       }
       let block: Block = slottedBlock

+ 9 - 9
packages/runtime-vapor/src/dom/hydration.ts

@@ -234,12 +234,12 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
   return node
 }
 
-export function locateNextNode(node: Node): Node | null {
+export function nextLogicalSibling(node: Node): Node | null {
   return isComment(node, '[')
-    ? _next(locateEndAnchor(node)!)
+    ? locateEndAnchor(node)!.nextSibling
     : isComment(node, 'teleport start')
-      ? _next(locateEndAnchor(node, 'teleport start', 'teleport end')!)
-      : _next(node)
+      ? locateEndAnchor(node, 'teleport start', 'teleport end')!.nextSibling
+      : node.nextSibling
 }
 
 function locateHydrationNodeImpl(consumeFragmentStart = false) {
@@ -307,9 +307,9 @@ export function locateHydrationBoundaryClose(
     if (isComment(node, ']')) {
       close = node
     } else {
-      let candidate = locateNextNode(node)
+      let candidate = nextLogicalSibling(node)
       while (candidate && !isComment(candidate, ']')) {
-        candidate = locateNextNode(candidate)
+        candidate = nextLogicalSibling(candidate)
       }
       close = candidate
     }
@@ -472,7 +472,7 @@ export function cleanupHydrationTail(node: Node, container?: ParentNode): void {
 
   let current: Node | null = node
   while (current && current.parentNode === container) {
-    const next = locateNextNode(current)
+    const next = nextLogicalSibling(current)
     removeHydrationNode(current)
     current = next
   }
@@ -529,7 +529,7 @@ function finalizeHydrationBoundary(close: Node | null): void {
     if (!isHydrationAnchor(cur)) {
       hasRemovableNode = true
     }
-    cur = locateNextNode(cur)
+    cur = nextLogicalSibling(cur)
   }
   if (!cur) return
   if (!hasRemovableNode) {
@@ -540,7 +540,7 @@ function finalizeHydrationBoundary(close: Node | null): void {
   warnHydrationChildrenMismatch((close as Node).parentElement)
 
   while (node && node !== close) {
-    const next = locateNextNode(node)
+    const next = nextLogicalSibling(node)
     if (!isHydrationAnchor(node)) {
       removeHydrationNode(node, close)
     }

+ 2 - 7
packages/runtime-vapor/src/dom/node.ts

@@ -1,5 +1,5 @@
 import type { ChildItem, InsertionParent } from '../insertionState'
-import { isComment, isHydrating, locateEndAnchor } from './hydration'
+import { isHydrating, nextLogicalSibling } from './hydration'
 
 /*@__NO_SIDE_EFFECTS__*/
 export function createElement(tagName: string): HTMLElement {
@@ -102,12 +102,7 @@ export function locateChildByLogicalIndex(
       return (parent.$llc = child)
     }
 
-    child = (
-      isComment(child, '[')
-        ? // fragment start: jump to the node after the matching end anchor
-          locateEndAnchor(child)!.nextSibling
-        : child.nextSibling
-    ) as ChildItem
+    child = nextLogicalSibling(child) as ChildItem
 
     fromIndex++
   }

+ 3 - 3
packages/runtime-vapor/src/fragment.ts

@@ -40,8 +40,8 @@ import {
   locateEndAnchor,
   locateHydrationBoundaryClose,
   locateHydrationNode,
-  locateNextNode,
   markHydrationAnchor,
+  nextLogicalSibling,
   setCurrentHydrationNode,
 } from './dom/hydration'
 import { isArray } from '@vue/shared'
@@ -559,7 +559,7 @@ export class DynamicFragment extends VaporFragment {
           !isComment(currentHydrationNode, ']')
         ) {
           const parentNode = getParentNode(currentHydrationNode)
-          const anchor = locateNextNode(currentHydrationNode)
+          const anchor = nextLogicalSibling(currentHydrationNode)
           // Empty branch against non-empty SSR output has no block node to
           // derive an insertion point from, so use the current hydration range.
           const reusableAnchor =
@@ -619,7 +619,7 @@ export class DynamicFragment extends VaporFragment {
         currentHydrationNode
       ) {
         const parentNode = getParentNode(currentHydrationNode)
-        const nextNode = locateNextNode(currentHydrationNode)
+        const nextNode = nextLogicalSibling(currentHydrationNode)
         if (parentNode) {
           this.nodes = []
           cleanupAndInsertRuntimeAnchor(