]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): fix hydration mismatch for disabled teleport at component root (#9399) 9429/head
authorCarlos Rodrigues <carlos@hypermob.co.uk>
Sat, 21 Oct 2023 03:37:52 +0000 (04:37 +0100)
committerGitHub <noreply@github.com>
Sat, 21 Oct 2023 03:37:52 +0000 (11:37 +0800)
close #6152

packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts

index d3cfd47c6be5eafea34d9749e2906522b48104d1..f0a3a9333a7987eb538e4a051230405812acfac0 100644 (file)
@@ -393,6 +393,28 @@ describe('SSR hydration', () => {
     )
   })
 
+  // #6152
+  test('Teleport (disabled + as component root)', () => {
+    const { container } = mountWithHydration(
+      '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
+      () => [
+        h('div', 'Parent fragment'),
+        h(() =>
+          h(Teleport, { to: 'body', disabled: true }, [
+            h('div', 'Teleport content')
+          ])
+        )
+      ]
+    )
+    expect(document.body.innerHTML).toBe('')
+    expect(container.innerHTML).toBe(
+      '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->'
+    )
+    expect(
+      `Hydration completed but contains mismatches.`
+    ).not.toHaveBeenWarned()
+  })
+
   test('Teleport (as component root)', () => {
     const teleportContainer = document.createElement('div')
     teleportContainer.id = 'teleport4'
index 89a00886332a1aa2d7790e6ae65d5a8322d23124..097443dbc533b23f106af0d6a3dbc1341f2bf02e 100644 (file)
@@ -227,20 +227,18 @@ export function createHydrationFunctions(
             optimized
           )
 
-          // component may be async, so in the case of fragments we cannot rely
-          // on component's rendered output to determine the end of the fragment
-          // instead, we do a lookahead to find the end anchor node.
-          nextNode = isFragmentStart
-            ? locateClosingAsyncAnchor(node)
-            : nextSibling(node)
-
-          // #4293 teleport as component root
-          if (
-            nextNode &&
-            isComment(nextNode) &&
-            nextNode.data === 'teleport end'
-          ) {
-            nextNode = nextSibling(nextNode)
+          // Locate the next node.
+          if (isFragmentStart) {
+            // If it's a fragment: since components may be async, we cannot rely
+            // on component's rendered output to determine the end of the
+            // fragment. Instead, we do a lookahead to find the end anchor node.
+            nextNode = locateClosingAnchor(node)
+          } else if (isComment(node) && node.data === 'teleport start') {
+            // #4293 #6152
+            // If a teleport is at component root, look ahead for teleport end.
+            nextNode = locateClosingAnchor(node, node.data, 'teleport end')
+          } else {
+            nextNode = nextSibling(node)
           }
 
           // #3787
@@ -533,7 +531,7 @@ export function createHydrationFunctions(
 
     if (isFragment) {
       // remove excessive fragment nodes
-      const end = locateClosingAsyncAnchor(node)
+      const end = locateClosingAnchor(node)
       while (true) {
         const next = nextSibling(node)
         if (next && next !== end) {
@@ -561,13 +559,18 @@ export function createHydrationFunctions(
     return next
   }
 
-  const locateClosingAsyncAnchor = (node: Node | null): Node | null => {
+  // looks ahead for a start and closing comment node
+  const locateClosingAnchor = (
+    node: Node | null,
+    open = '[',
+    close = ']'
+  ): Node | null => {
     let match = 0
     while (node) {
       node = nextSibling(node)
       if (node && isComment(node)) {
-        if (node.data === '[') match++
-        if (node.data === ']') {
+        if (node.data === open) match++
+        if (node.data === close) {
           if (match === 0) {
             return nextSibling(node)
           } else {