Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ssr): Using teleport disabled causes mismatch #9399

Merged
merged 3 commits into from
Oct 21, 2023
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.
Jump to
Jump to file
Failed to load files.
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