Просмотр исходного кода

refactor(runtime-vapor): extract async hydration logic for tree-shaking (#14318)

edison 3 месяцев назад
Родитель
Сommit
c89316054f

+ 109 - 45
packages/runtime-vapor/src/apiDefineAsyncComponent.ts

@@ -24,10 +24,108 @@ import {
   isHydrating,
   locateEndAnchor,
   removeFragmentNodes,
+  setCurrentHydrationNode,
 } from './dom/hydration'
-import { invokeArrayFns } from '@vue/shared'
 import { type TransitionOptions, insert, remove } from './block'
-import { parentNode } from './dom/node'
+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>(
@@ -59,49 +157,15 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
       // not the actual hydrate function
       hydrate: () => void,
     ) {
-      // 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,
-      )
+      asyncHydrate &&
+        asyncHydrate(
+          el,
+          instance,
+          hydrate,
+          load,
+          getResolvedComp,
+          hydrateStrategy,
+        )
     },
 
     get __asyncResolved() {

+ 2 - 31
packages/runtime-vapor/src/component.ts

@@ -93,14 +93,12 @@ import {
   adoptTemplate,
   advanceHydrationNode,
   currentHydrationNode,
-  isComment,
   isHydrating,
-  locateEndAnchor,
   locateHydrationNode,
   locateNextNode,
   setCurrentHydrationNode,
 } from './dom/hydration'
-import { _next, createComment, createElement, createTextNode } from './dom/node'
+import { createComment, createElement, createTextNode } from './dom/node'
 import {
   type TeleportFragment,
   isTeleportFragment,
@@ -368,34 +366,7 @@ export function createComponent(
     component.__asyncHydrate &&
     !component.__asyncResolved
   ) {
-    // it may get unmounted before its inner component is loaded,
-    // so we need to give it a placeholder block that matches its
-    // adopted DOM
-    const el = currentHydrationNode!
-    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
-    }
-    // also mark it 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,
-    )
-    component.__asyncHydrate(el as Element, instance, () =>
+    component.__asyncHydrate(currentHydrationNode as Element, instance, () =>
       setupComponent(instance, component),
     )
   } else {

+ 2 - 0
packages/runtime-vapor/src/dom/hydration.ts

@@ -24,6 +24,7 @@ import {
 } from './node'
 import { findBlockNode, remove } from '../block'
 import type { DynamicFragment } from '../fragment'
+import { registerAsyncHydrateImpl } from '../apiDefineAsyncComponent'
 
 export let currentHydrationNode: Node | null = null
 
@@ -60,6 +61,7 @@ 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