Skip to content

Commit b2e2be0

Browse files
committed
refactor(@angular/ssr): remove RenderMode.AppShell in favor of new configuration option
This commit removes the `RenderMode.AppShell` option. Instead, a new configuration parameter, `{ appShellRoute: 'shell' }`, is introduced to the `provideServerRoutesConfig` method. ```ts provideServerRoutesConfig(serverRoutes, { appShellRoute: 'shell' }) ```
1 parent 553d3d7 commit b2e2be0

File tree

14 files changed

+174
-146
lines changed

14 files changed

+174
-146
lines changed

goldens/public-api/angular/ssr/index.api.md

+10-11
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,20 @@ export enum PrerenderFallback {
2424
}
2525

2626
// @public
27-
export function provideServerRoutesConfig(routes: ServerRoute[]): EnvironmentProviders;
27+
export function provideServerRoutesConfig(routes: ServerRoute[], options?: ServerRoutesConfigOptions): EnvironmentProviders;
2828

2929
// @public
3030
export enum RenderMode {
31-
AppShell = 0,
32-
Client = 2,
33-
Prerender = 3,
34-
Server = 1
31+
Client = 1,
32+
Prerender = 2,
33+
Server = 0
3534
}
3635

3736
// @public
3837
export type RequestHandlerFunction = (request: Request) => Promise<Response | null> | null | Response;
3938

4039
// @public
41-
export type ServerRoute = ServerRouteAppShell | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer;
42-
43-
// @public
44-
export interface ServerRouteAppShell extends Omit<ServerRouteCommon, 'headers' | 'status'> {
45-
renderMode: RenderMode.AppShell;
46-
}
40+
export type ServerRoute = ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer;
4741

4842
// @public
4943
export interface ServerRouteClient extends ServerRouteCommon {
@@ -69,6 +63,11 @@ export interface ServerRoutePrerenderWithParams extends Omit<ServerRoutePrerende
6963
getPrerenderParams: () => Promise<Record<string, string>[]>;
7064
}
7165

66+
// @public
67+
export interface ServerRoutesConfigOptions {
68+
appShellRoute?: string;
69+
}
70+
7271
// @public
7372
export interface ServerRouteServer extends ServerRouteCommon {
7473
renderMode: RenderMode.Server;

packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts

-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
117117
harness.expectFile('dist/browser/main.js').toExist();
118118
const indexFileContent = harness.expectFile('dist/browser/index.html').content;
119119
indexFileContent.toContain('app-shell works!');
120-
// TODO(alanagius): enable once integration of routes in complete.
121-
// indexFileContent.toContain('ng-server-context="app-shell"');
122120
});
123121

