|
|
@@ -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() {
|