Skip to content

Commit d8990fc

Browse files
authored
fix(ssr): fix hydration mismatch for disabled teleport at component root (#9399)
close #6152
1 parent a8f6638 commit d8990fc

File tree

2 files changed

+43
-18
lines changed

2 files changed

+43
-18
lines changed

Diff for: packages/runtime-core/__tests__/hydration.spec.ts

+22
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,28 @@ describe('SSR hydration', () => {
393393
)
394394
})
395395

396+
// #6152
397+
test('Teleport (disabled + as component root)', () => {
398+
const { container } = mountWithHydration(
399+
'<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
400+
() => [
401+
h('div', 'Parent fragment'),
402+
h(() =>
403+
h(Teleport, { to: 'body', disabled: true }, [
404+
h('div', 'Teleport content')
405+
])
406+
)
407+
]
408+
)
409+
expect(document.body.innerHTML).toBe('')
410+
expect(container.innerHTML).toBe(
411+
'<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->'
412+
)
413+
expect(
414+
`Hydration completed but contains mismatches.`
415+
).not.toHaveBeenWarned()
416+
})
417+
396418
test('Teleport (as component root)', () => {
397419
const teleportContainer = document.createElement('div')
398420
teleportContainer.id = 'teleport4'

Diff for: packages/runtime-core/src/hydration.ts

+21-18
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,18 @@ export function createHydrationFunctions(
227227
optimized
228228
)
229229

230-
// component may be async, so in the case of fragments we cannot rely
231-
// on component's rendered output to determine the end of the fragment
232-
// instead, we do a lookahead to find the end anchor node.
233-
nextNode = isFragmentStart
234-
? locateClosingAsyncAnchor(node)
235-
: nextSibling(node)
236-
237-
// #4293 teleport as component root
238-
if (
239-
nextNode &&
240-
isComment(nextNode) &&
241-
nextNode.data === 'teleport end'
242-
) {
243-
nextNode = nextSibling(nextNode)
230+
// Locate the next node.
231+
if (isFragmentStart) {
232+
// If it's a fragment: since components may be async, we cannot rely
233+
// on component's rendered output to determine the end of the
234+
// fragment. Instead, we do a lookahead to find the end anchor node.
235+
nextNode = locateClosingAnchor(node)
236+
} else if (isComment(node) && node.data === 'teleport start') {
237+
// #4293 #6152
238+
// If a teleport is at component root, look ahead for teleport end.
239+
nextNode = locateClosingAnchor(node, node.data, 'teleport end')
240+
} else {
241+
nextNode = nextSibling(node)
244242
}
245243

246244
// #3787
@@ -533,7 +531,7 @@ export function createHydrationFunctions(
533531

534532
if (isFragment) {
535533
// remove excessive fragment nodes
536-
const end = locateClosingAsyncAnchor(node)
534+
const end = locateClosingAnchor(node)
537535
while (true) {
538536
const next = nextSibling(node)
539537
if (next && next !== end) {
@@ -561,13 +559,18 @@ export function createHydrationFunctions(
561559
return next
562560
}
563561

564-
const locateClosingAsyncAnchor = (node: Node | null): Node | null => {
562+
// looks ahead for a start and closing comment node
563+
const locateClosingAnchor = (
564+
node: Node | null,
565+
open = '[',
566+
close = ']'
567+
): Node | null => {
565568
let match = 0
566569
while (node) {
567570
node = nextSibling(node)
568571
if (node && isComment(node)) {
569-
if (node.data === '[') match++
570-
if (node.data === ']') {
572+
if (node.data === open) match++
573+
if (node.data === close) {
571574
if (match === 0) {
572575
return nextSibling(node)
573576
} else {

0 commit comments

Comments
 (0)