124122
it('critical CSS is inlined', async () => {

packages/angular/build/src/utils/server-rendering/models.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type WritableSerializableRouteTreeNode = Writeable<SerializableRouteTreeN
2323

2424
export interface RoutersExtractorWorkerResult {
2525
serializedRouteTree: SerializableRouteTreeNode;
26+
appShellRoute?: string;
2627
errors: string[];
2728
}
2829

@@ -33,8 +34,7 @@ export interface RoutersExtractorWorkerResult {
3334
* It maps `RenderMode` enum values to their corresponding numeric identifiers.
3435
*/
3536
export const RouteRenderMode: Record<keyof typeof RenderMode, RenderMode> = {
36-
AppShell: 0,
37-
Server: 1,
38-
Client: 2,
39-
Prerender: 3,
37+
Server: 0,
38+
Client: 1,
39+
Prerender: 2,
4040
};

packages/angular/build/src/utils/server-rendering/prerender.ts

+42-35
Original file line numberDiff line numberDiff line change
@@ -97,24 +97,26 @@ export async function prerenderPages(
9797
}
9898

9999
// Get routes to prerender
100-
const { errors: extractionErrors, serializedRouteTree: serializableRouteTreeNode } =
101-
await getAllRoutes(
102-
workspaceRoot,
103-
baseHref,
104-
outputFilesForWorker,
105-
assetsReversed,
106-
appShellOptions,
107-
prerenderOptions,
108-
sourcemap,
109-
outputMode,
110-
).catch((err) => {
111-
return {
112-
errors: [
113-
`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`,
114-
],
115-
serializedRouteTree: [],
116-
};
117-
});
100+
const {
101+
errors: extractionErrors,
102+
serializedRouteTree: serializableRouteTreeNode,
103+
appShellRoute,
104+
} = await getAllRoutes(
105+
workspaceRoot,
106+
baseHref,
107+
outputFilesForWorker,
108+
assetsReversed,
109+
appShellOptions,
110+
prerenderOptions,
111+
sourcemap,
112+
outputMode,
113+
).catch((err) => {
114+
return {
115+
errors: [`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`],
116+
serializedRouteTree: [],
117+
appShellRoute: undefined,
118+
};
119+
});
118120

119121
errors.push(...extractionErrors);
120122

@@ -133,7 +135,6 @@ export async function prerenderPages(
133135
switch (metadata.renderMode) {
134136
case undefined: /* Legacy building mode */
135137
case RouteRenderMode.Prerender:
136-
case RouteRenderMode.AppShell:
137138
serializableRouteTreeNodeForPrerender.push(metadata);
138139
break;
139140
case RouteRenderMode.Server:
@@ -166,6 +167,7 @@ export async function prerenderPages(
166167
assetsReversed,
167168
appShellOptions,
168169
outputMode,
170+
appShellRoute ?? appShellOptions?.route,
169171
);
170172

171173
errors.push(...renderingErrors);
@@ -188,6 +190,7 @@ async function renderPages(
188190
assetFilesForWorker: Record<string, string>,
189191
appShellOptions: AppShellOptions | undefined,
190192
outputMode: OutputMode | undefined,
193+
appShellRoute: string | undefined,
191194
): Promise<{
192195
output: PrerenderOutput;
193196
errors: string[];
@@ -215,7 +218,7 @@ async function renderPages(
215218

216219
try {
217220
const renderingPromises: Promise<void>[] = [];
218-
const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
221+
const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute);
219222
const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
220223

221224
for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
@@ -232,16 +235,14 @@ async function renderPages(
232235
continue;
233236
}
234237

235-
const isAppShellRoute =
236-
renderMode === RouteRenderMode.AppShell ||
237-
// Legacy handling
238-
(renderMode === undefined && appShellRoute === routeWithoutBaseHref);
239-
240-
const render: Promise<string | null> = renderWorker.run({ url: route, isAppShellRoute });
238+
const render: Promise<string | null> = renderWorker.run({ url: route });
241239
const renderResult: Promise<void> = render
242240
.then((content) => {
243241
if (content !== null) {
244-
output[outPath] = { content, appShellRoute: isAppShellRoute };
242+
output[outPath] = {
243+
content,
244+
appShellRoute: appShellRouteWithLeadingSlash === routeWithoutBaseHref,
245+
};
245246
}
246247
})
247248
.catch((err) => {
@@ -274,14 +275,21 @@ async function getAllRoutes(
274275
prerenderOptions: PrerenderOptions | undefined,
275276
sourcemap: boolean,
276277
outputMode: OutputMode | undefined,
277-
): Promise<{ serializedRouteTree: SerializableRouteTreeNode; errors: string[] }> {
278+
): Promise<{
279+
serializedRouteTree: SerializableRouteTreeNode;
280+
appShellRoute?: string;
281+
errors: string[];
282+
}> {
278283
const { routesFile, discoverRoutes } = prerenderOptions ?? {};
279284
const routes: WritableSerializableRouteTreeNode = [];
285+
let appShellRoute: string | undefined;
280286

281287
if (appShellOptions) {
288+
appShellRoute = urlJoin(baseHref, appShellOptions.route);
289+
282290
routes.push({
283-
renderMode: RouteRenderMode.AppShell,
284-
route: urlJoin(baseHref, appShellOptions.route),
291+
renderMode: RouteRenderMode.Prerender,
292+
route: appShellRoute,
285293
});
286294
}
287295

@@ -296,7 +304,7 @@ async function getAllRoutes(
296304
}
297305

298306
if (!discoverRoutes) {
299-
return { errors: [], serializedRouteTree: routes };
307+
return { errors: [], appShellRoute, serializedRouteTree: routes };
300308
}
301309

302310
const workerExecArgv = [IMPORT_EXEC_ARGV];
@@ -319,12 +327,11 @@ async function getAllRoutes(
319327
});
320328

321329
try {
322-
const { serializedRouteTree, errors }: RoutersExtractorWorkerResult = await renderWorker.run(
323-
{},
324-
);
330+
const { serializedRouteTree, appShellRoute, errors }: RoutersExtractorWorkerResult =
331+
await renderWorker.run({});
325332

326333
if (!routes.length) {
327-
return { errors, serializedRouteTree };
334+
return { errors, appShellRoute, serializedRouteTree };
328335
}
329336

330337
// Merge the routing trees

packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async function extractRoutes(): Promise<RoutersExtractorWorkerResult> {
3333
const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } =
3434
await loadEsmModuleFromMemory('./main.server.mjs');
3535

36-
const { routeTree, errors } = await extractRoutesAndCreateRouteTree(
36+
const { routeTree, appShellRoute, errors } = await extractRoutesAndCreateRouteTree(
3737
serverURL,
3838
undefined /** manifest */,
3939
true /** invokeGetPrerenderParams */,
@@ -42,6 +42,7 @@ async function extractRoutes(): Promise<RoutersExtractorWorkerResult> {
4242

4343
return {
4444
errors,
45+
appShellRoute,
4546
serializedRouteTree: routeTree.toObject(),
4647
};
4748
}

packages/angular/ssr/public_api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export { createRequestHandler, type RequestHandlerFunction } from './src/handler
1414
export {
1515
type PrerenderFallback,
1616
type ServerRoute,
17+
type ServerRoutesConfigOptions,
1718
provideServerRoutesConfig,
1819
RenderMode,
19-
type ServerRouteAppShell,
2020
type ServerRouteClient,
2121
type ServerRoutePrerender,
2222
type ServerRoutePrerenderWithParams,

packages/angular/ssr/src/app.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ const MAX_INLINE_CSS_CACHE_ENTRIES = 50;
3535
*
3636
* - `RenderMode.Prerender` maps to `'ssg'` (Static Site Generation).
3737
* - `RenderMode.Server` maps to `'ssr'` (Server-Side Rendering).
38-
* - `RenderMode.AppShell` maps to `'app-shell'` (pre-rendered application shell).
3938
* - `RenderMode.Client` maps to an empty string `''` (Client-Side Rendering, no server context needed).
4039
*/
4140
const SERVER_CONTEXT_VALUE: Record<RenderMode, string> = {
4241
[RenderMode.Prerender]: 'ssg',
4342
[RenderMode.Server]: 'ssr',
44-
[RenderMode.AppShell]: 'app-shell',
4543
[RenderMode.Client]: '',
4644
};
4745

@@ -237,11 +235,18 @@ export class AngularServerApp {
237235
matchedRoute: RouteTreeNodeMetadata,
238236
requestContext?: unknown,
239237
): Promise<Response | null> {
240-
const { renderMode, headers, status } = matchedRoute;
241-
if (
242-
!this.allowStaticRouteRender &&
243-
(renderMode === RenderMode.Prerender || renderMode === RenderMode.AppShell)
244-
) {
238+
const { redirectTo, status } = matchedRoute;
239+
240+
if (redirectTo !== undefined) {
241+
// Note: The status code is validated during route extraction.
242+
// 302 Found is used by default for redirections
243+
// See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
244+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
245+
return Response.redirect(new URL(redirectTo, new URL(request.url)), (status as any) ?? 302);
246+
}
247+
248+
const { renderMode, headers } = matchedRoute;
249+
if (!this.allowStaticRouteRender && renderMode === RenderMode.Prerender) {
245250
return null;
246251
}
247252

0 commit comments

Comments
 (0)