Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/runtime-core/__tests__/hydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,28 @@ describe('SSR hydration', () => {
)
})

// #6152
test('Teleport (disabled + as component root)', () => {
const { container } = mountWithHydration(
'<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
() => [
h('div', 'Parent fragment'),
h(() =>
h(Teleport, { to: 'body', disabled: true }, [
h('div', 'Teleport content')
])
)
]
)
expect(document.body.innerHTML).toBe('')
expect(container.innerHTML).toBe(
'<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->'
)
expect(
`Hydration completed but contains mismatches.`
).not.toHaveBeenWarned()
})

test('Teleport (as component root)', () => {
const teleportContainer = document.createElement('div')
teleportContainer.id = 'teleport4'
Expand Down
39 changes: 21 additions & 18 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down