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> & {
/** 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;
}
matchedMetaData.matched = true;
matchedMetaData.presentInClientRouter = true;
}
const metadata: ServerConfigRouteTreeNodeMetadata = {
@ -142,7 +142,7 @@ async function* traverseRoutesConfig(options: {
route: currentRoutePath,
};
delete metadata.matched;
delete metadata.presentInClientRouter;
// Handle redirects
if (typeof redirectTo === 'string') {
@ -246,8 +246,8 @@ async function* handleSSGRoute(
if (!getPrerenderParams) {
yield {
error:
`The '${stripLeadingSlash(currentRoutePath)}' route uses prerendering and includes parameters, but 'getPrerenderParams' is missing. ` +
`Please define 'getPrerenderParams' function for this route in your server routing configuration ` +
`The '${stripLeadingSlash(currentRoutePath)}' route uses prerendering and includes parameters, but 'getPrerenderParams' ` +
`is missing. Please define 'getPrerenderParams' function for this route in your server routing configuration ` +
`or specify a different 'renderMode'.`,
};
@ -442,24 +442,38 @@ export async function getRoutesFromAngularRouterConfig(
includePrerenderFallbackRoutes,
});
let seenAppShellRoute: string | undefined;
for await (const result of traverseRoutes) {
if ('error' in result) {
errors.push(result.error);
} 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);
}
}
if (serverConfigRouteTree) {
for (const { route, matched } of serverConfigRouteTree.traverse()) {
if (matched || route === '**') {
for (const { route, presentInClientRouter } of serverConfigRouteTree.traverse()) {
if (presentInClientRouter || route === '**') {
// Skip if matched or it's the catch-all route.
continue;
}
errors.push(
`The server route '${route}' does not match any routes defined in the Angular routing configuration. ` +
'Please verify and if unneeded remove this route from the server configuration.',
`The '${route}' server route does not match any routes defined in the Angular ` +
`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[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`,
);
});
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'.`,
);
});
});