import {
|
warn,
|
once,
|
isDef,
|
isUndef,
|
isTrue,
|
isObject,
|
hasSymbol,
|
isPromise,
|
remove
|
} from 'core/util/index'
|
|
import VNode, { createEmptyVNode } from 'core/vdom/vnode'
|
import { currentRenderingInstance } from 'core/instance/render'
|
import type { VNodeData } from 'types/vnode'
|
import type { Component } from 'types/component'
|
|
function ensureCtor(comp: any, base) {
|
if (comp.__esModule || (hasSymbol && comp[Symbol.toStringTag] === 'Module')) {
|
comp = comp.default
|
}
|
return isObject(comp) ? base.extend(comp) : comp
|
}
|
|
export function createAsyncPlaceholder(
|
factory: Function,
|
data: VNodeData | undefined,
|
context: Component,
|
children: Array<VNode> | undefined,
|
tag?: string
|
): VNode {
|
const node = createEmptyVNode()
|
node.asyncFactory = factory
|
node.asyncMeta = { data, context, children, tag }
|
return node
|
}
|
|
export function resolveAsyncComponent(
|
factory: { (...args: any[]): any; [keye: string]: any },
|
baseCtor: typeof Component
|
): typeof Component | void {
|
if (isTrue(factory.error) && isDef(factory.errorComp)) {
|
return factory.errorComp
|
}
|
|
if (isDef(factory.resolved)) {
|
return factory.resolved
|
}
|
|
const owner = currentRenderingInstance
|
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
|
// already pending
|
factory.owners.push(owner)
|
}
|
|
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
|
return factory.loadingComp
|
}
|
|
if (owner && !isDef(factory.owners)) {
|
const owners = (factory.owners = [owner])
|
let sync = true
|
let timerLoading: number | null = null
|
let timerTimeout: number | null = null
|
|
owner.$on('hook:destroyed', () => remove(owners, owner))
|
|
const forceRender = (renderCompleted: boolean) => {
|
for (let i = 0, l = owners.length; i < l; i++) {
|
owners[i].$forceUpdate()
|
}
|
|
if (renderCompleted) {
|
owners.length = 0
|
if (timerLoading !== null) {
|
clearTimeout(timerLoading)
|
timerLoading = null
|
}
|
if (timerTimeout !== null) {
|
clearTimeout(timerTimeout)
|
timerTimeout = null
|
}
|
}
|
}
|
|
const resolve = once((res: Object | Component) => {
|
// cache resolved
|
factory.resolved = ensureCtor(res, baseCtor)
|
// invoke callbacks only if this is not a synchronous resolve
|
// (async resolves are shimmed as synchronous during SSR)
|
if (!sync) {
|
forceRender(true)
|
} else {
|
owners.length = 0
|
}
|
})
|
|
const reject = once(reason => {
|
__DEV__ &&
|
warn(
|
`Failed to resolve async component: ${String(factory)}` +
|
(reason ? `\nReason: ${reason}` : '')
|
)
|
if (isDef(factory.errorComp)) {
|
factory.error = true
|
forceRender(true)
|
}
|
})
|
|
const res = factory(resolve, reject)
|
|
if (isObject(res)) {
|
if (isPromise(res)) {
|
// () => Promise
|
if (isUndef(factory.resolved)) {
|
res.then(resolve, reject)
|
}
|
} else if (isPromise(res.component)) {
|
res.component.then(resolve, reject)
|
|
if (isDef(res.error)) {
|
factory.errorComp = ensureCtor(res.error, baseCtor)
|
}
|
|
if (isDef(res.loading)) {
|
factory.loadingComp = ensureCtor(res.loading, baseCtor)
|
if (res.delay === 0) {
|
factory.loading = true
|
} else {
|
// @ts-expect-error NodeJS timeout type
|
timerLoading = setTimeout(() => {
|
timerLoading = null
|
if (isUndef(factory.resolved) && isUndef(factory.error)) {
|
factory.loading = true
|
forceRender(false)
|
}
|
}, res.delay || 200)
|
}
|
}
|
|
if (isDef(res.timeout)) {
|
// @ts-expect-error NodeJS timeout type
|
timerTimeout = setTimeout(() => {
|
timerTimeout = null
|
if (isUndef(factory.resolved)) {
|
reject(__DEV__ ? `timeout (${res.timeout}ms)` : null)
|
}
|
}, res.timeout)
|
}
|
}
|
}
|
|
sync = false
|
// return in case resolved synchronously
|
return factory.loading ? factory.loadingComp : factory.resolved
|
}
|
}
|