diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index d3cfd47c6be..f0a3a9333a7 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -393,6 +393,28 @@ describe('SSR hydration', () => { ) }) + // #6152 + test('Teleport (disabled + as component root)', () => { + const { container } = mountWithHydration( + '
Parent fragment
Teleport content
', + () => [ + h('div', 'Parent fragment'), + h(() => + h(Teleport, { to: 'body', disabled: true }, [ + h('div', 'Teleport content') + ]) + ) + ] + ) + expect(document.body.innerHTML).toBe('') + expect(container.innerHTML).toBe( + '
Parent fragment
Teleport content
' + ) + expect( + `Hydration completed but contains mismatches.` + ).not.toHaveBeenWarned() + }) + test('Teleport (as component root)', () => { const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport4' diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 89a00886332..097443dbc53 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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 {