fix(@angular/ssr): show error when multiple routes are set with RenderMode.AppShell

This change introduces error handling to ensure that when multiple routes are configured with `RenderMode.AppShell`, an error message is displayed. This prevents misconfiguration and enhances clarity in route management.
This commit is contained in:
Alan Agius 2024-09-26 20:35:07 +00:00 committed by Alan Agius
parent 50df631960
commit 64c52521d0
2 changed files with 46 additions and 10 deletions

View File

@ -45,7 +45,7 @@ const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
*/ */
type ServerConfigRouteTreeAdditionalMetadata = Partial<ServerRoute> & { type ServerConfigRouteTreeAdditionalMetadata = Partial<ServerRoute> & {
/** Indicates if the route has been matched with the Angular router routes. */ /** Indicates if the route has been matched with the Angular router routes. */
matched?: boolean; presentInClientRouter?: boolean;
}; };
/** /**
@ -134,7 +134,7 @@ async function* traverseRoutesConfig(options: {
continue; continue;
} }
matchedMetaData.matched = true; matchedMetaData.presentInClientRouter = true;
} }
const metadata: ServerConfigRouteTreeNodeMetadata = { const metadata: ServerConfigRouteTreeNodeMetadata = {
@ -142,7 +142,7 @@ async function* traverseRoutesConfig(options: {
route: currentRoutePath, route: currentRoutePath,
}; };
delete metadata.matched; delete metadata.presentInClientRouter;
// Handle redirects // Handle redirects
if (typeof redirectTo === 'string') { if (typeof redirectTo === 'string') {
@ -246,8 +246,8 @@ async function* handleSSGRoute(
if (!getPrerenderParams) { if (!getPrerenderParams) {
yield { yield {
error: error:
`The '${stripLeadingSlash(currentRoutePath)}' route uses prerendering and includes parameters, but 'getPrerenderParams' is missing. ` + `The '${stripLeadingSlash(currentRoutePath)}' route uses prerendering and includes parameters, but 'getPrerenderParams' ` +
`Please define 'getPrerenderParams' function for this route in your server routing configuration ` + `is missing. Please define 'getPrerenderParams' function for this route in your server routing configuration ` +
`or specify a different 'renderMode'.`, `or specify a different 'renderMode'.`,
}; };
@ -442,24 +442,38 @@ export async function getRoutesFromAngularRouterConfig(
includePrerenderFallbackRoutes, includePrerenderFallbackRoutes,
}); });
let seenAppShellRoute: string | undefined;
for await (const result of traverseRoutes) { for await (const result of traverseRoutes) {
if ('error' in result) { if ('error' in result) {
errors.push(result.error); errors.push(result.error);
} else { } else {
if (result.renderMode === RenderMode.AppShell) {
if (seenAppShellRoute !== undefined) {
errors.push(
`Error: Both '${seenAppShellRoute}' and '${stripLeadingSlash(result.route)}' routes have ` +
`their 'renderMode' set to 'AppShell'. AppShell renderMode should only be assigned to one route. ` +
`Please review your route configurations to ensure that only one route is set to 'RenderMode.AppShell'.`,
);
}
seenAppShellRoute = stripLeadingSlash(result.route);
}
routesResults.push(result); routesResults.push(result);
} }
} }
if (serverConfigRouteTree) { if (serverConfigRouteTree) {
for (const { route, matched } of serverConfigRouteTree.traverse()) { for (const { route, presentInClientRouter } of serverConfigRouteTree.traverse()) {
if (matched || route === '**') { if (presentInClientRouter || route === '**') {
// Skip if matched or it's the catch-all route. // Skip if matched or it's the catch-all route.
continue; continue;
} }
errors.push( errors.push(
`The server route '${route}' does not match any routes defined in the Angular routing configuration. ` + `The '${route}' server route does not match any routes defined in the Angular ` +
'Please verify and if unneeded remove this route from the server configuration.', `routing configuration (typically provided as a part of the 'provideRouter' call). ` +
'Please make sure that the mentioned server route is present in the Angular routing configuration.',
); );
} }
} }

View File

@ -314,7 +314,7 @@ describe('extractRoutesAndCreateRouteTree', () => {
expect(errors).toHaveSize(1); expect(errors).toHaveSize(1);
expect(errors[0]).toContain( expect(errors[0]).toContain(
`The server route 'invalid' does not match any routes defined in the Angular routing configuration`, `The 'invalid' server route does not match any routes defined in the Angular routing configuration`,
); );
}); });
@ -339,4 +339,26 @@ describe('extractRoutesAndCreateRouteTree', () => {
`The 'invalid' route does not match any route defined in the server routing configuration`, `The 'invalid' route does not match any route defined in the server routing configuration`,
); );
}); });
it(`should error when 'RenderMode.AppShell' is used on more than one route`, async () => {
setAngularAppTestingManifest(
[
{ path: 'home', component: DummyComponent },
{ path: 'shell', component: DummyComponent },
],
[{ path: '**', renderMode: RenderMode.AppShell }],
);
const { errors } = await extractRoutesAndCreateRouteTree(
url,
/** manifest */ undefined,
/** invokeGetPrerenderParams */ false,
/** includePrerenderFallbackRoutes */ false,
);
expect(errors).toHaveSize(1);
expect(errors[0]).toContain(
`Both 'home' and 'shell' routes have their 'renderMode' set to 'AppShell'.`,
);
});
}); });