diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 58661233..34e76100 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -13,7 +13,8 @@ import { createTextVNode, createVNode, withDirectives, - vModelCheckbox + vModelCheckbox, + renderSlot } from '@vue/runtime-dom' import { renderToString, SSRContext } from '@vue/server-renderer' import { PatchFlags } from '../../shared/src' @@ -912,6 +913,24 @@ describe('SSR hydration', () => { expect((container.firstChild!.firstChild as any)._value).toBe(true) }) + // #5728 + test('empty text node in slot', () => { + const Comp = { + render(this: any) { + return renderSlot(this.$slots, 'default', {}, () => [ + createTextVNode('') + ]) + } + } + const { container, vnode } = mountWithHydration('', () => h(Comp)) + expect(container.childNodes.length).toBe(3) + const text = container.childNodes[1] + expect(text.nodeType).toBe(3) + expect(vnode.el).toBe(container.childNodes[0]) + // component => slot fragment => text node + expect((vnode as any).component?.subTree.children[0].el).toBe(text) + }) + describe('mismatch handling', () => { test('text node', () => { const { container } = mountWithHydration(`foo`, () => 'bar') diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index fcc22897..a8a568ca 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -110,7 +110,18 @@ export function createHydrationFunctions( switch (type) { case Text: if (domType !== DOMNodeTypes.TEXT) { - nextNode = onMismatch() + // #5728 empty text node inside a slot can cause hydration failure + // because the server rendered HTML won't contain a text node + if (vnode.children === '') { + insert( + (vnode.el = document.createTextNode('')), + node.parentElement!, + node + ) + nextNode = node + } else { + nextNode = onMismatch() + } } else { if ((node as Text).data !== vnode.children) { hasMismatch = true