]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hydration): handle consectuvie text nodes during hydration
authorEvan You <evan@vuejs.org>
Tue, 16 Jul 2024 03:06:45 +0000 (11:06 +0800)
committerEvan You <evan@vuejs.org>
Tue, 16 Jul 2024 03:09:00 +0000 (11:09 +0800)
close #7285
close #7301

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

index 60941d0369c95f0da4a9fbbdd05a965d4c8f0947..6ae9caf8e796c1f3ab7c74149b254bf454ab3199 100644 (file)
@@ -148,6 +148,15 @@ describe('SSR hydration', () => {
     expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
   })
 
+  // #7285
+  test('element with multiple continuous text vnodes', async () => {
+    // should no mismatch warning
+    const { container } = mountWithHydration('<div>fooo</div>', () =>
+      h('div', ['fo', createTextVNode('o'), 'o']),
+    )
+    expect(container.textContent).toBe('fooo')
+  })
+
   test('element with elements children', async () => {
     const msg = ref('foo')
     const fn = vi.fn()
@@ -239,6 +248,17 @@ describe('SSR hydration', () => {
     )
   })
 
+  // #7285
+  test('Fragment (multiple continuous text vnodes)', async () => {
+    // should no mismatch warning
+    const { container } = mountWithHydration('<!--[-->fooo<!--]-->', () => [
+      'fo',
+      createTextVNode('o'),
+      'o',
+    ])
+    expect(container.textContent).toBe('fooo')
+  })
+
   test('Teleport', async () => {
     const msg = ref('foo')
     const fn = vi.fn()
index 8eca0705dfde79c2e203ba759c3f875225be3a08..20ff37cdf1ce0068976bdcea21571cae4be8b730 100644 (file)
@@ -531,7 +531,27 @@ export function createHydrationFunctions(
       const vnode = optimized
         ? children[i]
         : (children[i] = normalizeVNode(children[i]))
+      const isText = vnode.type === Text
       if (node) {
+        if (isText && !optimized) {
+          // #7285 possible consecutive text vnodes from manual render fns or
+          // JSX-compiled fns, but on the client the browser parses only 1 text
+          // node.
+          // look ahead for next possible text vnode
+          let next = children[i + 1]
+          if (next && (next = normalizeVNode(next)).type === Text) {
+            // create an extra TextNode on the client for the next vnode to
+            // adopt
+            insert(
+              createText(
+                (node as Text).data.slice((vnode.children as string).length),
+              ),
+              container,
+              nextSibling(node),
+            )
+            ;(node as Text).data = vnode.children as string
+          }
+        }
         node = hydrateNode(
           node,
           vnode,
@@ -540,7 +560,7 @@ export function createHydrationFunctions(
           slotScopeIds,
           optimized,
         )
-      } else if (vnode.type === Text && !vnode.children) {
+      } else if (isText && !vnode.children) {
         // #7215 create a TextNode for empty text node
         // because server rendered HTML won't contain a text node
         insert((vnode.el = createText('')), container)