fix(hydration): fix update before async component is hydrated (#3563)
fix #3560
This commit is contained in:
parent
43f78151bf
commit
c8d96837b8
@ -626,6 +626,67 @@ describe('SSR hydration', () => {
|
|||||||
expect(spy).toHaveBeenCalled()
|
expect(spy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('execute the updateComponent(AsyncComponentWrapper) before the async component is resolved', async () => {
|
||||||
|
const Comp = {
|
||||||
|
render() {
|
||||||
|
return h('h1', 'Async component')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let serverResolve: any
|
||||||
|
let AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
serverResolve = r
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const bol = ref(true)
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
// change state, this makes updateComponent(AsyncComp) execute before
|
||||||
|
// the async component is resolved
|
||||||
|
bol.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return [bol.value ? 'hello' : 'world', h(AsyncComp)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// server render
|
||||||
|
const htmlPromise = renderToString(h(App))
|
||||||
|
serverResolve(Comp)
|
||||||
|
const html = await htmlPromise
|
||||||
|
expect(html).toMatchInlineSnapshot(
|
||||||
|
`"<!--[-->hello<h1>Async component</h1><!--]-->"`
|
||||||
|
)
|
||||||
|
|
||||||
|
// hydration
|
||||||
|
let clientResolve: any
|
||||||
|
AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
clientResolve = r
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = html
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
|
||||||
|
// resolve
|
||||||
|
clientResolve(Comp)
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
|
||||||
|
// should be hydrated now
|
||||||
|
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--[-->world<h1>Async component</h1><!--]-->"`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('elements with camel-case in svg ', () => {
|
test('elements with camel-case in svg ', () => {
|
||||||
const { vnode, container } = mountWithHydration(
|
const { vnode, container } = mountWithHydration(
|
||||||
'<animateTransform></animateTransform>',
|
'<animateTransform></animateTransform>',
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
VNodeHook
|
VNodeHook
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { flushPostFlushCbs } from './scheduler'
|
import { flushPostFlushCbs } from './scheduler'
|
||||||
import { ComponentOptions, ComponentInternalInstance } from './component'
|
import { ComponentInternalInstance } from './component'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
|
||||||
@ -178,7 +178,6 @@ export function createHydrationFunctions(
|
|||||||
// on its sub-tree.
|
// on its sub-tree.
|
||||||
vnode.slotScopeIds = slotScopeIds
|
vnode.slotScopeIds = slotScopeIds
|
||||||
const container = parentNode(node)!
|
const container = parentNode(node)!
|
||||||
const hydrateComponent = () => {
|
|
||||||
mountComponent(
|
mountComponent(
|
||||||
vnode,
|
vnode,
|
||||||
container,
|
container,
|
||||||
@ -188,14 +187,6 @@ export function createHydrationFunctions(
|
|||||||
isSVGContainer(container),
|
isSVGContainer(container),
|
||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
}
|
|
||||||
// async component
|
|
||||||
const loadAsync = (vnode.type as ComponentOptions).__asyncLoader
|
|
||||||
if (loadAsync) {
|
|
||||||
loadAsync().then(hydrateComponent)
|
|
||||||
} else {
|
|
||||||
hydrateComponent()
|
|
||||||
}
|
|
||||||
// component may be async, so in the case of fragments we cannot rely
|
// 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
|
// on component's rendered output to determine the end of the fragment
|
||||||
// instead, we do a lookahead to find the end anchor node.
|
// instead, we do a lookahead to find the end anchor node.
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
Data,
|
Data,
|
||||||
setupComponent
|
setupComponent
|
||||||
@ -1430,23 +1431,22 @@ function baseCreateRenderer(
|
|||||||
instance.emit('hook:beforeMount')
|
instance.emit('hook:beforeMount')
|
||||||
}
|
}
|
||||||
|
|
||||||
// render
|
if (el && hydrateNode) {
|
||||||
|
// vnode has adopted host node - perform hydration instead of mount.
|
||||||
|
const hydrateSubTree = () => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
startMeasure(instance, `render`)
|
startMeasure(instance, `render`)
|
||||||
}
|
}
|
||||||
const subTree = (instance.subTree = renderComponentRoot(instance))
|
instance.subTree = renderComponentRoot(instance)
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
endMeasure(instance, `render`)
|
endMeasure(instance, `render`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el && hydrateNode) {
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
startMeasure(instance, `hydrate`)
|
startMeasure(instance, `hydrate`)
|
||||||
}
|
}
|
||||||
// vnode has adopted host node - perform hydration instead of mount.
|
hydrateNode!(
|
||||||
hydrateNode(
|
el as Node,
|
||||||
initialVNode.el as Node,
|
instance.subTree,
|
||||||
subTree,
|
|
||||||
instance,
|
instance,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
null
|
null
|
||||||
@ -1454,7 +1454,27 @@ function baseCreateRenderer(
|
|||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
endMeasure(instance, `hydrate`)
|
endMeasure(instance, `hydrate`)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAsyncWrapper(initialVNode)) {
|
||||||
|
(initialVNode.type as ComponentOptions).__asyncLoader!().then(
|
||||||
|
// note: we are moving the render call into an async callback,
|
||||||
|
// which means it won't track dependencies - but it's ok because
|
||||||
|
// a server-rendered async wrapper is already in resolved state
|
||||||
|
// and it will never need to change.
|
||||||
|
hydrateSubTree
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
|
hydrateSubTree()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (__DEV__) {
|
||||||
|
startMeasure(instance, `render`)
|
||||||
|
}
|
||||||
|
const subTree = (instance.subTree = renderComponentRoot(instance))
|
||||||
|
if (__DEV__) {
|
||||||
|
endMeasure(instance, `render`)
|
||||||
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
startMeasure(instance, `patch`)
|
startMeasure(instance, `patch`)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user