/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/ssr/src/routes/route-config.ts
486 строк
16 KB
Alan Agius
fix(@angular/ssr): support server-side rendering configuration options
29 май 2026, 09:04
29 май 2026, 09:04
21ca098
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { EnvironmentProviders, InjectionToken, Provider, Type, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core'; import { provideServerRendering as provideServerRenderingPlatformServer } from '@angular/platform-server'; import { type DefaultExport, ROUTES, type Route } from '@angular/router'; /** * The internal path used for the app shell route. * @internal */ const APP_SHELL_ROUTE = 'ng-app-shell'; /** * Identifies a particular kind of `ServerRenderingFeatureKind`. * @see {@link ServerRenderingFeature} */ enum ServerRenderingFeatureKind { AppShell, ServerRoutes, } /** * Helper type to represent a server routes feature. * @see {@link ServerRenderingFeatureKind} */ interface ServerRenderingFeature<FeatureKind extends ServerRenderingFeatureKind> { ɵkind: FeatureKind; ɵproviders: (Provider | EnvironmentProviders)[]; } /** * Different rendering modes for server routes. * @see {@link withRoutes} * @see {@link ServerRoute} */ export enum RenderMode { /** Server-Side Rendering (SSR) mode, where content is rendered on the server for each request. */ Server, /** Client-Side Rendering (CSR) mode, where content is rendered on the client side in the browser. */ Client, /** Static Site Generation (SSG) mode, where content is pre-rendered at build time and served as static files. */ Prerender, } /** * Defines the fallback strategies for Static Site Generation (SSG) routes when a pre-rendered path is not available. * This is particularly relevant for routes with parameterized URLs where some paths might not be pre-rendered at build time. * @see {@link ServerRoutePrerenderWithParams} */ export enum PrerenderFallback { /** * Fallback to Server-Side Rendering (SSR) if the pre-rendered path is not available. * This strategy dynamically generates the page on the server at request time. */ Server, /** * Fallback to Client-Side Rendering (CSR) if the pre-rendered path is not available. * This strategy allows the page to be rendered on the client side. */ Client, /** * No fallback; if the path is not pre-rendered, the server will not handle the request. * This means the application will not provide any response for paths that are not pre-rendered. */ None, } /** * Common interface for server routes, providing shared properties. */ export interface ServerRouteCommon { /** The path associated with this route. */ path: string; /** Optional additional headers to include in the response for this route. */ headers?: Record<string, string>; /** Optional status code to return for this route. */ status?: number; } /** * A server route that uses Client-Side Rendering (CSR) mode. * @see {@link RenderMode} */ export interface ServerRouteClient extends ServerRouteCommon { /** Specifies that the route uses Client-Side Rendering (CSR) mode. */ renderMode: RenderMode.Client; } /** * A server route that uses Static Site Generation (SSG) mode. * @see {@link RenderMode} */ export interface ServerRoutePrerender extends Omit<ServerRouteCommon, 'status'> { /** Specifies that the route uses Static Site Generation (SSG) mode. */ renderMode: RenderMode.Prerender; /** Fallback cannot be specified unless `getPrerenderParams` is used. */ fallback?: never; } /** * A server route configuration that uses Static Site Generation (SSG) mode, including support for routes with parameters. * @see {@link RenderMode} * @see {@link ServerRoutePrerender} * @see {@link PrerenderFallback} */ export interface ServerRoutePrerenderWithParams extends Omit<ServerRoutePrerender, 'fallback'> { /** * Optional strategy to use if the SSG path is not pre-rendered. * This is especially relevant for routes with parameterized URLs, where some paths may not be pre-rendered at build time. * * This property determines how to handle requests for paths that are not pre-rendered: * - `PrerenderFallback.Server`: Use Server-Side Rendering (SSR) to dynamically generate the page at request time. * - `PrerenderFallback.Client`: Use Client-Side Rendering (CSR) to fetch and render the page on the client side. * - `PrerenderFallback.None`: No fallback; if the path is not pre-rendered, the server will not handle the request. * * @default `PrerenderFallback.Server` if not provided. */ fallback?: PrerenderFallback; /** * A function that returns a Promise resolving to an array of objects, each representing a route path with URL parameters. * This function runs in the injector context, allowing access to Angular services and dependencies. * * It also works for catch-all routes (e.g., `/**`), where the parameter name will be `**` and the return value will be * the segments of the path, such as `/foo/bar`. These routes can also be combined, e.g., `/product/:id/**`, * where both a parameterized segment (`:id`) and a catch-all segment (`**`) can be used together to handle more complex paths. * * @returns A Promise resolving to an array where each element is an object with string keys (representing URL parameter names) * and string values (representing the corresponding values for those parameters in the route path). * * @example * ```typescript * export const serverRouteConfig: ServerRoutes[] = [ * { * path: '/product/:id', * renderMode: RenderMode.Prerender, * async getPrerenderParams() { * const productService = inject(ProductService); * const ids = await productService.getIds(); // Assuming this returns ['1', '2', '3'] * * return ids.map(id => ({ id })); // Generates paths like: ['product/1', 'product/2', 'product/3'] * }, * }, * { * path: '/product/:id/**', * renderMode: RenderMode.Prerender, * async getPrerenderParams() { * return [ * { id: '1', '**': 'laptop/3' }, * { id: '2', '**': 'laptop/4' } * ]; // Generates paths like: ['product/1/laptop/3', 'product/2/laptop/4'] * }, * }, * ]; * ``` */ getPrerenderParams: () => Promise<Record<string, string>[]>; } /** * A server route that uses Server-Side Rendering (SSR) mode. * @see {@link RenderMode} */ export interface ServerRouteServer extends ServerRouteCommon { /** Specifies that the route uses Server-Side Rendering (SSR) mode. */ renderMode: RenderMode.Server; } /** * Server route configuration. * @see {@link withRoutes} */ export type ServerRoute = | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer; /** * Configuration value for server routes configuration. * @internal */ export interface ServerRoutesConfig { /** * Defines the route to be used as the app shell. */ appShellRoute?: string; /** List of server routes for the application. */ routes: ServerRoute[]; } /** * Token for providing the server routes configuration. * @internal */ export const SERVER_ROUTES_CONFIG = new InjectionToken<ServerRoutesConfig>('SERVER_ROUTES_CONFIG'); /** * Configures server-side routing for the application. * * This function registers an array of `ServerRoute` definitions, enabling server-side rendering * for specific URL paths. These routes are used to pre-render content on the server, improving * initial load performance and SEO. * * @param routes - An array of `ServerRoute` objects, each defining a server-rendered route. * @returns A `ServerRenderingFeature` object configuring server-side routes. * * @example * ```ts * import { provideServerRendering, withRoutes, ServerRoute, RenderMode } from '@angular/ssr'; * * const serverRoutes: ServerRoute[] = [ * { * path: '', // This renders the "/" route on the client (CSR) * renderMode: RenderMode.Client, * }, * { * path: 'about', // This page is static, so we prerender it (SSG) * renderMode: RenderMode.Prerender, * }, * { * path: 'profile', // This page requires user-specific data, so we use SSR * renderMode: RenderMode.Server, * }, * { * path: '**', // All other routes will be rendered on the server (SSR) * renderMode: RenderMode.Server, * }, * ]; * * provideServerRendering(withRoutes(serverRoutes)); * ``` * * @see {@link provideServerRendering} * @see {@link ServerRoute} */ export function withRoutes( routes: ServerRoute[], ): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes> { const config: ServerRoutesConfig = { routes }; return { ɵkind: ServerRenderingFeatureKind.ServerRoutes, ɵproviders: [ { provide: SERVER_ROUTES_CONFIG, useValue: config, }, ], }; } /** * Configures the shell of the application. * * The app shell is a minimal, static HTML page that is served immediately, while the * full Angular application loads in the background. This improves perceived performance * by providing instant feedback to the user. * * This function configures the app shell route, which serves the provided component for * requests that do not match any defined server routes. * * @param component - The Angular component to render for the app shell. Can be a direct * component type or a dynamic import function. * @returns A `ServerRenderingFeature` object configuring the app shell. * * @example * ```ts * import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr'; * import { AppShellComponent } from './app-shell.component'; * * provideServerRendering( * withRoutes(serverRoutes), * withAppShell(AppShellComponent) * ); * ``` * * @example * ```ts * import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr'; * * provideServerRendering( * withRoutes(serverRoutes), * withAppShell(() => * import('./app-shell.component').then((m) => m.AppShellComponent) * ) * ); * ``` * * @see {@link provideServerRendering} * @see {@link https://angular.dev/ecosystem/service-workers/app-shell App shell pattern on Angular.dev} */ export function withAppShell( component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>), ): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell> { const routeConfig: Route = { path: APP_SHELL_ROUTE, }; if ('ɵcmp' in component) { routeConfig.component = component as Type<unknown>; } else { routeConfig.loadComponent = component as () => Promise<Type<unknown>>; } return { ɵkind: ServerRenderingFeatureKind.AppShell, ɵproviders: [ { provide: ROUTES, useValue: routeConfig, multi: true, }, provideEnvironmentInitializer(() => { const config = inject(SERVER_ROUTES_CONFIG); config.appShellRoute = APP_SHELL_ROUTE; }), ], }; } /** * Options for configuring server-side rendering. */ export interface ServerRenderingOptions { /** * The maximum allowed response body size when using the Fetch API. * @default 1MB */ maxResponseBodySize: number; } /** * Configures server-side rendering for an Angular application. * * This function sets up the necessary providers for server-side rendering, including * support for server routes and app shell. It combines features configured using * `withRoutes` and `withAppShell` to provide a comprehensive server-side rendering setup. * * @param features - Optional features to configure additional server rendering behaviors. * @returns An `EnvironmentProviders` instance with the server-side rendering configuration. * * @example * Basic example of how you can enable server-side rendering in your application * when using the `bootstrapApplication` function: * * ```ts * import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser'; * import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr'; * import { AppComponent } from './app/app.component'; * import { SERVER_ROUTES } from './app/app.server.routes'; * import { AppShellComponent } from './app/app-shell.component'; * * const bootstrap = (context: BootstrapContext) => * bootstrapApplication(AppComponent, { * providers: [ * provideServerRendering( * withRoutes(SERVER_ROUTES), * withAppShell(AppShellComponent), * ), * ], * }, context); * * export default bootstrap; * ``` * @see {@link withRoutes} configures server-side routing * @see {@link withAppShell} configures the application shell */ export function provideServerRendering( ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[] ): EnvironmentProviders; /** * Configures server-side rendering for an Angular application with additional options. * * This function sets up the necessary providers for server-side rendering, including * support for server routes and app shell. It combines features configured using * `withRoutes` and `withAppShell` to provide a comprehensive server-side rendering setup. * * @param options - Configuration options for server-side rendering. * @param features - Optional features to configure additional server rendering behaviors. * @returns An `EnvironmentProviders` instance with the server-side rendering configuration. * * @example * Basic example of how you can enable server-side rendering with options in your application * when using the `bootstrapApplication` function: * * ```ts * import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser'; * import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr'; * import { AppComponent } from './app/app.component'; * import { SERVER_ROUTES } from './app/app.server.routes'; * import { AppShellComponent } from './app/app-shell.component'; * * const bootstrap = (context: BootstrapContext) => * bootstrapApplication(AppComponent, { * providers: [ * provideServerRendering( * { maxResponseBodySize: 1024 * 1024 }, // 1MB limit * withRoutes(SERVER_ROUTES), * withAppShell(AppShellComponent), * ), * ], * }, context); * * export default bootstrap; * ``` * @see {@link withRoutes} configures server-side routing * @see {@link withAppShell} configures the application shell */ export function provideServerRendering( options: ServerRenderingOptions, ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[] ): EnvironmentProviders; export function provideServerRendering( ...args: | ServerRenderingFeature<ServerRenderingFeatureKind>[] | [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]] ): EnvironmentProviders { let options: ServerRenderingOptions | undefined; let features: ServerRenderingFeature<ServerRenderingFeatureKind>[]; if (hasOptions(args)) { const [first, ...rest] = args; options = first; features = rest; } else { features = args; } const providers: (Provider | EnvironmentProviders)[] = [ provideServerRenderingPlatformServer(options), ]; let hasAppShell = false; let hasServerRoutes = false; for (const { ɵkind, ɵproviders } of features) { hasAppShell ||= ɵkind === ServerRenderingFeatureKind.AppShell; hasServerRoutes ||= ɵkind === ServerRenderingFeatureKind.ServerRoutes; providers.push(...ɵproviders); } if (!hasServerRoutes && hasAppShell) { throw new Error( `Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'.` + `The 'withAppShell()' function requires 'withRoutes()' to be used.`, ); } return makeEnvironmentProviders(providers); } /** * Checks if the first element of args is a `ServerRenderingOptions` object. */ function hasOptions( args: | ServerRenderingFeature<ServerRenderingFeatureKind>[] | [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]], ): args is [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]] { const value = args[0]; return !!value && typeof value === 'object' && !('ɵkind' in value); }