Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions goldens/public-api/angular/ssr/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,20 @@ export enum PrerenderFallback {
}

// @public
export function provideServerRoutesConfig(routes: ServerRoute[]): EnvironmentProviders;
export function provideServerRoutesConfig(routes: ServerRoute[], options?: ServerRoutesConfigOptions): EnvironmentProviders;

// @public
export enum RenderMode {
AppShell = 0,
Client = 2,
Prerender = 3,
Server = 1
Client = 1,
Prerender = 2,
Server = 0
}

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

// @public
export type ServerRoute = ServerRouteAppShell | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer;

// @public
export interface ServerRouteAppShell extends Omit<ServerRouteCommon, 'headers' | 'status'> {
renderMode: RenderMode.AppShell;
}
export type ServerRoute = ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer;

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

// @public
export interface ServerRoutesConfigOptions {
appShellRoute?: string;
}

// @public
export interface ServerRouteServer extends ServerRouteCommon {
renderMode: RenderMode.Server;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
harness.expectFile('dist/browser/main.js').toExist();
const indexFileContent = harness.expectFile('dist/browser/index.html').content;
indexFileContent.toContain('app-shell works!');
// TODO(alanagius): enable once integration of routes in complete.
// indexFileContent.toContain('ng-server-context="app-shell"');
});

