]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(runtime-vapor): inline async hydration logic (#14320)
authoredison <daiwei521@126.com>
Thu, 15 Jan 2026 03:42:25 +0000 (11:42 +0800)
committerGitHub <noreply@github.com>
Thu, 15 Jan 2026 03:42:25 +0000 (11:42 +0800)
packages/runtime-vapor/src/apiDefineAsyncComponent.ts
packages/runtime-vapor/src/dom/hydration.ts

index 91e04c7373483c5bdc672465d2e28cad999cde29..b5408f747f8935420670a15e818cb536fc3cec41 100644 (file)
@@ -30,103 +30,6 @@ import { type TransitionOptions, insert, remove } from './block'
 import { _next, parentNode } from './dom/node'
 import { invokeArrayFns } from '@vue/shared'
 
-export type AsyncHydrateImplFn = (
-  el: Element,
-  instance: VaporComponentInstance,
-  hydrate: () => void,
-  load: () => Promise<VaporComponent>,
-  getResolvedComp: () => VaporComponent | undefined,
-  hydrateStrategy: any,
-) => void
-
-let asyncHydrate: AsyncHydrateImplFn | undefined
-
-// Register async hydrate implementation for tree-shaking
-export function registerAsyncHydrateImpl(): void {
-  asyncHydrate = asyncHydrateImpl
-}
-
-const asyncHydrateImpl: AsyncHydrateImplFn = (
-  el: Element,
-  instance: VaporComponentInstance,
-  hydrate: () => void,
-  load: () => Promise<VaporComponent>,
-  getResolvedComp: () => VaporComponent | undefined,
-  hydrateStrategy: any,
-) => {
-  // Create placeholder block that matches the adopted DOM.
-  // The async component may get unmounted before its inner component is loaded,
-  // so we need to give it a placeholder block.
-  if (isComment(el, '[')) {
-    const end = _next(locateEndAnchor(el)!)
-    const block = (instance.block = [el as Node])
-    let cur = el as Node
-    while (true) {
-      let n = _next(cur)
-      if (n && n !== end) {
-        block.push((cur = n))
-      } else {
-        break
-      }
-    }
-  } else {
-    instance.block = el
-  }
-
-  // Mark as mounted to ensure it can be unmounted before
-  // its inner component is resolved
-  instance.isMounted = true
-
-  // Advance current hydration node to the nextSibling
-  setCurrentHydrationNode(
-    isComment(el, '[') ? locateEndAnchor(el)! : el.nextSibling,
-  )
-
-  // If async component needs to be updated before hydration, hydration is no longer needed.
-  let isHydrated = false
-  watch(
-    () => instance.attrs,
-    () => {
-      // early return if already hydrated
-      if (isHydrated) return
-
-      // call the beforeUpdate hook to avoid calling hydrate in performAsyncHydrate
-      instance.bu && invokeArrayFns(instance.bu)
-
-      // mount the inner component and remove the placeholder
-      const parent = parentNode(el)!
-      load().then(() => {
-        if (instance.isUnmounted) return
-        hydrate()
-        if (isComment(el, '[')) {
-          const endAnchor = locateEndAnchor(el)!
-          removeFragmentNodes(el, endAnchor)
-          insert(instance.block, parent, endAnchor)
-        } else {
-          insert(instance.block, parent, el)
-          remove(el, parent)
-        }
-      })
-    },
-    { deep: true, once: true },
-  )
-
-  performAsyncHydrate(
-    el,
-    instance,
-    () => {
-      hydrateNode(el, () => {
-        hydrate()
-        insert(instance.block, parentNode(el)!, el)
-        isHydrated = true
-      })
-    },
-    getResolvedComp,
-    load,
-    hydrateStrategy,
-  )
-}
-
 /*@ __NO_SIDE_EFFECTS__ */
 export function defineVaporAsyncComponent<T extends VaporComponent>(
   source: AsyncComponentLoader<T> | AsyncComponentOptions<T>,
@@ -157,15 +60,80 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
       // not the actual hydrate function
       hydrate: () => void,
     ) {
-      asyncHydrate &&
-        asyncHydrate(
-          el,
-          instance,
-          hydrate,
-          load,
-          getResolvedComp,
-          hydrateStrategy,
-        )
+      // early return allows tree-shaking of hydration logic when not used
+      if (!isHydrating) return
+
+      // Create placeholder block that matches the adopted DOM.
+      // The async component may get unmounted before its inner component is loaded,
+      // so we need to give it a placeholder block.
+      if (isComment(el, '[')) {
+        const end = _next(locateEndAnchor(el)!)
+        const block = (instance.block = [el as Node])
+        let cur = el as Node
+        while (true) {
+          let n = _next(cur)
+          if (n && n !== end) {
+            block.push((cur = n))
+          } else {
+            break
+          }
+        }
+      } else {
+        instance.block = el
+      }
+
+      // Mark as mounted to ensure it can be unmounted before
+      // its inner component is resolved
+      instance.isMounted = true
+
+      // Advance current hydration node to the nextSibling
+      setCurrentHydrationNode(
+        isComment(el, '[') ? locateEndAnchor(el)! : el.nextSibling,
+      )
+
+      // If async component needs to be updated before hydration, hydration is no longer needed.
+      let isHydrated = false
+      watch(
+        () => instance.attrs,
+        () => {
+          // early return if already hydrated
+          if (isHydrated) return
+
+          // call the beforeUpdate hook to avoid calling hydrate in performAsyncHydrate
+          instance.bu && invokeArrayFns(instance.bu)
+
+          // mount the inner component and remove the placeholder
+          const parent = parentNode(el)!
+          load().then(() => {
+            if (instance.isUnmounted) return
+            hydrate()
+            if (isComment(el, '[')) {
+              const endAnchor = locateEndAnchor(el)!
+              removeFragmentNodes(el, endAnchor)
+              insert(instance.block, parent, endAnchor)
+            } else {
+              insert(instance.block, parent, el)
+              remove(el, parent)
+            }
+          })
+        },
+        { deep: true, once: true },
+      )
+
+      performAsyncHydrate(
+        el,
+        instance,
+        () => {
+          hydrateNode(el, () => {
+            hydrate()
+            insert(instance.block, parentNode(el)!, el)
+            isHydrated = true
+          })
+        },
+        getResolvedComp,
+        load,
+        hydrateStrategy,
+      )
     },
 
     get __asyncResolved() {
index 63fc4f151935d2f53e891005674301a002226a81..6214904e3e68e7ee45ba629a495a1956184de157 100644 (file)
@@ -24,7 +24,6 @@ import {
 } from './node'
 import { findBlockNode, remove } from '../block'
 import type { DynamicFragment } from '../fragment'
-import { registerAsyncHydrateImpl } from '../apiDefineAsyncComponent'
 
 export let currentHydrationNode: Node | null = null
 
@@ -61,7 +60,6 @@ function performHydration<T>(
     adoptTemplate = adoptTemplateImpl
     locateHydrationNode = locateHydrationNodeImpl
     _hydrateDynamicFragment = hydrateDynamicFragmentImpl
-    registerAsyncHydrateImpl()
     // optimize anchor cache lookup
     ;(Comment.prototype as any).$fe = undefined
     ;(Node.prototype as any).$pns = undefined