]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(suspense/hydration): fix hydration timing of async component inside suspense
authorEvan You <evan@vuejs.org>
Wed, 24 Jul 2024 14:15:25 +0000 (22:15 +0800)
committerEvan You <evan@vuejs.org>
Wed, 24 Jul 2024 14:15:25 +0000 (22:15 +0800)
close #6638

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

index efc0507d81e343fc6b3c60fa95c5d48aed3cb7ce..ed1e228c4cf000ae10b55bd28cbb0f3d5b966923 100644 (file)
@@ -688,6 +688,54 @@ describe('SSR hydration', () => {
     expect(container.innerHTML).toBe(`<span>1</span>`)
   })
 
+  // #6638
+  test('Suspense + async component', async () => {
+    let isSuspenseResolved = false
+    let isSuspenseResolvedInChild: any
+    const AsyncChild = defineAsyncComponent(() =>
+      Promise.resolve(
+        defineComponent({
+          setup() {
+            isSuspenseResolvedInChild = isSuspenseResolved
+            const count = ref(0)
+            return () =>
+              h(
+                'span',
+                {
+                  onClick: () => {
+                    count.value++
+                  },
+                },
+                count.value,
+              )
+          },
+        }),
+      ),
+    )
+    const { vnode, container } = mountWithHydration('<span>0</span>', () =>
+      h(
+        Suspense,
+        {
+          onResolve() {
+            isSuspenseResolved = true
+          },
+        },
+        () => h(AsyncChild),
+      ),
+    )
+    expect(vnode.el).toBe(container.firstChild)
+    // wait for hydration to finish
+    await new Promise(r => setTimeout(r))
+
+    expect(isSuspenseResolvedInChild).toBe(false)
+    expect(isSuspenseResolved).toBe(true)
+
+    // assert interaction
+    triggerEvent('click', container.querySelector('span')!)
+    await nextTick()
+    expect(container.innerHTML).toBe(`<span>1</span>`)
+  })
+
   test('Suspense (full integration)', async () => {
     const mountedCalls: number[] = []
     const asyncDeps: Promise<any>[] = []
index 4452407c2a8f4541be2450c4403bbd2c210194fd..ddc46c70049e242ccfe0e1ebf493cf680414eb9e 100644 (file)
@@ -1276,7 +1276,7 @@ function baseCreateRenderer(
     const componentUpdateFn = () => {
       if (!instance.isMounted) {
         let vnodeHook: VNodeHook | null | undefined
-        const { el, props } = initialVNode
+        const { el, props, type } = initialVNode
         const { bm, m, parent } = instance
         const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
 
@@ -1325,8 +1325,11 @@ function baseCreateRenderer(
             }
           }
 
-          if (isAsyncWrapperVNode) {
-            ;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
+          if (
+            isAsyncWrapperVNode &&
+            !(type as ComponentOptions).__asyncResolved
+          ) {
+            ;(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