|
@@ -1,6 +1,6 @@
|
|
|
import { ChildrenFlags } from '../flags'
|
|
import { ChildrenFlags } from '../flags'
|
|
|
-import { createComponentVNode, VNodeData } from '../vdom'
|
|
|
|
|
-import { Component, ComponentType, FunctionalComponent } from '../component'
|
|
|
|
|
|
|
+import { createComponentVNode, Slots } from '../vdom'
|
|
|
|
|
+import { Component, ComponentType, ComponentClass } from '../component'
|
|
|
|
|
|
|
|
export interface AsyncComponentFactory {
|
|
export interface AsyncComponentFactory {
|
|
|
(): Promise<ComponentType>
|
|
(): Promise<ComponentType>
|
|
@@ -22,92 +22,90 @@ export type AsyncComponentOptions =
|
|
|
interface AsyncContainerData {
|
|
interface AsyncContainerData {
|
|
|
comp: ComponentType | null
|
|
comp: ComponentType | null
|
|
|
err: Error | null
|
|
err: Error | null
|
|
|
|
|
+ delayed: boolean
|
|
|
timedOut: boolean
|
|
timedOut: boolean
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-interface AsyncContainerProps {
|
|
|
|
|
- options: AsyncComponentFullOptions
|
|
|
|
|
- rawData: VNodeData | null
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-export class AsyncContainer extends Component<
|
|
|
|
|
- AsyncContainerData,
|
|
|
|
|
- AsyncContainerProps
|
|
|
|
|
-> {
|
|
|
|
|
- data() {
|
|
|
|
|
- return {
|
|
|
|
|
- comp: null,
|
|
|
|
|
- err: null,
|
|
|
|
|
- timedOut: false
|
|
|
|
|
- }
|
|
|
|
|
|
|
+export function createAsyncComponent(
|
|
|
|
|
+ options: AsyncComponentOptions
|
|
|
|
|
+): ComponentClass {
|
|
|
|
|
+ if (typeof options === 'function') {
|
|
|
|
|
+ options = { factory: options }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- created() {
|
|
|
|
|
- const { factory, timeout } = this.$props.options
|
|
|
|
|
- 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)
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- render(props: AsyncContainerProps) {
|
|
|
|
|
- if (this.err || (this.timedOut && !this.comp)) {
|
|
|
|
|
- const error =
|
|
|
|
|
- this.err ||
|
|
|
|
|
- new Error(`Async component timed out after ${props.options.timeout}ms.`)
|
|
|
|
|
- const errorComp = props.options.error
|
|
|
|
|
- return errorComp
|
|
|
|
|
- ? createComponentVNode(
|
|
|
|
|
- errorComp,
|
|
|
|
|
- { error },
|
|
|
|
|
- null,
|
|
|
|
|
- ChildrenFlags.NO_CHILDREN
|
|
|
|
|
- )
|
|
|
|
|
- : null
|
|
|
|
|
- } else if (this.comp) {
|
|
|
|
|
- return createComponentVNode(
|
|
|
|
|
- this.comp,
|
|
|
|
|
- props.rawData,
|
|
|
|
|
- null,
|
|
|
|
|
- ChildrenFlags.UNKNOWN_CHILDREN
|
|
|
|
|
- )
|
|
|
|
|
- } else {
|
|
|
|
|
- const loadingComp = props.options.loading
|
|
|
|
|
- return loadingComp
|
|
|
|
|
- ? createComponentVNode(
|
|
|
|
|
- loadingComp,
|
|
|
|
|
- null,
|
|
|
|
|
- null,
|
|
|
|
|
- ChildrenFlags.NO_CHILDREN
|
|
|
|
|
- )
|
|
|
|
|
- : null
|
|
|
|
|
|
|
+ // 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)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-export function createAsyncComponent(
|
|
|
|
|
- options: AsyncComponentOptions
|
|
|
|
|
-): FunctionalComponent {
|
|
|
|
|
- if (typeof options === 'function') {
|
|
|
|
|
- options = { factory: options }
|
|
|
|
|
- }
|
|
|
|
|
- return (_, __, ___, rawData) =>
|
|
|
|
|
- createComponentVNode(
|
|
|
|
|
- AsyncContainer,
|
|
|
|
|
- { options, rawData },
|
|
|
|
|
- null,
|
|
|
|
|
- ChildrenFlags.NO_CHILDREN
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ render(props: any, slots: Slots) {
|
|
|
|
|
+ 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
|
|
|
}
|
|
}
|