it('critical CSS is inlined', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/angular/build/src/utils/server-rendering/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type WritableSerializableRouteTreeNode = Writeable<SerializableRouteTreeN

export interface RoutersExtractorWorkerResult {
serializedRouteTree: SerializableRouteTreeNode;
appShellRoute?: string;
errors: string[];
}

Expand All @@ -33,8 +34,7 @@ export interface RoutersExtractorWorkerResult {
* It maps `RenderMode` enum values to their corresponding numeric identifiers.
*/
export const RouteRenderMode: Record<keyof typeof RenderMode, RenderMode> = {
AppShell: 0,
Server: 1,
Client: 2,
Prerender: 3,
Server: 0,
Client: 1,
Prerender: 2,
};
77 changes: 42 additions & 35 deletions packages/angular/build/src/utils/server-rendering/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,26 @@ export async function prerenderPages(
}

// Get routes to prerender
const { errors: extractionErrors, serializedRouteTree: serializableRouteTreeNode } =
await getAllRoutes(
workspaceRoot,
baseHref,
outputFilesForWorker,
assetsReversed,
appShellOptions,
prerenderOptions,
sourcemap,
outputMode,
).catch((err) => {
return {
errors: [
`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`,
],
serializedRouteTree: [],
};
});
const {
errors: extractionErrors,
serializedRouteTree: serializableRouteTreeNode,
appShellRoute,
} = await getAllRoutes(
workspaceRoot,
baseHref,
outputFilesForWorker,
assetsReversed,
appShellOptions,
prerenderOptions,
sourcemap,
outputMode,
).catch((err) => {
return {
errors: [`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`],
serializedRouteTree: [],
appShellRoute: undefined,
};
});

errors.push(...extractionErrors);

Expand All @@ -133,7 +135,6 @@ export async function prerenderPages(
switch (metadata.renderMode) {
case undefined: /* Legacy building mode */
case RouteRenderMode.Prerender:
case RouteRenderMode.AppShell:
serializableRouteTreeNodeForPrerender.push(metadata);
break;
case RouteRenderMode.Server:
Expand Down Expand Up @@ -166,6 +167,7 @@ export async function prerenderPages(
assetsReversed,
appShellOptions,
outputMode,
appShellRoute ?? appShellOptions?.route,
);

errors.push(...renderingErrors);
Expand All @@ -188,6 +190,7 @@ async function renderPages(
assetFilesForWorker: Record<string, string>,
appShellOptions: AppShellOptions | undefined,
outputMode: OutputMode | undefined,
appShellRoute: string | undefined,
): Promise<{
output: PrerenderOutput;
errors: string[];
Expand Down Expand Up @@ -215,7 +218,7 @@ async function renderPages(

try {
const renderingPromises: Promise<void>[] = [];
const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute);
const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);

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

const isAppShellRoute =
renderMode === RouteRenderMode.AppShell ||
// Legacy handling
(renderMode === undefined && appShellRoute === routeWithoutBaseHref);

const render: Promise<string | null> = renderWorker.run({ url: route, isAppShellRoute });
const render: Promise<string | null> = renderWorker.run({ url: route });
const renderResult: Promise<void> = render
.then((content) => {
if (content !== null) {
output[outPath] = { content, appShellRoute: isAppShellRoute };
output[outPath] = {
content,
appShellRoute: appShellRouteWithLeadingSlash === routeWithoutBaseHref,
};
}
})
.catch((err) => {
Expand Down Expand Up @@ -274,14 +275,21 @@ async function getAllRoutes(
prerenderOptions: PrerenderOptions | undefined,
sourcemap: boolean,
outputMode: OutputMode | undefined,
): Promise<{ serializedRouteTree: SerializableRouteTreeNode; errors: string[] }> {
): Promise<{
serializedRouteTree: SerializableRouteTreeNode;
appShellRoute?: string;
errors: string[];
}> {
const { routesFile, discoverRoutes } = prerenderOptions ?? {};
const routes: WritableSerializableRouteTreeNode = [];
let appShellRoute: string | undefined;

if (appShellOptions) {
appShellRoute = urlJoin(baseHref, appShellOptions.route);

routes.push({
renderMode: RouteRenderMode.AppShell,
route: urlJoin(baseHref, appShellOptions.route),
renderMode: RouteRenderMode.Prerender,
route: appShellRoute,
});
}

Expand All @@ -296,7 +304,7 @@ async function getAllRoutes(
}

if (!discoverRoutes) {
return { errors: [], serializedRouteTree: routes };
return { errors: [], appShellRoute, serializedRouteTree: routes };
}

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

try {
const { serializedRouteTree, errors }: RoutersExtractorWorkerResult = await renderWorker.run(
{},
);
const { serializedRouteTree, appShellRoute, errors }: RoutersExtractorWorkerResult =
await renderWorker.run({});

if (!routes.length) {
return { errors, serializedRouteTree };
return { errors, appShellRoute, serializedRouteTree };
}

// Merge the routing trees
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function extractRoutes(): Promise<RoutersExtractorWorkerResult> {
const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } =
await loadEsmModuleFromMemory('./main.server.mjs');

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

return {
errors,
appShellRoute,
serializedRouteTree: routeTree.toObject(),
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/ssr/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export { createRequestHandler, type RequestHandlerFunction } from './src/handler
export {
type PrerenderFallback,
type ServerRoute,
type ServerRoutesConfigOptions,
provideServerRoutesConfig,
RenderMode,
type ServerRouteAppShell,
type ServerRouteClient,
type ServerRoutePrerender,
type ServerRoutePrerenderWithParams,
Expand Down
19 changes: 12 additions & 7 deletions packages/angular/ssr/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@ const MAX_INLINE_CSS_CACHE_ENTRIES = 50;
*
* - `RenderMode.Prerender` maps to `'ssg'` (Static Site Generation).
* - `RenderMode.Server` maps to `'ssr'` (Server-Side Rendering).
* - `RenderMode.AppShell` maps to `'app-shell'` (pre-rendered application shell).
* - `RenderMode.Client` maps to an empty string `''` (Client-Side Rendering, no server context needed).
*/
const SERVER_CONTEXT_VALUE: Record<RenderMode, string> = {
[RenderMode.Prerender]: 'ssg',
[RenderMode.Server]: 'ssr',
[RenderMode.AppShell]: 'app-shell',
[RenderMode.Client]: '',
};

Expand Down Expand Up @@ -237,11 +235,18 @@ export class AngularServerApp {
matchedRoute: RouteTreeNodeMetadata,
requestContext?: unknown,
): Promise<Response | null> {
const { renderMode, headers, status } = matchedRoute;
if (
!this.allowStaticRouteRender &&
(renderMode === RenderMode.Prerender || renderMode === RenderMode.AppShell)
) {
const { redirectTo, status } = matchedRoute;

if (redirectTo !== undefined) {
// Note: The status code is validated during route extraction.
// 302 Found is used by default for redirections
// See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Response.redirect(new URL(redirectTo, new URL(request.url)), (status as any) ?? 302);
}

const { renderMode, headers } = matchedRoute;
if (!this.allowStaticRouteRender && renderMode === RenderMode.Prerender) {
return null;
}

Expand Down
Loading
Loading