diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index a1213145..8db566b5 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -626,6 +626,67 @@ describe('SSR hydration', () => {
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
Async component
"`
+ )
+
+ // 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(
+ `"worldAsync component
"`
+ )
+ })
+
test('elements with camel-case in svg ', () => {
const { vnode, container } = mountWithHydration(
'',
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index 0085ce6e..45b6d7b0 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -8,7 +8,7 @@ import {
VNodeHook
} from './vnode'
import { flushPostFlushCbs } from './scheduler'
-import { ComponentOptions, ComponentInternalInstance } from './component'
+import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
@@ -178,24 +178,15 @@ export function createHydrationFunctions(
// on its sub-tree.
vnode.slotScopeIds = slotScopeIds
const container = parentNode(node)!
- const hydrateComponent = () => {
- mountComponent(
- vnode,
- container,
- null,
- parentComponent,
- parentSuspense,
- isSVGContainer(container),
- optimized
- )
- }
- // async component
- const loadAsync = (vnode.type as ComponentOptions).__asyncLoader
- if (loadAsync) {
- loadAsync().then(hydrateComponent)
- } else {
- hydrateComponent()
- }
+ mountComponent(
+ vnode,
+ container,
+ null,
+ parentComponent,
+ parentSuspense,
+ isSVGContainer(container),
+ 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.
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index c075b3e5..4e661bc9 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -16,6 +16,7 @@ import {
} from './vnode'
import {
ComponentInternalInstance,
+ ComponentOptions,
createComponentInstance,
Data,
setupComponent
@@ -1430,31 +1431,50 @@ function baseCreateRenderer(
instance.emit('hook:beforeMount')
}
- // render
- if (__DEV__) {
- startMeasure(instance, `render`)
- }
- const subTree = (instance.subTree = renderComponentRoot(instance))
- if (__DEV__) {
- endMeasure(instance, `render`)
- }
-
if (el && hydrateNode) {
- if (__DEV__) {
- startMeasure(instance, `hydrate`)
- }
// vnode has adopted host node - perform hydration instead of mount.
- hydrateNode(
- initialVNode.el as Node,
- subTree,
- instance,
- parentSuspense,
- null
- )
- if (__DEV__) {
- endMeasure(instance, `hydrate`)
+ const hydrateSubTree = () => {
+ if (__DEV__) {
+ startMeasure(instance, `render`)
+ }
+ instance.subTree = renderComponentRoot(instance)
+ if (__DEV__) {
+ endMeasure(instance, `render`)
+ }
+ if (__DEV__) {
+ startMeasure(instance, `hydrate`)
+ }
+ hydrateNode!(
+ el as Node,
+ instance.subTree,
+ instance,
+ parentSuspense,
+ null
+ )
+ if (__DEV__) {
+ 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 {
+ hydrateSubTree()
}
} else {
+ if (__DEV__) {
+ startMeasure(instance, `render`)
+ }
+ const subTree = (instance.subTree = renderComponentRoot(instance))
+ if (__DEV__) {
+ endMeasure(instance, `render`)
+ }
if (__DEV__) {
startMeasure(instance, `patch`)
}