@@ -110,6 +110,108 @@ interface AngularRouterConfigResult {
110110
111111type EntryPointToBrowserMapping = AngularAppManifest [ 'entryPointToBrowserMapping' ] ;
112112
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+
113215/**
114216 * Traverses an array of route configurations to generate route tree node metadata.
115217 *
@@ -124,64 +226,79 @@ async function* traverseRoutesConfig(options: {
124226 compiler : Compiler ;
125227 parentInjector : Injector ;
126228 parentRoute : string ;
127- serverConfigRouteTree : RouteTree < ServerConfigRouteTreeAdditionalMetadata > | undefined ;
229+ serverConfigRouteTree ? : RouteTree < ServerConfigRouteTreeAdditionalMetadata > ;
128230 invokeGetPrerenderParams : boolean ;
129231 includePrerenderFallbackRoutes : boolean ;
130- entryPointToBrowserMapping : EntryPointToBrowserMapping | undefined ;
232+ entryPointToBrowserMapping ? : EntryPointToBrowserMapping ;
131233 parentPreloads ?: readonly string [ ] ;
132234} ) : 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 ;
144236
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 ) ;
166240
241+ if ( matcher && serverConfigRouteTree ) {
242+ let foundMatch = false ;
243+ for ( const matchedMetaData of serverConfigRouteTree . traverse ( ) ) {
244+ if ( ! matchedMetaData . route . startsWith ( currentRoutePath ) ) {
167245 continue ;
168246 }
169247
170- matchedMetaData = serverConfigRouteTree . match ( currentRoutePath ) ;
171- if ( ! matchedMetaData ) {
248+ foundMatch = true ;
249+ matchedMetaData . presentInClientRouter = true ;
250+
251+ if ( matchedMetaData . renderMode === RenderMode . Prerender ) {
172252 yield {
173253 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'.` ,
176256 } ;
177-
178257 continue ;
179258 }
180259
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 ;
182294 }
183295
184- const metadata : ServerConfigRouteTreeNodeMetadata = {
296+ matchedMetaData . presentInClientRouter = true ;
297+ }
298+
299+ yield * handleRoute ( {
300+ ...options ,
301+ metadata : {
185302 renderMode : RenderMode . Prerender ,
186303 ...matchedMetaData ,
187304 preload : parentPreloads ,
@@ -190,81 +307,10 @@ async function* traverseRoutesConfig(options: {
190307 // ['one', 'two', 'three'] -> 'one/two/three'
191308 route : path === '' ? addTrailingSlash ( currentRoutePath ) : currentRoutePath ,
192309 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+ } ) ;
268314 }
269315}
270316
0 commit comments