mirror of https://github.com/vuejs/core.git
fix(hydration): properly handle optimized mode during hydrate node (#10638)
close #10607
This commit is contained in:
parent
0cef65cee4
commit
2ec06fd6c8
|
@ -7,7 +7,10 @@ import {
|
||||||
Teleport,
|
Teleport,
|
||||||
Transition,
|
Transition,
|
||||||
type VNode,
|
type VNode,
|
||||||
|
createBlock,
|
||||||
createCommentVNode,
|
createCommentVNode,
|
||||||
|
createElementBlock,
|
||||||
|
createElementVNode,
|
||||||
createSSRApp,
|
createSSRApp,
|
||||||
createStaticVNode,
|
createStaticVNode,
|
||||||
createTextVNode,
|
createTextVNode,
|
||||||
|
@ -17,16 +20,19 @@ import {
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
openBlock,
|
||||||
ref,
|
ref,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
useCssVars,
|
useCssVars,
|
||||||
vModelCheckbox,
|
vModelCheckbox,
|
||||||
vShow,
|
vShow,
|
||||||
|
withCtx,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||||
|
import { expect } from 'vitest'
|
||||||
|
|
||||||
function mountWithHydration(html: string, render: () => any) {
|
function mountWithHydration(html: string, render: () => any) {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
|
@ -1292,6 +1298,81 @@ describe('SSR hydration', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10607
|
||||||
|
test('update component stable slot (prod + optimized mode)', async () => {
|
||||||
|
__DEV__ = false
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
|
||||||
|
const Comp = {
|
||||||
|
render(this: any) {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock('div', null, [renderSlot(this.$slots, 'default')])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const show = ref(false)
|
||||||
|
const clicked = ref(false)
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
setup() {
|
||||||
|
const items = ref<number[]>([])
|
||||||
|
onMounted(() => {
|
||||||
|
items.value = [1]
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createBlock(Comp, null, {
|
||||||
|
default: withCtx(() => [
|
||||||
|
createElementVNode('div', null, [
|
||||||
|
createElementVNode('div', null, [
|
||||||
|
clicked.value
|
||||||
|
? (openBlock(),
|
||||||
|
createElementBlock('div', { key: 0 }, 'foo'))
|
||||||
|
: createCommentVNode('v-if', true),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
createElementVNode(
|
||||||
|
'div',
|
||||||
|
null,
|
||||||
|
items.value.length,
|
||||||
|
1 /* TEXT */,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
_: 1 /* STABLE */,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createSSRApp({
|
||||||
|
components: { Wrapper },
|
||||||
|
data() {
|
||||||
|
return { show }
|
||||||
|
},
|
||||||
|
template: `<Wrapper :show="show"/>`,
|
||||||
|
}).mount(container)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(async () => {
|
||||||
|
clicked.value = true
|
||||||
|
await nextTick()
|
||||||
|
}).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
|
||||||
|
)
|
||||||
|
__DEV__ = true
|
||||||
|
})
|
||||||
|
|
||||||
describe('mismatch handling', () => {
|
describe('mismatch handling', () => {
|
||||||
test('text node', () => {
|
test('text node', () => {
|
||||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||||
|
|
|
@ -120,6 +120,7 @@ export function createHydrationFunctions(
|
||||||
slotScopeIds: string[] | null,
|
slotScopeIds: string[] | null,
|
||||||
optimized = false,
|
optimized = false,
|
||||||
): Node | null => {
|
): Node | null => {
|
||||||
|
optimized = optimized || !!vnode.dynamicChildren
|
||||||
const isFragmentStart = isComment(node) && node.data === '['
|
const isFragmentStart = isComment(node) && node.data === '['
|
||||||
const onMismatch = () =>
|
const onMismatch = () =>
|
||||||
handleMismatch(
|
handleMismatch(
|
||||||
|
|
Loading…
Reference in New Issue