88
99import { RuntimeError , RuntimeErrorCode } from '../errors' ;
1010import { Type } from '../interface/type' ;
11-
1211import type { Component } from './directives' ;
1312
13+ let componentResourceResolutionQueue = new Map < Type < any > , Component > ( ) ;
14+
15+ // Track when existing ɵcmp for a Type is waiting on resources.
16+ const componentDefPendingResolution = new Set < Type < any > > ( ) ;
17+
1418/**
1519 * Used to resolve resource URLs on `@Component` when used with JIT compilation.
1620 *
@@ -20,8 +24,7 @@ import type {Component} from './directives';
2024 * selector: 'my-comp',
2125 * templateUrl: 'my-comp.html', // This requires asynchronous resolution
2226 * })
23- * class MyComponent{
24- * }
27+ * class MyComponent { }
2528 *
2629 * // Calling `renderComponent` will fail because `renderComponent` is a synchronous process
2730 * // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously.
@@ -31,90 +34,90 @@ import type {Component} from './directives';
3134 *
3235 * // Use browser's `fetch()` function as the default resource resolution strategy.
3336 * resolveComponentResources(fetch).then(() => {
34- * // After resolution all URLs have been converted into `template` strings.
35- * renderComponent(MyComponent);
37+ * // After resolution all URLs have been converted into `template` strings.
38+ * renderComponent(MyComponent);
3639 * });
3740 *
3841 * ```
3942 *
40- * NOTE: In AOT the resolution happens during compilation, and so there should be no need
43+ * @remarks In AOT the resolution happens during compilation, and so there should be no need
4144 * to call this method outside JIT mode.
4245 *
4346 * @param resourceResolver a function which is responsible for returning a `Promise` to the
4447 * contents of the resolved URL. Browser's `fetch()` method is a good default implementation.
4548 */
46- export function resolveComponentResources (
49+ export async function resolveComponentResources (
4750 resourceResolver : ( url : string ) => Promise < string | { text ( ) : Promise < string > ; status ?: number } > ,
4851) : Promise < void > {
49- // Store all promises which are fetching the resources.
50- const componentResolved : Promise < void > [ ] = [ ] ;
52+ const currentQueue = componentResourceResolutionQueue ;
53+ componentResourceResolutionQueue = new Map ( ) ;
5154
5255 // Cache so that we don't fetch the same resource more than once.
53- const urlMap = new Map < string , Promise < string > > ( ) ;
56+ const urlCache = new Map < string , Promise < string > > ( ) ;
57+
58+ // Helper to dedupe resource fetches
5459 function cachedResourceResolve ( url : string ) : Promise < string > {
55- let promise = urlMap . get ( url ) ;
56- if ( ! promise ) {
57- const resp = resourceResolver ( url ) ;
58- urlMap . set ( url , ( promise = resp . then ( ( res ) => unwrapResponse ( url , res ) ) ) ) ;
60+ const promiseCached = urlCache . get ( url ) ;
61+ if ( promiseCached ) {
62+ return promiseCached ;
5963 }
64+
65+ const promise = resourceResolver ( url ) . then ( ( response ) => unwrapResponse ( url , response ) ) ;
66+ urlCache . set ( url , promise ) ;
67+
6068 return promise ;
6169 }
6270
63- componentResourceResolutionQueue . forEach ( ( component : Component , type : Type < any > ) => {
64- const promises : Promise < void > [ ] = [ ] ;
71+ const resolutionPromises = Array . from ( currentQueue ) . map ( async ( [ type , component ] ) => {
72+ if ( component . styleUrl && component . styleUrls ?. length ) {
73+ throw new Error (
74+ '@Component cannot define both `styleUrl` and `styleUrls`. ' +
75+ 'Use `styleUrl` if the component has one stylesheet, or `styleUrls` if it has multiple' ,
76+ ) ;
77+ }
78+
79+ const componentTasks : Promise < void > [ ] = [ ] ;
80+
6581 if ( component . templateUrl ) {
66- promises . push (
82+ componentTasks . push (
6783 cachedResourceResolve ( component . templateUrl ) . then ( ( template ) => {
6884 component . template = template ;
6985 } ) ,
7086 ) ;
7187 }
88+
7289 const styles =
73- typeof component . styles === 'string' ? [ component . styles ] : component . styles || [ ] ;
90+ typeof component . styles === 'string' ? [ component . styles ] : ( component . styles ?? [ ] ) ;
7491 component . styles = styles ;
7592
76- if ( component . styleUrl && component . styleUrls ?. length ) {
77- throw new Error (
78- '@Component cannot define both `styleUrl` and `styleUrls`. ' +
79- 'Use `styleUrl` if the component has one stylesheet, or `styleUrls` if it has multiple' ,
80- ) ;
81- } else if ( component . styleUrls ?. length ) {
82- const styleOffset = component . styles . length ;
83- const styleUrls = component . styleUrls ;
84- component . styleUrls . forEach ( ( styleUrl , index ) => {
85- styles . push ( '' ) ; // pre-allocate array.
86- promises . push (
87- cachedResourceResolve ( styleUrl ) . then ( ( style ) => {
88- styles [ styleOffset + index ] = style ;
89- styleUrls . splice ( styleUrls . indexOf ( styleUrl ) , 1 ) ;
90- if ( styleUrls . length == 0 ) {
91- component . styleUrls = undefined ;
92- }
93- } ) ,
94- ) ;
95- } ) ;
96- } else if ( component . styleUrl ) {
97- promises . push (
98- cachedResourceResolve ( component . styleUrl ) . then ( ( style ) => {
99- styles . push ( style ) ;
100- component . styleUrl = undefined ;
101- } ) ,
93+ let { styleUrl, styleUrls} = component ;
94+ if ( styleUrl ) {
95+ styleUrls = [ styleUrl ] ;
96+ component . styleUrl = undefined ;
97+ }
98+
99+ if ( styleUrls ?. length ) {
100+ const allFetched = Promise . all ( styleUrls . map ( ( url ) => cachedResourceResolve ( url ) ) ) . then (
101+ ( fetchedStyles ) => {
102+ styles . push ( ...fetchedStyles ) ;
103+ component . styleUrls = undefined ;
104+ } ,
102105 ) ;
106+
107+ componentTasks . push ( allFetched ) ;
103108 }
104109
105- const fullyResolved = Promise . all ( promises ) . then ( ( ) => componentDefResolved ( type ) ) ;
106- componentResolved . push ( fullyResolved ) ;
110+ await Promise . all ( componentTasks ) ;
111+ componentDefPendingResolution . delete ( type ) ;
107112 } ) ;
108- clearResolutionOfComponentResourcesQueue ( ) ;
109- return Promise . all ( componentResolved ) . then ( ( ) => undefined ) ;
110- }
111-
112- let componentResourceResolutionQueue = new Map < Type < any > , Component > ( ) ;
113113
114- // Track when existing ɵcmp for a Type is waiting on resources.
115- const componentDefPendingResolution = new Set < Type < any > > ( ) ;
114+ await Promise . all ( resolutionPromises ) ;
115+ }
116116
117- export function maybeQueueResolutionOfComponentResources ( type : Type < any > , metadata : Component ) {
117+ export function maybeQueueResolutionOfComponentResources (
118+ type : Type < any > ,
119+ metadata : Component ,
120+ ) : void {
118121 if ( componentNeedsResolution ( metadata ) ) {
119122 componentResourceResolutionQueue . set ( type , metadata ) ;
120123 componentDefPendingResolution . add ( type ) ;
@@ -128,10 +131,11 @@ export function isComponentDefPendingResolution(type: Type<any>): boolean {
128131export function componentNeedsResolution ( component : Component ) : boolean {
129132 return ! ! (
130133 ( component . templateUrl && ! component . hasOwnProperty ( 'template' ) ) ||
131- ( component . styleUrls && component . styleUrls . length ) ||
134+ component . styleUrls ?. length ||
132135 component . styleUrl
133136 ) ;
134137}
138+
135139export function clearResolutionOfComponentResourcesQueue ( ) : Map < Type < any > , Component > {
136140 const old = componentResourceResolutionQueue ;
137141 componentResourceResolutionQueue = new Map ( ) ;
@@ -140,32 +144,30 @@ export function clearResolutionOfComponentResourcesQueue(): Map<Type<any>, Compo
140144
141145export function restoreComponentResolutionQueue ( queue : Map < Type < any > , Component > ) : void {
142146 componentDefPendingResolution . clear ( ) ;
143- queue . forEach ( ( _ , type ) => componentDefPendingResolution . add ( type ) ) ;
147+ for ( const type of queue . keys ( ) ) {
148+ componentDefPendingResolution . add ( type ) ;
149+ }
144150 componentResourceResolutionQueue = queue ;
145151}
146152
147- export function isComponentResourceResolutionQueueEmpty ( ) {
153+ export function isComponentResourceResolutionQueueEmpty ( ) : boolean {
148154 return componentResourceResolutionQueue . size === 0 ;
149155}
150156
151- function unwrapResponse (
157+ async function unwrapResponse (
152158 url : string ,
153159 response : string | { text ( ) : Promise < string > ; status ?: number } ,
154- ) : string | Promise < string > {
160+ ) : Promise < string > {
155161 if ( typeof response === 'string' ) {
156162 return response ;
157163 }
164+
158165 if ( response . status !== undefined && response . status !== 200 ) {
159- return Promise . reject (
160- new RuntimeError (
161- RuntimeErrorCode . EXTERNAL_RESOURCE_LOADING_FAILED ,
162- ngDevMode && `Could not load resource: ${ url } . Response status: ${ response . status } ` ,
163- ) ,
166+ throw new RuntimeError (
167+ RuntimeErrorCode . EXTERNAL_RESOURCE_LOADING_FAILED ,
168+ ngDevMode && `Could not load resource: ${ url } . Response status: ${ response . status } ` ,
164169 ) ;
165170 }
166- return response . text ( ) ;
167- }
168171
169- function componentDefResolved ( type : Type < any > ) : void {
170- componentDefPendingResolution . delete ( type ) ;
172+ return response . text ( ) ;
171173}
0 commit comments