@@ -25,7 +25,6 @@ const ResourceLoader = (() => {
2525 }
2626 }
2727
28- // Function to categorize errors for better logging
2928 function categorizeError ( error , fileType , url ) {
3029 if ( error . name === "AbortError" ) {
3130 return { type : "abort" , message : `Fetch aborted for: ${ url } ` } ;
@@ -61,9 +60,7 @@ const ResourceLoader = (() => {
6160 }
6261 }
6362
64- // Validate crossorigin and integrity attributes for security-sensitive resources
6563 function validateSecurityAttributes ( element , fileType , attributes ) {
66- // Cross-origin validation
6764 if ( fileType === "js" || fileType === "css" ) {
6865 if (
6966 attributes . crossorigin &&
@@ -77,7 +74,6 @@ const ResourceLoader = (() => {
7774 }
7875 }
7976
80- // Integrity validation for JS and CSS files
8177 if ( fileType === "js" || fileType === "css" ) {
8278 if ( ! attributes . integrity ) {
8379 log (
@@ -102,11 +98,10 @@ const ResourceLoader = (() => {
10298 }
10399 } ) ;
104100
105- // Apply security-related attributes after general attributes
106101 validateSecurityAttributes ( element , fileType , attributes ) ;
107102 }
108103
109- async function include ( urls , options = { } ) {
104+ function include ( urls , options = { } ) {
110105 if ( ! Array . isArray ( urls ) ) {
111106 urls = [ urls ] ;
112107 }
@@ -122,17 +117,18 @@ const ResourceLoader = (() => {
122117 crossorigin = false ,
123118 logLevel = "warn" ,
124119 onError = null ,
120+ onSuccess = null ,
125121 retries = 0 ,
126122 retryDelay = 1000 ,
127123 deferScriptsUntilReady = true ,
128124 batchSize = 5 ,
129125 maxConcurrency = 3 ,
130- priority = 0 , // New option for resource priority
126+ priority = 0 ,
127+ removeFailedElements = true ,
131128 } = options ;
132129
133130 setLoggingLevel ( logLevel ) ;
134131
135- // Sort resources by priority (higher priority resources load first)
136132 const sortedUrls = urls . sort ( ( a , b ) => {
137133 const priorityA = a . priority || 0 ;
138134 const priorityB = b . priority || 0 ;
@@ -200,7 +196,11 @@ const ResourceLoader = (() => {
200196 }
201197 if ( retryCount < retries ) {
202198 log ( `Retrying to load resource: ${ finalUrl } ` , "warn" ) ;
203- setTimeout ( ( ) => loadResource ( url , retryCount + 1 ) , retryDelay ) ;
199+ setTimeout ( ( ) => {
200+ loadResource ( url , retryCount + 1 )
201+ . then ( resolve )
202+ . catch ( reject ) ;
203+ } , retryDelay ) ;
204204 }
205205 } ;
206206
@@ -237,13 +237,14 @@ const ResourceLoader = (() => {
237237 finalUrl
238238 ) ;
239239 reject ( categorizedError ) ;
240- if ( onError ) onError ( categorizedError ) ;
240+ if ( onError ) onError ( categorizedError , url ) ;
241241 if ( retryCount < retries ) {
242242 log ( `Retrying to load resource: ${ finalUrl } ` , "warn" ) ;
243- setTimeout (
244- ( ) => loadResource ( url , retryCount + 1 ) ,
245- retryDelay
246- ) ;
243+ setTimeout ( ( ) => {
244+ loadResource ( url , retryCount + 1 )
245+ . then ( resolve )
246+ . catch ( reject ) ;
247+ } , retryDelay ) ;
247248 }
248249 } ) ;
249250 cancel = ( ) => controller . abort ( ) ;
@@ -284,13 +285,14 @@ const ResourceLoader = (() => {
284285 finalUrl
285286 ) ;
286287 reject ( categorizedError ) ;
287- if ( onError ) onError ( categorizedError ) ;
288+ if ( onError ) onError ( categorizedError , url ) ;
288289 if ( retryCount < retries ) {
289290 log ( `Retrying to load resource: ${ finalUrl } ` , "warn" ) ;
290- setTimeout (
291- ( ) => loadResource ( url , retryCount + 1 ) ,
292- retryDelay
293- ) ;
291+ setTimeout ( ( ) => {
292+ loadResource ( url , retryCount + 1 )
293+ . then ( resolve )
294+ . catch ( reject ) ;
295+ } , retryDelay ) ;
294296 }
295297 } ) ;
296298 return ;
@@ -312,23 +314,26 @@ const ResourceLoader = (() => {
312314 finalUrl
313315 ) ;
314316 reject ( categorizedError ) ;
315- if ( onError ) onError ( categorizedError ) ;
317+ if ( onError ) onError ( categorizedError , url ) ;
316318 if ( retryCount < retries ) {
317319 log ( `Retrying to load resource: ${ finalUrl } ` , "warn" ) ;
318- setTimeout (
319- ( ) => loadResource ( url , retryCount + 1 ) ,
320- retryDelay
321- ) ;
320+ setTimeout ( ( ) => {
321+ loadResource ( url , retryCount + 1 )
322+ . then ( resolve )
323+ . catch ( reject ) ;
324+ } , retryDelay ) ;
322325 }
323326 } ) ;
324327 cancel = ( ) => controller . abort ( ) ;
325328 return ;
326329 default :
327- reject ( new Error ( `Unsupported file type: ${ fileType } ` ) ) ;
330+ const error = new Error ( `Unsupported file type: ${ fileType } ` ) ;
331+ reject ( error ) ;
328332 log (
329333 `Failed to load unsupported file type: ${ finalUrl } ` ,
330334 "error"
331335 ) ;
336+ return ;
332337 }
333338
334339 applyAttributes ( element , attributes , fileType ) ;
@@ -338,38 +343,46 @@ const ResourceLoader = (() => {
338343 element . onload = ( ) => {
339344 if ( ! timedOut ) {
340345 clearTimeout ( timeoutId ) ;
341-
342- // Check if resource was really loaded (some resources may trigger onload without being valid)
343- if (
344- element . naturalWidth === 0 ||
345- element . readyState === "complete"
346- ) {
347- log ( `Resource loaded from: ${ finalUrl } ` , "verbose" ) ;
348- resourceStates [ url ] = "loaded" ;
349- resolve ( ) ;
350- } else {
351- log ( `Failed to load resource from: ${ finalUrl } ` , "error" ) ;
352- reject ( new Error ( `Failed to load resource: ${ finalUrl } ` ) ) ;
353- }
346+ log ( `Resource loaded from: ${ finalUrl } ` , "verbose" ) ;
347+ resourceStates [ url ] = "loaded" ;
348+ resolve ( ) ;
349+ if ( onSuccess ) onSuccess ( url ) ;
354350 }
355351 } ;
356352
357353 element . onerror = ( ) => {
358354 clearTimeout ( timeoutId ) ;
355+
359356 const loadError = new Error (
360357 `Failed to load resource from: ${ finalUrl } `
361358 ) ;
359+
362360 const categorizedError = categorizeError (
363361 loadError ,
364362 fileType ,
365363 finalUrl
366364 ) ;
365+
367366 reject ( categorizedError ) ;
367+
368368 log ( `Failed to load resource from: ${ finalUrl } ` , "warn" ) ;
369+
369370 resourceStates [ url ] = "unloaded" ;
371+
372+ if ( removeFailedElements && element && element . parentNode ) {
373+ element . parentNode . removeChild ( element ) ;
374+ log ( `Removed failed element: ${ finalUrl } ` , "verbose" ) ;
375+ }
376+
377+ if ( onError ) onError ( categorizedError , url ) ;
378+
370379 if ( retryCount < retries ) {
371380 log ( `Retrying to load resource: ${ finalUrl } ` , "warn" ) ;
372- setTimeout ( ( ) => loadResource ( url , retryCount + 1 ) , retryDelay ) ;
381+ setTimeout ( ( ) => {
382+ loadResource ( url , retryCount + 1 )
383+ . then ( resolve )
384+ . catch ( reject ) ;
385+ } , retryDelay ) ;
373386 }
374387 } ;
375388
@@ -409,37 +422,51 @@ const ResourceLoader = (() => {
409422 } else {
410423 loadScriptWhenReady ( ) ;
411424 }
412- } ) . catch ( ( err ) => {
413- log ( `Error loading resource: ${ url } ` , "warn" ) ;
414- return Promise . resolve ( ) ;
415425 } ) ,
416426 cancel,
417427 } ) . promise ;
418428 } ;
419429
420- const loadWithConcurrencyLimit = async (
421- resources ,
422- loadFn ,
423- maxConcurrency
424- ) => {
425- let active = 0 ;
430+ function loadWithConcurrencyLimit ( resources , loadFn , maxConcurrency ) {
426431 let index = 0 ;
432+ const results = [ ] ;
433+ const total = resources . length ;
434+
435+ return new Promise ( ( resolve ) => {
436+ const startNext = ( ) => {
437+ if ( index >= total ) {
438+ if ( results . length === total ) {
439+ resolve ( results ) ;
440+ }
441+ return ;
442+ }
427443
428- const processNext = async ( ) => {
429- while ( active < maxConcurrency && index < resources . length ) {
430- const currentUrl = resources [ index ++ ] ;
431- active ++ ;
432- loadFn ( currentUrl ) . finally ( ( ) => {
433- active -- ;
434- processNext ( ) ;
435- } ) ;
436- }
437- } ;
444+ const currentIndex = index ++ ;
445+ const url = resources [ currentIndex ] ;
446+
447+ loadFn ( url )
448+ . then ( ( ) => {
449+ results [ currentIndex ] = { status : "fulfilled" , value : url } ;
450+ startNext ( ) ;
451+ } )
452+ . catch ( ( error ) => {
453+ results [ currentIndex ] = {
454+ status : "rejected" ,
455+ reason : error ,
456+ url,
457+ } ;
458+ startNext ( ) ;
459+ } ) ;
460+ } ;
438461
439- await processNext ( ) ;
440- } ;
462+ // Start initial batch
463+ for ( let i = 0 ; i < Math . min ( maxConcurrency , total ) ; i ++ ) {
464+ startNext ( ) ;
465+ }
466+ } ) ;
467+ }
441468
442- await loadWithConcurrencyLimit ( urls , loadResource , maxConcurrency ) ;
469+ return loadWithConcurrencyLimit ( sortedUrls , loadResource , maxConcurrency ) ;
443470 }
444471
445472 function unloadResource ( url ) {
0 commit comments