@@ -110,6 +110,110 @@ interface AngularRouterConfigResult {
110
110
111
111
type EntryPointToBrowserMapping = AngularAppManifest [ 'entryPointToBrowserMapping' ] ;
112
112
113
+ /**
114
+ * Handles a single route within the route tree and yields metadata or errors.
115
+ *
116
+ * @param options - Configuration options for handling the route.
117
+ * @returns An async iterable iterator yielding `RouteTreeNodeMetadata` or an error object.
118
+ */
119
+ async function * handleRoute ( options : {
120
+ metadata : ServerConfigRouteTreeNodeMetadata ;
121
+ currentRoutePath : string ;
122
+ route : Route ;
123
+ compiler : Compiler ;
124
+ parentInjector : Injector ;
125
+ serverConfigRouteTree ?: RouteTree < ServerConfigRouteTreeAdditionalMetadata > ;
126
+ invokeGetPrerenderParams : boolean ;
127
+ includePrerenderFallbackRoutes : boolean ;
128
+ entryPointToBrowserMapping ?: EntryPointToBrowserMapping ;
129
+ parentPreloads ?: readonly string [ ] ;
130
+ } ) : AsyncIterableIterator < RouteTreeNodeMetadata | { error : string } > {
131
+ try {
132
+ const {
133
+ metadata,
134
+ currentRoutePath,
135
+ route,
136
+ compiler,
137
+ parentInjector,
138
+ serverConfigRouteTree,
139
+ entryPointToBrowserMapping,
140
+ invokeGetPrerenderParams,
141
+ includePrerenderFallbackRoutes,
142
+ } = options ;
143
+
144
+ const { redirectTo, loadChildren, loadComponent, children, ɵentryName } = route ;
145
+
146
+ if ( ɵentryName && loadComponent ) {
147
+ appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , true ) ;
148
+ }
149
+
150
+ if ( metadata . renderMode === RenderMode . Prerender ) {
151
+ yield * handleSSGRoute (
152
+ serverConfigRouteTree ,
153
+ typeof redirectTo === 'string' ? redirectTo : undefined ,
154
+ metadata ,
155
+ parentInjector ,
156
+ invokeGetPrerenderParams ,
157
+ includePrerenderFallbackRoutes ,
158
+ ) ;
159
+ } else if ( typeof redirectTo === 'string' ) {
160
+ if ( metadata . status && ! VALID_REDIRECT_RESPONSE_CODES . has ( metadata . status ) ) {
161
+ yield {
162
+ error :
163
+ `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
164
+ `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
165
+ } ;
166
+ } else {
167
+ yield { ...metadata , redirectTo : resolveRedirectTo ( metadata . route , redirectTo ) } ;
168
+ }
169
+ } else {
170
+ yield metadata ;
171
+ }
172
+
173
+ // Recursively process child routes
174
+ if ( children ?. length ) {
175
+ yield * traverseRoutesConfig ( {
176
+ ...options ,
177
+ routes : children ,
178
+ parentRoute : currentRoutePath ,
179
+ parentPreloads : metadata . preload ,
180
+ } ) ;
181
+ }
182
+
183
+ // Load and process lazy-loaded child routes
184
+ if ( loadChildren ) {
185
+ if ( ɵentryName ) {
186
+ // When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
187
+ // As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
188
+ // across different child routes. In contrast, `loadComponent` only loads a single component, which allows
189
+ // for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
190
+ appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , false ) ;
191
+ }
192
+
193
+ const loadedChildRoutes = await loadChildrenHelper (
194
+ route ,
195
+ compiler ,
196
+ parentInjector ,
197
+ ) . toPromise ( ) ;
198
+
199
+ if ( loadedChildRoutes ) {
200
+ const { routes : childRoutes , injector = parentInjector } = loadedChildRoutes ;
201
+ yield * traverseRoutesConfig ( {
202
+ ...options ,
203
+ routes : childRoutes ,
204
+ parentInjector : injector ,
205
+ parentRoute : currentRoutePath ,
206
+ parentPreloads : metadata . preload ,
207
+ } ) ;
208
+ }
209
+ }
210
+ } catch ( error ) {
211
+ yield {
212
+ error : `Error in handleRoute for '${ options . currentRoutePath } ': ${ ( error as Error ) . message } ` ,
213
+ } ;
214
+ }
215
+ }
216
+
113
217
/**
114
218
* Traverses an array of route configurations to generate route tree node metadata.
115
219
*
@@ -124,57 +228,69 @@ async function* traverseRoutesConfig(options: {
124
228
compiler : Compiler ;
125
229
parentInjector : Injector ;
126
230
parentRoute : string ;
127
- serverConfigRouteTree : RouteTree < ServerConfigRouteTreeAdditionalMetadata > | undefined ;
231
+ serverConfigRouteTree ? : RouteTree < ServerConfigRouteTreeAdditionalMetadata > ;
128
232
invokeGetPrerenderParams : boolean ;
129
233
includePrerenderFallbackRoutes : boolean ;
130
- entryPointToBrowserMapping : EntryPointToBrowserMapping | undefined ;
234
+ entryPointToBrowserMapping ? : EntryPointToBrowserMapping ;
131
235
parentPreloads ?: readonly string [ ] ;
132
236
} ) : AsyncIterableIterator < RouteTreeNodeMetadata | { error : string } > {
133
- const {
134
- routes,
135
- compiler,
136
- parentInjector,
137
- parentRoute,
138
- serverConfigRouteTree,
139
- entryPointToBrowserMapping,
140
- parentPreloads,
141
- invokeGetPrerenderParams,
142
- includePrerenderFallbackRoutes,
143
- } = options ;
237
+ const { routes : routeConfigs , parentPreloads, parentRoute, serverConfigRouteTree } = options ;
144
238
145
- for ( const route of routes ) {
239
+ for ( const route of routeConfigs ) {
146
240
try {
147
- const {
148
- path = '' ,
149
- matcher,
150
- redirectTo,
151
- loadChildren,
152
- loadComponent,
153
- children,
154
- ɵentryName,
155
- } = route ;
241
+ const { matcher, path = matcher ? '**' : '' } = route ;
156
242
const currentRoutePath = joinUrlParts ( parentRoute , path ) ;
157
243
158
- // Get route metadata from the server config route tree, if available
159
- let matchedMetaData : ServerConfigRouteTreeNodeMetadata | undefined ;
160
- if ( serverConfigRouteTree ) {
161
- if ( matcher ) {
162
- // Only issue this error when SSR routing is used.
163
- yield {
164
- error : `The route '${ stripLeadingSlash ( currentRoutePath ) } ' uses a route matcher that is not supported.` ,
244
+ if ( matcher && serverConfigRouteTree ) {
245
+ let foundMatch = false ;
246
+ for ( const matchedMetaData of serverConfigRouteTree . traverse ( ) ) {
247
+ if ( ! matchedMetaData . route . startsWith ( currentRoutePath ) ) {
248
+ continue ;
249
+ }
250
+
251
+ foundMatch = true ;
252
+ matchedMetaData . presentInClientRouter = true ;
253
+
254
+ if ( matchedMetaData . renderMode === RenderMode . Prerender ) {
255
+ yield {
256
+ error :
257
+ `The route '${ stripLeadingSlash ( currentRoutePath ) } ' is set for prerendering but has a defined matcher. ` +
258
+ `Routes with matchers cannot use prerendering. Please specify a different 'renderMode'.` ,
259
+ } ;
260
+ continue ;
261
+ }
262
+
263
+ const metadata : ServerConfigRouteTreeNodeMetadata = {
264
+ ...matchedMetaData ,
265
+ preload : parentPreloads ,
266
+ route : matchedMetaData . route ,
267
+ presentInClientRouter : undefined ,
165
268
} ;
166
269
167
- continue ;
270
+ yield * handleRoute ( { ... options , metadata , currentRoutePath , route } ) ;
168
271
}
169
272
273
+ if ( ! foundMatch ) {
274
+ yield {
275
+ error :
276
+ `The route '${ stripLeadingSlash ( currentRoutePath ) } ' has a defined matcher ` +
277
+ 'but does not match any route in the server routing configuration. ' +
278
+ 'Please ensure this route is added to the server routing configuration.' ,
279
+ } ;
280
+ }
281
+
282
+ continue ;
283
+ }
284
+
285
+ let matchedMetaData : ServerConfigRouteTreeNodeMetadata | undefined ;
286
+ if ( serverConfigRouteTree ) {
170
287
matchedMetaData = serverConfigRouteTree . match ( currentRoutePath ) ;
171
288
if ( ! matchedMetaData ) {
172
289
yield {
173
290
error :
174
291
`The '${ stripLeadingSlash ( currentRoutePath ) } ' route does not match any route defined in the server routing configuration. ` +
175
292
'Please ensure this route is added to the server routing configuration.' ,
176
293
} ;
177
-
178
294
continue ;
179
295
}
180
296
@@ -192,71 +308,32 @@ async function* traverseRoutesConfig(options: {
192
308
presentInClientRouter : undefined ,
193
309
} ;
194
310
195
- if ( ɵentryName && loadComponent ) {
196
- appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , true ) ;
197
- }
198
-
199
- if ( metadata . renderMode === RenderMode . Prerender ) {
200
- // Handle SSG routes
201
- yield * handleSSGRoute (
202
- serverConfigRouteTree ,
203
- typeof redirectTo === 'string' ? redirectTo : undefined ,
204
- metadata ,
205
- parentInjector ,
206
- invokeGetPrerenderParams ,
207
- includePrerenderFallbackRoutes ,
208
- ) ;
209
- } else if ( typeof redirectTo === 'string' ) {
210
- // Handle redirects
211
- if ( metadata . status && ! VALID_REDIRECT_RESPONSE_CODES . has ( metadata . status ) ) {
212
- yield {
213
- error :
214
- `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
215
- `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
216
- } ;
311
+ yield * handleRoute ( { ...options , metadata, currentRoutePath, route } ) ;
217
312
218
- continue ;
219
- }
220
-
221
- yield { ...metadata , redirectTo : resolveRedirectTo ( metadata . route , redirectTo ) } ;
222
- } else {
223
- yield metadata ;
224
- }
225
-
226
- // Recursively process child routes
227
- if ( children ?. length ) {
313
+ if ( route . children ?. length ) {
228
314
yield * traverseRoutesConfig ( {
229
315
...options ,
230
- routes : children ,
316
+ routes : route . children ,
231
317
parentRoute : currentRoutePath ,
232
- parentPreloads : metadata . preload ,
318
+ parentPreloads,
233
319
} ) ;
234
320
}
235
321
236
- // Load and process lazy-loaded child routes
237
- if ( loadChildren ) {
238
- if ( ɵentryName ) {
239
- // When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
240
- // As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
241
- // across different child routes. In contrast, `loadComponent` only loads a single component, which allows
242
- // for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
243
- appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , false ) ;
244
- }
245
-
322
+ if ( route . loadChildren ) {
246
323
const loadedChildRoutes = await loadChildrenHelper (
247
324
route ,
248
- compiler ,
249
- parentInjector ,
325
+ options . compiler ,
326
+ options . parentInjector ,
250
327
) . toPromise ( ) ;
251
328
252
329
if ( loadedChildRoutes ) {
253
- const { routes : childRoutes , injector = parentInjector } = loadedChildRoutes ;
330
+ const { routes : childRoutes , injector = options . parentInjector } = loadedChildRoutes ;
254
331
yield * traverseRoutesConfig ( {
255
332
...options ,
256
333
routes : childRoutes ,
257
334
parentInjector : injector ,
258
335
parentRoute : currentRoutePath ,
259
- parentPreloads : metadata . preload ,
336
+ parentPreloads,
260
337
} ) ;
261
338
}
262
339
}
0 commit comments