@@ -12,7 +12,7 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
12
12
import { type VNode , createVNode } from './vnode'
13
13
import { defineComponent } from './apiDefineComponent'
14
14
import { warn } from './warning'
15
- import { ref } from '@vue/reactivity'
15
+ import { type Ref , ref } from '@vue/reactivity'
16
16
import { ErrorCodes , handleError } from './errorHandling'
17
17
import { isKeepAlive } from './components/KeepAlive'
18
18
import { markAsyncBoundary } from './helpers/useId'
@@ -24,10 +24,10 @@ export type AsyncComponentLoader<T = any> = () => Promise<
24
24
AsyncComponentResolveResult < T >
25
25
>
26
26
27
- export interface AsyncComponentOptions < T = any > {
27
+ export interface AsyncComponentOptions < T = any , C = any > {
28
28
loader : AsyncComponentLoader < T >
29
- loadingComponent ?: Component
30
- errorComponent ?: Component
29
+ loadingComponent ?: C
30
+ errorComponent ?: C
31
31
delay ?: number
32
32
timeout ?: number
33
33
suspensible ?: boolean
@@ -46,75 +46,20 @@ export const isAsyncWrapper = (i: GenericComponentInstance | VNode): boolean =>
46
46
/*! #__NO_SIDE_EFFECTS__ */
47
47
export function defineAsyncComponent <
48
48
T extends Component = { new ( ) : ComponentPublicInstance } ,
49
- > ( source : AsyncComponentLoader < T > | AsyncComponentOptions < T > ) : T {
50
- if ( isFunction ( source ) ) {
51
- source = { loader : source }
52
- }
53
-
49
+ > ( source : AsyncComponentLoader < T > | AsyncComponentOptions < T , Component > ) : T {
54
50
const {
55
- loader,
56
- loadingComponent,
57
- errorComponent,
58
- delay = 200 ,
59
- hydrate : hydrateStrategy ,
60
- timeout, // undefined = never times out
61
- suspensible = true ,
62
- onError : userOnError ,
63
- } = source
64
-
65
- let pendingRequest : Promise < ConcreteComponent > | null = null
66
- let resolvedComp : ConcreteComponent | undefined
67
-
68
- let retries = 0
69
- const retry = ( ) => {
70
- retries ++
71
- pendingRequest = null
72
- return load ( )
73
- }
74
-
75
- const load = ( ) : Promise < ConcreteComponent > => {
76
- let thisRequest : Promise < ConcreteComponent >
77
- return (
78
- pendingRequest ||
79
- ( thisRequest = pendingRequest =
80
- loader ( )
81
- . catch ( err => {
82
- err = err instanceof Error ? err : new Error ( String ( err ) )
83
- if ( userOnError ) {
84
- return new Promise ( ( resolve , reject ) => {
85
- const userRetry = ( ) => resolve ( retry ( ) )
86
- const userFail = ( ) => reject ( err )
87
- userOnError ( err , userRetry , userFail , retries + 1 )
88
- } )
89
- } else {
90
- throw err
91
- }
92
- } )
93
- . then ( ( comp : any ) => {
94
- if ( thisRequest !== pendingRequest && pendingRequest ) {
95
- return pendingRequest
96
- }
97
- if ( __DEV__ && ! comp ) {
98
- warn (
99
- `Async component loader resolved to undefined. ` +
100
- `If you are using retry(), make sure to return its return value.` ,
101
- )
102
- }
103
- // interop module default
104
- if (
105
- comp &&
106
- ( comp . __esModule || comp [ Symbol . toStringTag ] === 'Module' )
107
- ) {
108
- comp = comp . default
109
- }
110
- if ( __DEV__ && comp && ! isObject ( comp ) && ! isFunction ( comp ) ) {
111
- throw new Error ( `Invalid async component load result: ${ comp } ` )
112
- }
113
- resolvedComp = comp
114
- return comp
115
- } ) )
116
- )
117
- }
51
+ load,
52
+ getResolvedComp,
53
+ setPendingRequest,
54
+ source : {
55
+ loadingComponent,
56
+ errorComponent,
57
+ delay,
58
+ hydrate : hydrateStrategy ,
59
+ timeout,
60
+ suspensible = true ,
61
+ } ,
62
+ } = createAsyncComponentContext ( source )
118
63
119
64
return defineComponent ( {
120
65
name : 'AsyncComponentWrapper' ,
@@ -132,28 +77,29 @@ export function defineAsyncComponent<
132
77
}
133
78
}
134
79
: hydrate
135
- if ( resolvedComp ) {
80
+ if ( getResolvedComp ( ) ) {
136
81
doHydrate ( )
137
82
} else {
138
83
load ( ) . then ( ( ) => ! instance . isUnmounted && doHydrate ( ) )
139
84
}
140
85
} ,
141
86
142
87
get __asyncResolved ( ) {
143
- return resolvedComp
88
+ return getResolvedComp ( )
144
89
} ,
145
90
146
91
setup ( ) {
147
92
const instance = currentInstance as ComponentInternalInstance
148
93
markAsyncBoundary ( instance )
149
94
150
95
// already resolved
96
+ let resolvedComp = getResolvedComp ( )
151
97
if ( resolvedComp ) {
152
98
return ( ) => createInnerComp ( resolvedComp ! , instance )
153
99
}
154
100
155
101
const onError = ( err : Error ) => {
156
- pendingRequest = null
102
+ setPendingRequest ( null )
157
103
handleError (
158
104
err ,
159
105
instance ,
@@ -182,27 +128,11 @@ export function defineAsyncComponent<
182
128
} )
183
129
}
184
130
185
- const loaded = ref ( false )
186
- const error = ref ( )
187
- const delayed = ref ( ! ! delay )
188
-
189
- if ( delay ) {
190
- setTimeout ( ( ) => {
191
- delayed . value = false
192
- } , delay )
193
- }
194
-
195
- if ( timeout != null ) {
196
- setTimeout ( ( ) => {
197
- if ( ! loaded . value && ! error . value ) {
198
- const err = new Error (
199
- `Async component timed out after ${ timeout } ms.` ,
200
- )
201
- onError ( err )
202
- error . value = err
203
- }
204
- } , timeout )
205
- }
131
+ const { loaded, error, delayed } = useAsyncComponentState (
132
+ delay ,
133
+ timeout ,
134
+ onError ,
135
+ )
206
136
207
137
load ( )
208
138
. then ( ( ) => {
@@ -223,6 +153,7 @@ export function defineAsyncComponent<
223
153
} )
224
154
225
155
return ( ) => {
156
+ resolvedComp = getResolvedComp ( )
226
157
if ( loaded . value && resolvedComp ) {
227
158
return createInnerComp ( resolvedComp , instance )
228
159
} else if ( error . value && errorComponent ) {
@@ -252,3 +183,114 @@ function createInnerComp(
252
183
253
184
return vnode
254
185
}
186
+
187
+ type AsyncComponentContext < T , C = ConcreteComponent > = {
188
+ load : ( ) => Promise < C >
189
+ source : AsyncComponentOptions < T >
190
+ getResolvedComp : ( ) => C | undefined
191
+ setPendingRequest : ( request : Promise < C > | null ) => void
192
+ }
193
+
194
+ // shared between core and vapor
195
+ export function createAsyncComponentContext < T , C = ConcreteComponent > (
196
+ source : AsyncComponentLoader < T > | AsyncComponentOptions < T > ,
197
+ ) : AsyncComponentContext < T , C > {
198
+ if ( isFunction ( source ) ) {
199
+ source = { loader : source }
200
+ }
201
+
202
+ const { loader, onError : userOnError } = source
203
+ let pendingRequest : Promise < C > | null = null
204
+ let resolvedComp : C | undefined
205
+
206
+ let retries = 0
207
+ const retry = ( ) => {
208
+ retries ++
209
+ pendingRequest = null
210
+ return load ( )
211
+ }
212
+
213
+ const load = ( ) : Promise < C > => {
214
+ let thisRequest : Promise < C >
215
+ return (
216
+ pendingRequest ||
217
+ ( thisRequest = pendingRequest =
218
+ loader ( )
219
+ . catch ( err => {
220
+ err = err instanceof Error ? err : new Error ( String ( err ) )
221
+ if ( userOnError ) {
222
+ return new Promise ( ( resolve , reject ) => {
223
+ const userRetry = ( ) => resolve ( retry ( ) )
224
+ const userFail = ( ) => reject ( err )
225
+ userOnError ( err , userRetry , userFail , retries + 1 )
226
+ } )
227
+ } else {
228
+ throw err
229
+ }
230
+ } )
231
+ . then ( ( comp : any ) => {
232
+ if ( thisRequest !== pendingRequest && pendingRequest ) {
233
+ return pendingRequest
234
+ }
235
+ if ( __DEV__ && ! comp ) {
236
+ warn (
237
+ `Async component loader resolved to undefined. ` +
238
+ `If you are using retry(), make sure to return its return value.` ,
239
+ )
240
+ }
241
+ if (
242
+ comp &&
243
+ ( comp . __esModule || comp [ Symbol . toStringTag ] === 'Module' )
244
+ ) {
245
+ comp = comp . default
246
+ }
247
+ if ( __DEV__ && comp && ! isObject ( comp ) && ! isFunction ( comp ) ) {
248
+ throw new Error ( `Invalid async component load result: ${ comp } ` )
249
+ }
250
+ resolvedComp = comp
251
+ return comp
252
+ } ) )
253
+ )
254
+ }
255
+
256
+ return {
257
+ load,
258
+ source,
259
+ getResolvedComp : ( ) => resolvedComp ,
260
+ setPendingRequest : ( request : Promise < C > | null ) =>
261
+ ( pendingRequest = request ) ,
262
+ }
263
+ }
264
+
265
+ // shared between core and vapor
266
+ export const useAsyncComponentState = (
267
+ delay : number | undefined ,
268
+ timeout : number | undefined ,
269
+ onError : ( err : Error ) => void ,
270
+ ) : {
271
+ loaded : Ref < boolean >
272
+ error : Ref < Error | undefined >
273
+ delayed : Ref < boolean >
274
+ } => {
275
+ const loaded = ref ( false )
276
+ const error = ref ( )
277
+ const delayed = ref ( ! ! delay )
278
+
279
+ if ( delay ) {
280
+ setTimeout ( ( ) => {
281
+ delayed . value = false
282
+ } , delay )
283
+ }
284
+
285
+ if ( timeout != null ) {
286
+ setTimeout ( ( ) => {
287
+ if ( ! loaded . value && ! error . value ) {
288
+ const err = new Error ( `Async component timed out after ${ timeout } ms.` )
289
+ onError ( err )
290
+ error . value = err
291
+ }
292
+ } , timeout )
293
+ }
294
+
295
+ return { loaded, error, delayed }
296
+ }
0 commit comments