Skip to content

Commit dd8a0cf

Browse files
committed
fix(hydration): fix tagName access eeror on comment/text node hydration mismatch
fix #9531
1 parent 405f345 commit dd8a0cf

File tree

2 files changed

+41
-17
lines changed

2 files changed

+41
-17
lines changed

packages/runtime-core/__tests__/hydration.spec.ts

+26
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ describe('SSR hydration', () => {
7575
expect(vnode.el.nodeType).toBe(8) // comment
7676
})
7777

78+
test('comment (real left square bracket)', () => {
79+
const { vnode, container } = mountWithHydration(
80+
`<div><span>foo</span><!--hello--></div>`,
81+
() => h('div', [h('span', 'foo'), createCommentVNode('hello')])
82+
)
83+
expect(vnode.el).toBe(container.firstChild)
84+
expect(container.innerHTML).toBe('<div><span>foo</span><!--hello--></div>')
85+
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
86+
})
87+
7888
test('static', () => {
7989
const html = '<div><span>hello</span></div>'
8090
const { vnode, container } = mountWithHydration(html, () =>
@@ -1177,5 +1187,21 @@ describe('SSR hydration', () => {
11771187
expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
11781188
expect(`Hydration children mismatch`).toHaveBeenWarned()
11791189
})
1190+
1191+
test('comment mismatch (element)', () => {
1192+
const { container } = mountWithHydration(`<div><span></span></div>`, () =>
1193+
h('div', [createCommentVNode('hi')])
1194+
)
1195+
expect(container.innerHTML).toBe('<div><!--hi--></div>')
1196+
expect(`Hydration node mismatch`).toHaveBeenWarned()
1197+
})
1198+
1199+
test('comment mismatch (text)', () => {
1200+
const { container } = mountWithHydration(`<div>foobar</div>`, () =>
1201+
h('div', [createCommentVNode('hi')])
1202+
)
1203+
expect(container.innerHTML).toBe('<div><!--hi--></div>')
1204+
expect(`Hydration node mismatch`).toHaveBeenWarned()
1205+
})
11801206
})
11811207
})

packages/runtime-core/src/hydration.ts

+15-17
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,17 @@ export function createHydrationFunctions(
145145
}
146146
break
147147
case Comment:
148-
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
149-
if ((node as Element).tagName.toLowerCase() === 'template') {
150-
const content = (vnode.el! as HTMLTemplateElement).content
151-
.firstChild!
152-
153-
// replace <template> node with inner children
154-
replaceNode(content, node, parentComponent)
155-
vnode.el = node = content
156-
nextNode = nextSibling(node)
157-
} else {
158-
nextNode = onMismatch()
159-
}
148+
if (isTemplateNode(node)) {
149+
nextNode = nextSibling(node)
150+
// wrapped <transition appear>
151+
// replace <template> node with inner child
152+
replaceNode(
153+
(vnode.el = node.content.firstChild!),
154+
node,
155+
parentComponent
156+
)
157+
} else if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
158+
nextNode = onMismatch()
160159
} else {
161160
nextNode = nextSibling(node)
162161
}
@@ -209,7 +208,7 @@ export function createHydrationFunctions(
209208
(domType !== DOMNodeTypes.ELEMENT ||
210209
(vnode.type as string).toLowerCase() !==
211210
(node as Element).tagName.toLowerCase()) &&
212-
!isTemplateNode(node as Element)
211+
!isTemplateNode(node)
213212
) {
214213
nextNode = onMismatch()
215214
} else {
@@ -637,17 +636,16 @@ export function createHydrationFunctions(
637636
let parent = parentComponent
638637
while (parent) {
639638
if (parent.vnode.el === oldNode) {
640-
parent.vnode.el = newNode
641-
parent.subTree.el = newNode
639+
parent.vnode.el = parent.subTree.el = newNode
642640
}
643641
parent = parent.parent
644642
}
645643
}
646644

647-
const isTemplateNode = (node: Element): boolean => {
645+
const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
648646
return (
649647
node.nodeType === DOMNodeTypes.ELEMENT &&
650-
node.tagName.toLowerCase() === 'template'
648+
(node as Element).tagName.toLowerCase() === 'template'
651649
)
652650
}
653651

0 commit comments

Comments
 (0)