/* @flow */ import { warn, once, isDef, isUndef, isTrue, isObject, hasSymbol, isPromise, remove } from 'core/util/index' import { createEmptyVNode } from 'core/vdom/vnode' import { currentRenderingInstance } from 'core/instance/render' 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, context: Component, children: ?Array, tag: ?string ): VNode { const node = createEmptyVNode() node.asyncFactory = factory node.asyncMeta = { data, context, children, tag } return node } export function resolveAsyncComponent ( factory: Function, baseCtor: Class ): Class | 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 = null let timerTimeout = null ;(owner: any).$on('hook:destroyed', () => remove(owners, owner)) const forceRender = (renderCompleted: boolean) => { for (let i = 0, l = owners.length; i < l; i++) { (owners[i]: any).$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 | Class) => { // 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 => { process.env.NODE_ENV !== 'production' && 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 { timerLoading = setTimeout(() => { timerLoading = null if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true forceRender(false) } }, res.delay || 200) } } if (isDef(res.timeout)) { timerTimeout = setTimeout(() => { timerTimeout = null if (isUndef(factory.resolved)) { reject( process.env.NODE_ENV !== 'production' ? `timeout (${res.timeout}ms)` : null ) } }, res.timeout) } } } sync = false // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved } }