2018-09-26 09:28:52 +08:00
|
|
|
import { ChildrenFlags } from '../flags'
|
2018-09-26 10:25:18 +08:00
|
|
|
import { createComponentVNode, Slots } from '../vdom'
|
|
|
|
import { Component, ComponentType, ComponentClass } from '../component'
|
2018-09-26 09:28:52 +08:00
|
|
|
|
|
|
|
export interface AsyncComponentFactory {
|
|
|
|
(): Promise<ComponentType>
|
|
|
|
resolved?: ComponentType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AsyncComponentFullOptions {
|
|
|
|
factory: AsyncComponentFactory
|
|
|
|
loading?: ComponentType
|
|
|
|
error?: ComponentType
|
|
|
|
delay?: number
|
|
|
|
timeout?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AsyncComponentOptions =
|
|
|
|
| AsyncComponentFactory
|
|
|
|
| AsyncComponentFullOptions
|
|
|
|
|
|
|
|
interface AsyncContainerData {
|
|
|
|
comp: ComponentType | null
|
|
|
|
err: Error | null
|
2018-09-26 10:25:18 +08:00
|
|
|
delayed: boolean
|
2018-09-26 09:28:52 +08:00
|
|
|
timedOut: boolean
|
|
|
|
}
|
|
|
|
|
2018-09-26 10:25:18 +08:00
|
|
|
export function createAsyncComponent(
|
|
|
|
options: AsyncComponentOptions
|
|
|
|
): ComponentClass {
|
|
|
|
if (typeof options === 'function') {
|
|
|
|
options = { factory: options }
|
2018-09-26 09:28:52 +08:00
|
|
|
}
|
|
|
|
|
2018-09-26 10:25:18 +08:00
|
|
|
const {
|
|
|
|
factory,
|
|
|
|
timeout,
|
|
|
|
delay = 200,
|
|
|
|
loading: loadingComp,
|
|
|
|
error: errorComp
|
|
|
|
} = options
|
|
|
|
|
|
|
|
return class AsyncContainer extends Component<AsyncContainerData> {
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
comp: null,
|
|
|
|
err: null,
|
|
|
|
delayed: false,
|
|
|
|
timedOut: false
|
|
|
|
}
|
2018-09-26 09:28:52 +08:00
|
|
|
}
|
|
|
|
|
2018-09-26 10:25:18 +08:00
|
|
|
// doing this in beforeMount so this is non-SSR only
|
|
|
|
beforeMount() {
|
|
|
|
if (factory.resolved) {
|
|
|
|
this.comp = factory.resolved
|
|
|
|
} else {
|
|
|
|
factory()
|
|
|
|
.then(resolved => {
|
|
|
|
this.comp = factory.resolved = resolved
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
this.err = err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (timeout != null) {
|
|
|
|
setTimeout(() => {
|
|
|
|
this.timedOut = true
|
|
|
|
}, timeout)
|
|
|
|
}
|
|
|
|
if (delay != null) {
|
|
|
|
this.delayed = true
|
|
|
|
setTimeout(() => {
|
|
|
|
this.delayed = false
|
|
|
|
}, delay)
|
|
|
|
}
|
2018-09-26 09:28:52 +08:00
|
|
|
}
|
|
|
|
|
2018-10-04 01:14:22 +08:00
|
|
|
render(_: any, { props, slots }: { props: any; slots: Slots }) {
|
2018-09-26 10:25:18 +08:00
|
|
|
if (this.err || (this.timedOut && !this.comp)) {
|
|
|
|
const error =
|
|
|
|
this.err || new Error(`Async component timed out after ${timeout}ms.`)
|
|
|
|
return errorComp
|
|
|
|
? createComponentVNode(
|
|
|
|
errorComp,
|
|
|
|
{ error },
|
|
|
|
null,
|
|
|
|
ChildrenFlags.NO_CHILDREN
|
|
|
|
)
|
|
|
|
: null
|
|
|
|
} else if (this.comp) {
|
|
|
|
return createComponentVNode(
|
|
|
|
this.comp,
|
|
|
|
props,
|
|
|
|
slots,
|
|
|
|
ChildrenFlags.STABLE_SLOTS
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return loadingComp && !this.delayed
|
|
|
|
? createComponentVNode(
|
|
|
|
loadingComp,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
ChildrenFlags.NO_CHILDREN
|
|
|
|
)
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} as ComponentClass
|
2018-09-26 09:28:52 +08:00
|
|
|
}
|