From 884900bd791634027039d8f47ad6e10cad10e3e8 Mon Sep 17 00:00:00 2001
From: Rairn <958414905@qq.com>
Date: Thu, 8 Dec 2022 22:21:51 +0800
Subject: [PATCH] fix(ssr): fix hydration mismatch warning about mutiple
continuous text vnodes (#7285)
---
.../runtime-core/__tests__/hydration.spec.ts | 19 +++++
packages/runtime-core/src/hydration.ts | 73 ++++++++++++++++++-
2 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index fece1a33f9d..efa2476f6a6 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -136,6 +136,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('foo
', () =>
+ h('div', ['fo', 'o'])
+ )
+ expect(container.textContent).toBe('foo')
+ })
+
test('element with elements children', async () => {
const msg = ref('foo')
const fn = vi.fn()
@@ -224,6 +233,16 @@ describe('SSR hydration', () => {
)
})
+ // #7285
+ test('Fragment (multiple continuous text vnodes)', async () => {
+ // should no mismatch warning
+ const { container } = mountWithHydration('foo', () => [
+ 'fo',
+ 'o'
+ ])
+ expect(container.textContent).toBe('foo')
+ })
+
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 2170a9192cf..9392f8048c4 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -428,11 +428,78 @@ export function createHydrationFunctions(
optimized = optimized || !!parentVNode.dynamicChildren
const children = parentVNode.children as VNode[]
const l = children.length
+ const continuousTextVnodes = []
+ const getVnode = (i: number) =>
+ optimized ? children[i] : (children[i] = normalizeVNode(children[i]))
let hasWarned = false
for (let i = 0; i < l; i++) {
- const vnode = optimized
- ? children[i]
- : (children[i] = normalizeVNode(children[i]))
+ let vnode = getVnode(i)
+
+ // #7285 - multiple continuous text vnodes in children can cause hydration
+ // failure because the server rendered HTML just contain one text node
+ if (
+ vnode.type === Text &&
+ node &&
+ node.nodeType === DOMNodeTypes.TEXT &&
+ vnode.children !== (node as Text).data
+ ) {
+ const nextVnode = getVnode(i + 1)
+ // for final merging into one text
+ continuousTextVnodes.push(vnode)
+ // if the next vnode is also text, it means the children has multiple continuous
+ // text vnodes, we need to merge them into one text to avoid hydration failure
+ if (nextVnode.type === Text) {
+ patch(
+ null,
+ vnode,
+ container,
+ node,
+ parentComponent,
+ parentSuspense,
+ isSVGContainer(container),
+ slotScopeIds
+ )
+ continue
+ } else if (continuousTextVnodes.length > 1) {
+ const text = continuousTextVnodes.map(v => v.children).join('')
+ if ((node as Text).data !== text) {
+ hasMismatch = true
+ __DEV__ &&
+ warn(
+ `Hydration text mismatch:` +
+ `\n- Client: ${JSON.stringify((node as Text).data)}` +
+ `\n- Server: ${text}`
+ )
+ }
+ // insert the last text vnode to the container
+ patch(
+ null,
+ vnode,
+ container,
+ node,
+ parentComponent,
+ parentSuspense,
+ isSVGContainer(container),
+ slotScopeIds
+ )
+
+ const nextNode = nextSibling(node)
+ // because the node's text has been inserted to the container,
+ // so we need to remove it
+ remove(node)
+ node = nextNode
+ vnode = nextVnode
+ continuousTextVnodes.length = 0
+ i++
+ if (i === l) {
+ continue
+ }
+ } else {
+ // if only one text vnode, do nothing
+ continuousTextVnodes.length = 0
+ }
+ }
+
if (node) {
node = hydrateNode(
node,