From: Evan You Date: Tue, 16 Jul 2024 03:06:45 +0000 (+0800) Subject: fix(hydration): handle consectuvie text nodes during hydration X-Git-Tag: v3.4.32~12 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f44c3b37d446d5f8e34539029dae0d806b25bb47;p=thirdparty%2Fvuejs%2Fcore.git fix(hydration): handle consectuvie text nodes during hydration close #7285 close #7301 --- diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 60941d0369..6ae9caf8e7 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -148,6 +148,15 @@ describe('SSR hydration', () => { expect(container.innerHTML).toBe(`
bar
`) }) + // #7285 + test('element with multiple continuous text vnodes', async () => { + // should no mismatch warning + const { container } = mountWithHydration('
fooo
', () => + 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() diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 8eca0705df..20ff37cdf1 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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)