@@ -110,6 +110,108 @@ 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
+ } ) : AsyncIterableIterator < RouteTreeNodeMetadata | { error : string } > {
130
+ try {
131
+ const {
132
+ metadata,
133
+ currentRoutePath,
134
+ route,
135
+ compiler,
136
+ parentInjector,
137
+ serverConfigRouteTree,
138
+ entryPointToBrowserMapping,
139
+ invokeGetPrerenderParams,
140
+ includePrerenderFallbackRoutes,
141
+ } = options ;
142
+
143
+ const { redirectTo, loadChildren, loadComponent, children, ɵentryName } = route ;
144
+ if ( ɵentryName && loadComponent ) {
145
+ appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , true ) ;
146
+ }
147
+
148
+ if ( metadata . renderMode === RenderMode . Prerender ) {
149
+ yield * handleSSGRoute (
150
+ serverConfigRouteTree ,
151
+ typeof redirectTo === 'string' ? redirectTo : undefined ,
152
+ metadata ,
153
+ parentInjector ,
154
+ invokeGetPrerenderParams ,
155
+ includePrerenderFallbackRoutes ,
156
+ ) ;
157
+ } else if ( typeof redirectTo === 'string' ) {
158
+ if ( metadata . status && ! VALID_REDIRECT_RESPONSE_CODES . has ( metadata . status ) ) {
159
+ yield {
160
+ error :
161
+ `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
162
+ `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
163
+ } ;
164
+ } else {
165
+ yield { ...metadata , redirectTo : resolveRedirectTo ( metadata . route , redirectTo ) } ;
166
+ }
167
+ } else {
168
+ yield metadata ;
169
+ }
170
+
171
+ // Recursively process child routes
172
+ if ( children ?. length ) {
173
+ yield * traverseRoutesConfig ( {
174
+ ...options ,
175
+ routes : children ,
176
+ parentRoute : currentRoutePath ,
177
+ parentPreloads : metadata . preload ,
178
+ } ) ;
179
+ }
180
+
181
+ // Load and process lazy-loaded child routes
182
+ if ( loadChildren ) {
183
+ if ( ɵentryName ) {
184
+ // When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
185
+ // As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
186
+ // across different child routes. In contrast, `loadComponent` only loads a single component, which allows
187
+ // for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
188
+ appendPreloadToMetadata ( ɵentryName , entryPointToBrowserMapping , metadata , false ) ;
189
+ }
190
+
191
+ const loadedChildRoutes = await loadChildrenHelper (
192
+ route ,
193
+ compiler ,
194
+ parentInjector ,
195
+ ) . toPromise ( ) ;
196
+
197
+ if ( loadedChildRoutes ) {
198
+ const { routes : childRoutes , injector = parentInjector } = loadedChildRoutes ;
199
+ yield * traverseRoutesConfig ( {
200
+ ...options ,
201
+ routes : childRoutes ,
202
+ parentInjector : injector ,
203
+ parentRoute : currentRoutePath ,
204
+ parentPreloads : metadata . preload ,
205
+ } ) ;
206
+ }
207
+ }
208
+ } catch ( error ) {
209
+ yield {
210
+ error : `Error in handleRoute for '${ options . currentRoutePath } ': ${ ( error as Error ) . message } ` ,
211
+ } ;
212
+ }
213
+ }
214
+
113
215
/**
114
216
* Traverses an array of route configurations to generate route tree node metadata.
115
217
*
@@ -124,64 +226,79 @@ async function* traverseRoutesConfig(options: {
124
226
compiler : Compiler ;
125
227
parentInjector : Injector ;
126
228
parentRoute : string ;
127
- serverConfigRouteTree : RouteTree < ServerConfigRouteTreeAdditionalMetadata > | undefined ;
229
+ serverConfigRouteTree ? : RouteTree < ServerConfigRouteTreeAdditionalMetadata > ;
128
230
invokeGetPrerenderParams : boolean ;
129
231
includePrerenderFallbackRoutes : boolean ;
130
- entryPointToBrowserMapping : EntryPointToBrowserMapping | undefined ;
232
+ entryPointToBrowserMapping ? : EntryPointToBrowserMapping ;
131
233
parentPreloads ?: readonly string [ ] ;
132
234
} ) : 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 ;
235
+ const { routes : routeConfigs , parentPreloads, parentRoute, serverConfigRouteTree } = options ;
144
236
145
- for ( const route of routes ) {
146
- try {
147
- const {
148
- path = '' ,
149
- matcher,
150
- redirectTo,
151
- loadChildren,
152
- loadComponent,
153
- children,
154
- ɵentryName,
155
- } = route ;
156
- const currentRoutePath = joinUrlParts ( parentRoute , path ) ;
157
-
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.` ,
165
- } ;
237
+ for ( const route of routeConfigs ) {
238
+ const { matcher, path = matcher ? '**' : '' } = route ;
239
+ const currentRoutePath = joinUrlParts ( parentRoute , path ) ;
166
240
241
+ if ( matcher && serverConfigRouteTree ) {
242
+ let foundMatch = false ;
243
+ for ( const matchedMetaData of serverConfigRouteTree . traverse ( ) ) {
244
+ if ( ! matchedMetaData . route . startsWith ( currentRoutePath ) ) {
167
245
continue ;
168
246
}
169
247
170
- matchedMetaData = serverConfigRouteTree . match ( currentRoutePath ) ;
171
- if ( ! matchedMetaData ) {
248
+ foundMatch = true ;
249
+ matchedMetaData . presentInClientRouter = true ;
250
+
251
+ if ( matchedMetaData . renderMode === RenderMode . Prerender ) {
172
252
yield {
173
253
error :
174
- `The '${ stripLeadingSlash ( currentRoutePath ) } ' route does not match any route defined in the server routing configuration . ` +
175
- 'Please ensure this route is added to the server routing configuration.' ,
254
+ `The route '${ stripLeadingSlash ( currentRoutePath ) } ' is set for prerendering but has a defined matcher . ` +
255
+ `Routes with matchers cannot use prerendering. Please specify a different 'renderMode'.` ,
176
256
} ;
177
-
178
257
continue ;
179
258
}
180
259
181
- matchedMetaData . presentInClientRouter = true ;
260
+ yield * handleRoute ( {
261
+ ...options ,
262
+ currentRoutePath,
263
+ route,
264
+ metadata : {
265
+ ...matchedMetaData ,
266
+ preload : parentPreloads ,
267
+ route : matchedMetaData . route ,
268
+ presentInClientRouter : undefined ,
269
+ } ,
270
+ } ) ;
271
+ }
272
+
273
+ if ( ! foundMatch ) {
274
+ yield {
275
+ error :
276
+ `The route '${ stripLeadingSlash ( currentRoutePath ) } ' has a defined matcher but does not ` +
277
+ 'match any route in the server routing configuration. Please ensure this route is added to the server routing configuration.' ,
278
+ } ;
279
+ }
280
+
281
+ continue ;
282
+ }
283
+
284
+ let matchedMetaData : ServerConfigRouteTreeNodeMetadata | undefined ;
285
+ if ( serverConfigRouteTree ) {
286
+ matchedMetaData = serverConfigRouteTree . match ( currentRoutePath ) ;
287
+ if ( ! matchedMetaData ) {
288
+ yield {
289
+ error :
290
+ `The '${ stripLeadingSlash ( currentRoutePath ) } ' route does not match any route defined in the server routing configuration. ` +
291
+ 'Please ensure this route is added to the server routing configuration.' ,
292
+ } ;
293
+ continue ;
182
294
}
183
295
184
- const metadata : ServerConfigRouteTreeNodeMetadata = {
296
+ matchedMetaData . presentInClientRouter = true ;
297
+ }
298
+
299
+ yield * handleRoute ( {
300
+ ...options ,
301
+ metadata : {
185
302
renderMode : RenderMode . Prerender ,
186
303
...matchedMetaData ,
187
304
preload : parentPreloads ,
@@ -190,81 +307,10 @@ async function* traverseRoutesConfig(options: {
190
307
// ['one', 'two', 'three'] -> 'one/two/three'
191
308
route : path === '' ? addTrailingSlash ( currentRoutePath ) : currentRoutePath ,
192
309
presentInClientRouter : undefined ,
193
- } ;
194
-
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
- } ;
217
-
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 ) {
228
- yield * traverseRoutesConfig ( {
229
- ...options ,
230
- routes : children ,
231
- parentRoute : currentRoutePath ,
232
- parentPreloads : metadata . preload ,
233
- } ) ;
234
- }
235
-
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
-
246
- const loadedChildRoutes = await loadChildrenHelper (
247
- route ,
248
- compiler ,
249
- parentInjector ,
250
- ) . toPromise ( ) ;
251
-
252
- if ( loadedChildRoutes ) {
253
- const { routes : childRoutes , injector = parentInjector } = loadedChildRoutes ;
254
- yield * traverseRoutesConfig ( {
255
- ...options ,
256
- routes : childRoutes ,
257
- parentInjector : injector ,
258
- parentRoute : currentRoutePath ,
259
- parentPreloads : metadata . preload ,
260
- } ) ;
261
- }
262
- }
263
- } catch ( error ) {
264
- yield {
265
- error : `Error processing route '${ stripLeadingSlash ( route . path ?? '' ) } ': ${ ( error as Error ) . message } ` ,
266
- } ;
267
- }
310
+ } ,
311
+ currentRoutePath,
312
+ route,
313
+ } ) ;
268
314
}
269
315
}
270
316
0 commit comments