@angular/ssr
Version:
Angular server side rendering utilities
1 lines • 183 kB
Source Map (JSON)
{"version":3,"file":"ssr.mjs","sources":["../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/assets.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/console.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/manifest.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/url.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/ng.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/promise.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/routes/route-config.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/routes/route-tree.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/routes/ng-routes.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/hooks.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/routes/router.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/crypto.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/inline-critical-css.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/utils/lru-cache.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/app.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/i18n.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/app-engine.ts","../../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/angular/ssr/src/handler.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport { AngularAppManifest, ServerAsset } from './manifest';\n\n/**\n * Manages server-side assets.\n */\nexport class ServerAssets {\n /**\n * Creates an instance of ServerAsset.\n *\n * @param manifest - The manifest containing the server assets.\n */\n constructor(private readonly manifest: AngularAppManifest) {}\n\n /**\n * Retrieves the content of a server-side asset using its path.\n *\n * @param path - The path to the server asset within the manifest.\n * @returns The server asset associated with the provided path, as a `ServerAsset` object.\n * @throws Error - Throws an error if the asset does not exist.\n */\n getServerAsset(path: string): ServerAsset {\n const asset = this.manifest.assets[path];\n if (!asset) {\n throw new Error(`Server asset '${path}' does not exist.`);\n }\n\n return asset;\n }\n\n /**\n * Checks if a specific server-side asset exists.\n *\n * @param path - The path to the server asset.\n * @returns A boolean indicating whether the asset exists.\n */\n hasServerAsset(path: string): boolean {\n return !!this.manifest.assets[path];\n }\n\n /**\n * Retrieves the asset for 'index.server.html'.\n *\n * @returns The `ServerAsset` object for 'index.server.html'.\n * @throws Error - Throws an error if 'index.server.html' does not exist.\n */\n getIndexServerHtml(): ServerAsset {\n return this.getServerAsset('index.server.html');\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport { ɵConsole } from '@angular/core';\n\n/**\n * A set of log messages that should be ignored and not printed to the console.\n */\nconst IGNORED_LOGS = new Set(['Angular is running in development mode.']);\n\n/**\n * Custom implementation of the Angular Console service that filters out specific log messages.\n *\n * This class extends the internal Angular `ɵConsole` class to provide customized logging behavior.\n * It overrides the `log` method to suppress logs that match certain predefined messages.\n */\nexport class Console extends ɵConsole {\n /**\n * Logs a message to the console if it is not in the set of ignored messages.\n *\n * @param message - The message to log to the console.\n *\n * This method overrides the `log` method of the `ɵConsole` class. It checks if the\n * message is in the `IGNORED_LOGS` set. If it is not, it delegates the logging to\n * the parent class's `log` method. Otherwise, the message is suppressed.\n */\n override log(message: string): void {\n if (!IGNORED_LOGS.has(message)) {\n super.log(message);\n }\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport type { BootstrapContext } from '@angular/platform-browser';\nimport type { SerializableRouteTreeNode } from './routes/route-tree';\nimport { AngularBootstrap } from './utils/ng';\n\n/**\n * Represents a server asset stored in the manifest.\n */\nexport interface ServerAsset {\n /**\n * Retrieves the text content of the asset.\n *\n * @returns A promise that resolves to the asset's content as a string.\n */\n text: () => Promise<string>;\n\n /**\n * A hash string representing the asset's content.\n */\n hash: string;\n\n /**\n * The size of the asset's content in bytes.\n */\n size: number;\n}\n\n/**\n * Represents the exports of an Angular server application entry point.\n */\nexport interface EntryPointExports {\n /**\n * A reference to the function that creates an Angular server application instance.\n *\n * @remarks The return type is `unknown` to prevent circular dependency issues.\n */\n ɵgetOrCreateAngularServerApp: () => unknown;\n\n /**\n * A reference to the function that destroys the `AngularServerApp` instance.\n */\n ɵdestroyAngularServerApp: () => void;\n}\n\n/**\n * Manifest for the Angular server application engine, defining entry points.\n */\nexport interface AngularAppEngineManifest {\n /**\n * A readonly record of entry points for the server application.\n * Each entry consists of:\n * - `key`: The url segment for the entry point.\n * - `value`: A function that returns a promise resolving to an object of type `EntryPointExports`.\n */\n readonly entryPoints: Readonly<Record<string, (() => Promise<EntryPointExports>) | undefined>>;\n\n /**\n * The base path for the server application.\n * This is used to determine the root path of the application.\n */\n readonly basePath: string;\n\n /**\n * A readonly record mapping supported locales to their respective entry-point paths.\n * Each entry consists of:\n * - `key`: The locale identifier (e.g., 'en', 'fr').\n * - `value`: The url segment associated with that locale.\n */\n readonly supportedLocales: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Manifest for a specific Angular server application, defining assets and bootstrap logic.\n */\nexport interface AngularAppManifest {\n /**\n * The base href for the application.\n * This is used to determine the root path of the application.\n */\n readonly baseHref: string;\n\n /**\n * A readonly record of assets required by the server application.\n * Each entry consists of:\n * - `key`: The path of the asset.\n * - `value`: An object of type `ServerAsset`.\n */\n readonly assets: Readonly<Record<string, ServerAsset | undefined>>;\n\n /**\n * The bootstrap mechanism for the server application.\n * A function that returns a promise that resolves to an `NgModule` or a function\n * returning a promise that resolves to an `ApplicationRef`.\n */\n readonly bootstrap: () => Promise<AngularBootstrap>;\n\n /**\n * Indicates whether critical CSS should be inlined into the HTML.\n * If set to `true`, critical CSS will be inlined for faster page rendering.\n */\n readonly inlineCriticalCss?: boolean;\n\n /**\n * The route tree representation for the routing configuration of the application.\n * This represents the routing information of the application, mapping route paths to their corresponding metadata.\n * It is used for route matching and navigation within the server application.\n */\n readonly routes?: SerializableRouteTreeNode;\n\n /**\n * An optional string representing the locale or language code to be used for\n * the application, aiding with localization and rendering content specific to the locale.\n */\n readonly locale?: string;\n\n /**\n * Maps entry-point names to their corresponding browser bundles and loading strategies.\n *\n * - **Key**: The entry-point name, typically the value of `ɵentryName`.\n * - **Value**: A readonly array of JavaScript bundle paths or `undefined` if no bundles are associated.\n *\n * ### Example\n * ```ts\n * {\n * 'src/app/lazy/lazy.ts': ['src/app/lazy/lazy.js']\n * }\n * ```\n */\n readonly entryPointToBrowserMapping?: Readonly<Record<string, readonly string[] | undefined>>;\n}\n\n/**\n * The Angular app manifest object.\n * This is used internally to store the current Angular app manifest.\n */\nlet angularAppManifest: AngularAppManifest | undefined;\n\n/**\n * Sets the Angular app manifest.\n *\n * @param manifest - The manifest object to set for the Angular application.\n */\nexport function setAngularAppManifest(manifest: AngularAppManifest): void {\n angularAppManifest = manifest;\n}\n\n/**\n * Gets the Angular app manifest.\n *\n * @returns The Angular app manifest.\n * @throws Will throw an error if the Angular app manifest is not set.\n */\nexport function getAngularAppManifest(): AngularAppManifest {\n if (!angularAppManifest) {\n throw new Error(\n 'Angular app manifest is not set. ' +\n `Please ensure you are using the '@angular/build:application' builder to build your server application.`,\n );\n }\n\n return angularAppManifest;\n}\n\n/**\n * The Angular app engine manifest object.\n * This is used internally to store the current Angular app engine manifest.\n */\nlet angularAppEngineManifest: AngularAppEngineManifest | undefined;\n\n/**\n * Sets the Angular app engine manifest.\n *\n * @param manifest - The engine manifest object to set.\n */\nexport function setAngularAppEngineManifest(manifest: AngularAppEngineManifest): void {\n angularAppEngineManifest = manifest;\n}\n\n/**\n * Gets the Angular app engine manifest.\n *\n * @returns The Angular app engine manifest.\n * @throws Will throw an error if the Angular app engine manifest is not set.\n */\nexport function getAngularAppEngineManifest(): AngularAppEngineManifest {\n if (!angularAppEngineManifest) {\n throw new Error(\n 'Angular app engine manifest is not set. ' +\n `Please ensure you are using the '@angular/build:application' builder to build your server application.`,\n );\n }\n\n return angularAppEngineManifest;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * Removes the trailing slash from a URL if it exists.\n *\n * @param url - The URL string from which to remove the trailing slash.\n * @returns The URL string without a trailing slash.\n *\n * @example\n * ```js\n * stripTrailingSlash('path/'); // 'path'\n * stripTrailingSlash('/path'); // '/path'\n * stripTrailingSlash('/'); // '/'\n * stripTrailingSlash(''); // ''\n * ```\n */\nexport function stripTrailingSlash(url: string): string {\n // Check if the last character of the URL is a slash\n return url.length > 1 && url[url.length - 1] === '/' ? url.slice(0, -1) : url;\n}\n\n/**\n * Removes the leading slash from a URL if it exists.\n *\n * @param url - The URL string from which to remove the leading slash.\n * @returns The URL string without a leading slash.\n *\n * @example\n * ```js\n * stripLeadingSlash('/path'); // 'path'\n * stripLeadingSlash('/path/'); // 'path/'\n * stripLeadingSlash('/'); // '/'\n * stripLeadingSlash(''); // ''\n * ```\n */\nexport function stripLeadingSlash(url: string): string {\n // Check if the first character of the URL is a slash\n return url.length > 1 && url[0] === '/' ? url.slice(1) : url;\n}\n\n/**\n * Adds a leading slash to a URL if it does not already have one.\n *\n * @param url - The URL string to which the leading slash will be added.\n * @returns The URL string with a leading slash.\n *\n * @example\n * ```js\n * addLeadingSlash('path'); // '/path'\n * addLeadingSlash('/path'); // '/path'\n * ```\n */\nexport function addLeadingSlash(url: string): string {\n // Check if the URL already starts with a slash\n return url[0] === '/' ? url : `/${url}`;\n}\n\n/**\n * Adds a trailing slash to a URL if it does not already have one.\n *\n * @param url - The URL string to which the trailing slash will be added.\n * @returns The URL string with a trailing slash.\n *\n * @example\n * ```js\n * addTrailingSlash('path'); // 'path/'\n * addTrailingSlash('path/'); // 'path/'\n * ```\n */\nexport function addTrailingSlash(url: string): string {\n // Check if the URL already end with a slash\n return url[url.length - 1] === '/' ? url : `${url}/`;\n}\n\n/**\n * Joins URL parts into a single URL string.\n *\n * This function takes multiple URL segments, normalizes them by removing leading\n * and trailing slashes where appropriate, and then joins them into a single URL.\n *\n * @param parts - The parts of the URL to join. Each part can be a string with or without slashes.\n * @returns The joined URL string, with normalized slashes.\n *\n * @example\n * ```js\n * joinUrlParts('path/', '/to/resource'); // '/path/to/resource'\n * joinUrlParts('/path/', 'to/resource'); // '/path/to/resource'\n * joinUrlParts('', ''); // '/'\n * ```\n */\nexport function joinUrlParts(...parts: string[]): string {\n const normalizeParts: string[] = [];\n for (const part of parts) {\n if (part === '') {\n // Skip any empty parts\n continue;\n }\n\n let normalizedPart = part;\n if (part[0] === '/') {\n normalizedPart = normalizedPart.slice(1);\n }\n if (part[part.length - 1] === '/') {\n normalizedPart = normalizedPart.slice(0, -1);\n }\n if (normalizedPart !== '') {\n normalizeParts.push(normalizedPart);\n }\n }\n\n return addLeadingSlash(normalizeParts.join('/'));\n}\n\n/**\n * Strips `/index.html` from the end of a URL's path, if present.\n *\n * This function is used to convert URLs pointing to an `index.html` file into their directory\n * equivalents. For example, it transforms a URL like `http://www.example.com/page/index.html`\n * into `http://www.example.com/page`.\n *\n * @param url - The URL object to process.\n * @returns A new URL object with `/index.html` removed from the path, if it was present.\n *\n * @example\n * ```typescript\n * const originalUrl = new URL('http://www.example.com/page/index.html');\n * const cleanedUrl = stripIndexHtmlFromURL(originalUrl);\n * console.log(cleanedUrl.href); // Output: 'http://www.example.com/page'\n * ```\n */\nexport function stripIndexHtmlFromURL(url: URL): URL {\n if (url.pathname.endsWith('/index.html')) {\n const modifiedURL = new URL(url);\n // Remove '/index.html' from the pathname\n modifiedURL.pathname = modifiedURL.pathname.slice(0, /** '/index.html'.length */ -11);\n\n return modifiedURL;\n }\n\n return url;\n}\n\n/**\n * Resolves `*` placeholders in a path template by mapping them to corresponding segments\n * from a base path. This is useful for constructing paths dynamically based on a given base path.\n *\n * The function processes the `toPath` string, replacing each `*` placeholder with\n * the corresponding segment from the `fromPath`. If the `toPath` contains no placeholders,\n * it is returned as-is. Invalid `toPath` formats (not starting with `/`) will throw an error.\n *\n * @param toPath - A path template string that may contain `*` placeholders. Each `*` is replaced\n * by the corresponding segment from the `fromPath`. Static paths (e.g., `/static/path`) are returned\n * directly without placeholder replacement.\n * @param fromPath - A base path string, split into segments, that provides values for\n * replacing `*` placeholders in the `toPath`.\n * @returns A resolved path string with `*` placeholders replaced by segments from the `fromPath`,\n * or the `toPath` returned unchanged if it contains no placeholders.\n *\n * @throws If the `toPath` does not start with a `/`, indicating an invalid path format.\n *\n * @example\n * ```typescript\n * // Example with placeholders resolved\n * const resolvedPath = buildPathWithParams('/*\\/details', '/123/abc');\n * console.log(resolvedPath); // Outputs: '/123/details'\n *\n * // Example with a static path\n * const staticPath = buildPathWithParams('/static/path', '/base/unused');\n * console.log(staticPath); // Outputs: '/static/path'\n * ```\n */\nexport function buildPathWithParams(toPath: string, fromPath: string): string {\n if (toPath[0] !== '/') {\n throw new Error(`Invalid toPath: The string must start with a '/'. Received: '${toPath}'`);\n }\n\n if (fromPath[0] !== '/') {\n throw new Error(`Invalid fromPath: The string must start with a '/'. Received: '${fromPath}'`);\n }\n\n if (!toPath.includes('/*')) {\n return toPath;\n }\n\n const fromPathParts = fromPath.split('/');\n const toPathParts = toPath.split('/');\n const resolvedParts = toPathParts.map((part, index) =>\n toPathParts[index] === '*' ? fromPathParts[index] : part,\n );\n\n return joinUrlParts(...resolvedParts);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport { APP_BASE_HREF, PlatformLocation } from '@angular/common';\nimport {\n ApplicationRef,\n type PlatformRef,\n type StaticProvider,\n type Type,\n ɵConsole,\n} from '@angular/core';\nimport { BootstrapContext } from '@angular/platform-browser';\nimport {\n INITIAL_CONFIG,\n ɵSERVER_CONTEXT as SERVER_CONTEXT,\n platformServer,\n ɵrenderInternal as renderInternal,\n} from '@angular/platform-server';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Console } from '../console';\nimport { joinUrlParts, stripIndexHtmlFromURL } from './url';\n\n/**\n * Represents the bootstrap mechanism for an Angular application.\n *\n * This type can either be:\n * - A reference to an Angular component or module (`Type<unknown>`) that serves as the root of the application.\n * - A function that returns a `Promise<ApplicationRef>`, which resolves with the root application reference.\n */\nexport type AngularBootstrap =\n | Type<unknown>\n | ((context: BootstrapContext) => Promise<ApplicationRef>);\n\n/**\n * Renders an Angular application or module to an HTML string.\n *\n * This function determines whether the provided `bootstrap` value is an Angular module\n * or a bootstrap function and invokes the appropriate rendering method (`renderModule` or `renderApplication`).\n *\n * @param html - The initial HTML document content.\n * @param bootstrap - An Angular module type or a function returning a promise that resolves to an `ApplicationRef`.\n * @param url - The application URL, used for route-based rendering in SSR.\n * @param platformProviders - An array of platform providers for the rendering process.\n * @param serverContext - A string representing the server context, providing additional metadata for SSR.\n * @returns A promise resolving to an object containing:\n * - `hasNavigationError`: Indicates if a navigation error occurred.\n * - `redirectTo`: (Optional) The redirect URL if a navigation redirect occurred.\n * - `content`: A function returning a promise that resolves to the rendered HTML string.\n */\nexport async function renderAngular(\n html: string,\n bootstrap: AngularBootstrap,\n url: URL,\n platformProviders: StaticProvider[],\n serverContext: string,\n): Promise<{ hasNavigationError: boolean; redirectTo?: string; content: () => Promise<string> }> {\n // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.\n const urlToRender = stripIndexHtmlFromURL(url).toString();\n const platformRef = platformServer([\n {\n provide: INITIAL_CONFIG,\n useValue: {\n url: urlToRender,\n document: html,\n },\n },\n {\n provide: SERVER_CONTEXT,\n useValue: serverContext,\n },\n {\n // An Angular Console Provider that does not print a set of predefined logs.\n provide: ɵConsole,\n // Using `useClass` would necessitate decorating `Console` with `@Injectable`,\n // which would require switching from `ts_library` to `ng_module`. This change\n // would also necessitate various patches of `@angular/bazel` to support ESM.\n useFactory: () => new Console(),\n },\n ...platformProviders,\n ]);\n\n let redirectTo: string | undefined;\n let hasNavigationError = true;\n\n try {\n let applicationRef: ApplicationRef;\n if (isNgModule(bootstrap)) {\n const moduleRef = await platformRef.bootstrapModule(bootstrap);\n applicationRef = moduleRef.injector.get(ApplicationRef);\n } else {\n applicationRef = await bootstrap({ platformRef });\n }\n\n // Block until application is stable.\n await applicationRef.whenStable();\n\n // TODO(alanagius): Find a way to avoid rendering here especially for redirects as any output will be discarded.\n const envInjector = applicationRef.injector;\n const routerIsProvided = !!envInjector.get(ActivatedRoute, null);\n const router = envInjector.get(Router);\n const lastSuccessfulNavigation = router.lastSuccessfulNavigation;\n\n if (!routerIsProvided) {\n hasNavigationError = false;\n } else if (lastSuccessfulNavigation?.finalUrl) {\n hasNavigationError = false;\n\n const { finalUrl, initialUrl } = lastSuccessfulNavigation;\n const finalUrlStringified = finalUrl.toString();\n\n if (initialUrl.toString() !== finalUrlStringified) {\n const baseHref =\n envInjector.get(APP_BASE_HREF, null, { optional: true }) ??\n envInjector.get(PlatformLocation).getBaseHrefFromDOM();\n\n redirectTo = joinUrlParts(baseHref, finalUrlStringified);\n }\n }\n\n return {\n hasNavigationError,\n redirectTo,\n content: () =>\n new Promise<string>((resolve, reject) => {\n // Defer rendering to the next event loop iteration to avoid blocking, as most operations in `renderInternal` are synchronous.\n setTimeout(() => {\n renderInternal(platformRef, applicationRef)\n .then(resolve)\n .catch(reject)\n .finally(() => void asyncDestroyPlatform(platformRef));\n }, 0);\n }),\n };\n } catch (error) {\n await asyncDestroyPlatform(platformRef);\n\n throw error;\n } finally {\n if (hasNavigationError || redirectTo) {\n void asyncDestroyPlatform(platformRef);\n }\n }\n}\n\n/**\n * Type guard to determine if a given value is an Angular module.\n * Angular modules are identified by the presence of the `ɵmod` static property.\n * This function helps distinguish between Angular modules and bootstrap functions.\n *\n * @param value - The value to be checked.\n * @returns True if the value is an Angular module (i.e., it has the `ɵmod` property), false otherwise.\n */\nexport function isNgModule(value: AngularBootstrap): value is Type<unknown> {\n return 'ɵmod' in value;\n}\n\n/**\n * Gracefully destroys the application in a macrotask, allowing pending promises to resolve\n * and surfacing any potential errors to the user.\n *\n * @param platformRef - The platform reference to be destroyed.\n */\nfunction asyncDestroyPlatform(platformRef: PlatformRef): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(() => {\n if (!platformRef.destroyed) {\n platformRef.destroy();\n }\n\n resolve();\n }, 0);\n });\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * Creates a promise that resolves with the result of the provided `promise` or rejects with an\n * `AbortError` if the `AbortSignal` is triggered before the promise resolves.\n *\n * @param promise - The promise to monitor for completion.\n * @param signal - An `AbortSignal` used to monitor for an abort event. If the signal is aborted,\n * the returned promise will reject.\n * @param errorMessagePrefix - A custom message prefix to include in the error message when the operation is aborted.\n * @returns A promise that either resolves with the value of the provided `promise` or rejects with\n * an `AbortError` if the `AbortSignal` is triggered.\n *\n * @throws {AbortError} If the `AbortSignal` is triggered before the `promise` resolves.\n */\nexport function promiseWithAbort<T>(\n promise: Promise<T>,\n signal: AbortSignal,\n errorMessagePrefix: string,\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const abortHandler = () => {\n reject(\n new DOMException(`${errorMessagePrefix} was aborted.\\n${signal.reason}`, 'AbortError'),\n );\n };\n\n // Check for abort signal\n if (signal.aborted) {\n abortHandler();\n\n return;\n }\n\n signal.addEventListener('abort', abortHandler, { once: true });\n\n promise\n .then(resolve)\n .catch(reject)\n .finally(() => {\n signal.removeEventListener('abort', abortHandler);\n });\n });\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n EnvironmentProviders,\n InjectionToken,\n Provider,\n Type,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n} from '@angular/core';\nimport { provideServerRendering as provideServerRenderingPlatformServer } from '@angular/platform-server';\nimport { type DefaultExport, ROUTES, type Route } from '@angular/router';\n\n/**\n * The internal path used for the app shell route.\n * @internal\n */\nconst APP_SHELL_ROUTE = 'ng-app-shell';\n\n/**\n * Identifies a particular kind of `ServerRenderingFeatureKind`.\n * @see {@link ServerRenderingFeature}\n */\nenum ServerRenderingFeatureKind {\n AppShell,\n ServerRoutes,\n}\n\n/**\n * Helper type to represent a server routes feature.\n * @see {@link ServerRenderingFeatureKind}\n */\ninterface ServerRenderingFeature<FeatureKind extends ServerRenderingFeatureKind> {\n ɵkind: FeatureKind;\n ɵproviders: (Provider | EnvironmentProviders)[];\n}\n\n/**\n * Different rendering modes for server routes.\n * @see {@link withRoutes}\n * @see {@link ServerRoute}\n */\nexport enum RenderMode {\n /** Server-Side Rendering (SSR) mode, where content is rendered on the server for each request. */\n Server,\n\n /** Client-Side Rendering (CSR) mode, where content is rendered on the client side in the browser. */\n Client,\n\n /** Static Site Generation (SSG) mode, where content is pre-rendered at build time and served as static files. */\n Prerender,\n}\n\n/**\n * Defines the fallback strategies for Static Site Generation (SSG) routes when a pre-rendered path is not available.\n * This is particularly relevant for routes with parameterized URLs where some paths might not be pre-rendered at build time.\n * @see {@link ServerRoutePrerenderWithParams}\n */\nexport enum PrerenderFallback {\n /**\n * Fallback to Server-Side Rendering (SSR) if the pre-rendered path is not available.\n * This strategy dynamically generates the page on the server at request time.\n */\n Server,\n\n /**\n * Fallback to Client-Side Rendering (CSR) if the pre-rendered path is not available.\n * This strategy allows the page to be rendered on the client side.\n */\n Client,\n\n /**\n * No fallback; if the path is not pre-rendered, the server will not handle the request.\n * This means the application will not provide any response for paths that are not pre-rendered.\n */\n None,\n}\n\n/**\n * Common interface for server routes, providing shared properties.\n */\nexport interface ServerRouteCommon {\n /** The path associated with this route. */\n path: string;\n\n /** Optional additional headers to include in the response for this route. */\n headers?: Record<string, string>;\n\n /** Optional status code to return for this route. */\n status?: number;\n}\n\n/**\n * A server route that uses Client-Side Rendering (CSR) mode.\n * @see {@link RenderMode}\n */\nexport interface ServerRouteClient extends ServerRouteCommon {\n /** Specifies that the route uses Client-Side Rendering (CSR) mode. */\n renderMode: RenderMode.Client;\n}\n\n/**\n * A server route that uses Static Site Generation (SSG) mode.\n * @see {@link RenderMode}\n */\nexport interface ServerRoutePrerender extends Omit<ServerRouteCommon, 'status'> {\n /** Specifies that the route uses Static Site Generation (SSG) mode. */\n renderMode: RenderMode.Prerender;\n\n /** Fallback cannot be specified unless `getPrerenderParams` is used. */\n fallback?: never;\n}\n\n/**\n * A server route configuration that uses Static Site Generation (SSG) mode, including support for routes with parameters.\n * @see {@link RenderMode}\n * @see {@link ServerRoutePrerender}\n * @see {@link PrerenderFallback}\n */\nexport interface ServerRoutePrerenderWithParams extends Omit<ServerRoutePrerender, 'fallback'> {\n /**\n * Optional strategy to use if the SSG path is not pre-rendered.\n * This is especially relevant for routes with parameterized URLs, where some paths may not be pre-rendered at build time.\n *\n * This property determines how to handle requests for paths that are not pre-rendered:\n * - `PrerenderFallback.Server`: Use Server-Side Rendering (SSR) to dynamically generate the page at request time.\n * - `PrerenderFallback.Client`: Use Client-Side Rendering (CSR) to fetch and render the page on the client side.\n * - `PrerenderFallback.None`: No fallback; if the path is not pre-rendered, the server will not handle the request.\n *\n * @default `PrerenderFallback.Server` if not provided.\n */\n fallback?: PrerenderFallback;\n\n /**\n * A function that returns a Promise resolving to an array of objects, each representing a route path with URL parameters.\n * This function runs in the injector context, allowing access to Angular services and dependencies.\n *\n * It also works for catch-all routes (e.g., `/**`), where the parameter name will be `**` and the return value will be\n * the segments of the path, such as `/foo/bar`. These routes can also be combined, e.g., `/product/:id/**`,\n * where both a parameterized segment (`:id`) and a catch-all segment (`**`) can be used together to handle more complex paths.\n *\n * @returns A Promise resolving to an array where each element is an object with string keys (representing URL parameter names)\n * and string values (representing the corresponding values for those parameters in the route path).\n *\n * @example\n * ```typescript\n * export const serverRouteConfig: ServerRoutes[] = [\n * {\n * path: '/product/:id',\n * renderMode: RenderMode.Prerender,\n * async getPrerenderParams() {\n * const productService = inject(ProductService);\n * const ids = await productService.getIds(); // Assuming this returns ['1', '2', '3']\n *\n * return ids.map(id => ({ id })); // Generates paths like: ['product/1', 'product/2', 'product/3']\n * },\n * },\n * {\n * path: '/product/:id/**',\n * renderMode: RenderMode.Prerender,\n * async getPrerenderParams() {\n * return [\n * { id: '1', '**': 'laptop/3' },\n * { id: '2', '**': 'laptop/4' }\n * ]; // Generates paths like: ['product/1/laptop/3', 'product/2/laptop/4']\n * },\n * },\n * ];\n * ```\n */\n getPrerenderParams: () => Promise<Record<string, string>[]>;\n}\n\n/**\n * A server route that uses Server-Side Rendering (SSR) mode.\n * @see {@link RenderMode}\n */\nexport interface ServerRouteServer extends ServerRouteCommon {\n /** Specifies that the route uses Server-Side Rendering (SSR) mode. */\n renderMode: RenderMode.Server;\n}\n\n/**\n * Server route configuration.\n * @see {@link withRoutes}\n */\nexport type ServerRoute =\n | ServerRouteClient\n | ServerRoutePrerender\n | ServerRoutePrerenderWithParams\n | ServerRouteServer;\n\n/**\n * Configuration value for server routes configuration.\n * @internal\n */\nexport interface ServerRoutesConfig {\n /**\n * Defines the route to be used as the app shell.\n */\n appShellRoute?: string;\n\n /** List of server routes for the application. */\n routes: ServerRoute[];\n}\n\n/**\n * Token for providing the server routes configuration.\n * @internal\n */\nexport const SERVER_ROUTES_CONFIG = new InjectionToken<ServerRoutesConfig>('SERVER_ROUTES_CONFIG');\n\n/**\n * Configures server-side routing for the application.\n *\n * This function registers an array of `ServerRoute` definitions, enabling server-side rendering\n * for specific URL paths. These routes are used to pre-render content on the server, improving\n * initial load performance and SEO.\n *\n * @param routes - An array of `ServerRoute` objects, each defining a server-rendered route.\n * @returns A `ServerRenderingFeature` object configuring server-side routes.\n *\n * @example\n * ```ts\n * import { provideServerRendering, withRoutes, ServerRoute, RenderMode } from '@angular/ssr';\n *\n * const serverRoutes: ServerRoute[] = [\n * {\n * route: '', // This renders the \"/\" route on the client (CSR)\n * renderMode: RenderMode.Client,\n * },\n * {\n * route: 'about', // This page is static, so we prerender it (SSG)\n * renderMode: RenderMode.Prerender,\n * },\n * {\n * route: 'profile', // This page requires user-specific data, so we use SSR\n * renderMode: RenderMode.Server,\n * },\n * {\n * route: '**', // All other routes will be rendered on the server (SSR)\n * renderMode: RenderMode.Server,\n * },\n * ];\n *\n * provideServerRendering(withRoutes(serverRoutes));\n * ```\n *\n * @see {@link provideServerRendering}\n * @see {@link ServerRoute}\n */\nexport function withRoutes(\n routes: ServerRoute[],\n): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes> {\n const config: ServerRoutesConfig = { routes };\n\n return {\n ɵkind: ServerRenderingFeatureKind.ServerRoutes,\n ɵproviders: [\n {\n provide: SERVER_ROUTES_CONFIG,\n useValue: config,\n },\n ],\n };\n}\n\n/**\n * Configures the shell of the application.\n *\n * The app shell is a minimal, static HTML page that is served immediately, while the\n * full Angular application loads in the background. This improves perceived performance\n * by providing instant feedback to the user.\n *\n * This function configures the app shell route, which serves the provided component for\n * requests that do not match any defined server routes.\n *\n * @param component - The Angular component to render for the app shell. Can be a direct\n * component type or a dynamic import function.\n * @returns A `ServerRenderingFeature` object configuring the app shell.\n *\n * @example\n * ```ts\n * import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';\n * import { AppShellComponent } from './app-shell.component';\n *\n * provideServerRendering(\n * withRoutes(serverRoutes),\n * withAppShell(AppShellComponent)\n * );\n * ```\n *\n * @example\n * ```ts\n * import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';\n *\n * provideServerRendering(\n * withRoutes(serverRoutes),\n * withAppShell(() =>\n * import('./app-shell.component').then((m) => m.AppShellComponent)\n * )\n * );\n * ```\n *\n * @see {@link provideServerRendering}\n * @see {@link https://angular.dev/ecosystem/service-workers/app-shell App shell pattern on Angular.dev}\n */\nexport function withAppShell(\n component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>),\n): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell> {\n const routeConfig: Route = {\n path: APP_SHELL_ROUTE,\n };\n\n if ('ɵcmp' in component) {\n routeConfig.component = component as Type<unknown>;\n } else {\n routeConfig.loadComponent = component as () => Promise<Type<unknown>>;\n }\n\n return {\n ɵkind: ServerRenderingFeatureKind.AppShell,\n ɵproviders: [\n {\n provide: ROUTES,\n useValue: routeConfig,\n multi: true,\n },\n provideEnvironmentInitializer(() => {\n const config = inject(SERVER_ROUTES_CONFIG);\n config.appShellRoute = APP_SHELL_ROUTE;\n }),\n ],\n };\n}\n\n/**\n * Configures server-side rendering for an Angular application.\n *\n * This function sets up the necessary providers for server-side rendering, including\n * support for server routes and app shell. It combines features configured using\n * `withRoutes` and `withAppShell` to provide a comprehensive server-side rendering setup.\n *\n * @param features - Optional features to configure additional server rendering behaviors.\n * @returns An `EnvironmentProviders` instance with the server-side rendering configuration.\n *\n * @example\n * Basic example of how you can enable server-side rendering in your application\n * when using the `bootstrapApplication` function:\n *\n * ```ts\n * import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';\n * import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';\n * import { AppComponent } from './app/app.component';\n * import { SERVER_ROUTES } from './app/app.server.routes';\n * import { AppShellComponent } from './app/app-shell.component';\n *\n * const bootstrap = (context: BootstrapContext) =>\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideServerRendering(\n * withRoutes(SERVER_ROUTES),\n * withAppShell(AppShellComponent),\n * ),\n * ],\n * }, context);\n *\n * export default bootstrap;\n * ```\n * @see {@link withRoutes} configures server-side routing\n * @see {@link withAppShell} configures the application shell\n */\nexport function provideServerRendering(\n ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]\n): EnvironmentProviders {\n let hasAppShell = false;\n let hasServerRoutes = false;\n const providers: (Provider | EnvironmentProviders)[] = [provideServerRenderingPlatformServer()];\n\n for (const { ɵkind, ɵproviders } of features) {\n hasAppShell ||= ɵkind === ServerRenderingFeatureKind.AppShell;\n hasServerRoutes ||= ɵkind === ServerRenderingFeatureKind.ServerRoutes;\n providers.push(...ɵproviders);\n }\n\n if (!hasServerRoutes && hasAppShell) {\n throw new Error(\n `Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'.` +\n `The 'withAppShell()' function requires 'withRoutes()' to be used.`,\n );\n }\n\n return makeEnvironmentProviders(providers);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport { addLeadingSlash } from '../utils/url';\nimport { RenderMode } from './route-config';\n\n/**\n * Represents the serialized format of a route tree as an array of node metadata objects.\n * Each entry in the array corresponds to a specific node's metadata within the route tree.\n */\nexport type SerializableRouteTreeNode = ReadonlyArray<RouteTreeNodeMetadata>;\n\n/**\n * Represents metadata for a route tree node, excluding the 'route' path segment.\n */\nexport type RouteTreeNodeMetadataWithoutRoute = Omit<RouteTreeNodeMetadata, 'route'>;\n\n/**\n * Describes metadata associated with a node in the route tree.\n * This metadata includes information such as the route path and optional redirect instructions.\n */\nexport interface RouteTreeNodeMetadata {\n /**\n * Optional redirect path associated with this node.\n * This defines where to redirect if this route is matched.\n */\n redirectTo?: string;\n\n /**\n * The route path for this node.\n *\n * A \"route\" is a URL path or pattern that is used to navigate to different parts of a web application.\n * It is made up of one or more segments separated by slashes `/`. For instance, in the URL `/products/details/42`,\n * the full route is `/products/details/42`, with segments `products`, `details`, and `42`.\n *\n * Routes define how URLs map to views or components in an application. Each route segment contributes to\n * the overall path that determines which view or component is displayed.\n *\n * - **Static Routes**: These routes have fixed segments. For example, `/about` or `/contact`.\n * - **Parameterized Routes**: These include dynamic segments that act as placeholders, such as `/users/:id`,\n * where `:id` could be any user ID.\n *\n * In the context of `RouteTreeNodeMetadata`, the `route` property represents the complete path that this node\n * in the route tree corresponds to. This path is used to determine how a specific URL in the browser maps to the\n * structure and content of the application.\n */\n route: string;\n\n /**\n * Optional status code to return for this route.\n */\n status?: number;\n\n /**\n * Optional additional headers to include in the response for this route.\n */\n headers?: Record<string, string>;\n\n /**\n * Specifies the rendering mode used for this route.\n */\n renderMode: RenderMode;\n\n /**\n * A list of resource that should be preloaded by the browser.\n */\n preload?: readonly string[];\n}\n\n/**\n * Represents a node within the route tree structure.\n * Each node corresponds to a route segment and may have associated metadata and child nodes.\n * The `AdditionalMetadata` type parameter allows for extending the node metadata with custom data.\n */\ninterface RouteTreeNode<AdditionalMetadata extends Record<string, unknown>> {\n /**\n * A map of child nodes, keyed by their corresponding route segment or wildcard.\n */\n children: Map<string, RouteTreeNode<AdditionalMetadata>>;\n\n /**\n * Optional metadata associated with this node, providing additional information such as redirects.\n */\n metadata?: RouteTreeNodeMetadata & AdditionalMetadata;\n}\n\n/**\n * A route tree implementation that supports efficient route matching, including support for wildcard routes.\n * This structure is useful for organizing and retrieving routes in a hierarchical manner,\n * enabling complex routing scenarios with nested paths.\n *\n * @typeParam AdditionalMetadata - Type of additional metadata that can be associated with route nodes.\n */\nexport class RouteTree<AdditionalMetadata extends Record<string, unknown> = {}> {\n /**\n * The root node of the route tree.\n * All routes are stored and accessed relative to this root node.\n */\n private readonly root = this.createEmptyRouteTreeNode();\n\n /**\n * Inserts a new route into the route tree.\n * The route is broken down into segments, and each segment is added to the tree.\n * Parameterized segments (e.g., :id) are normalized to wildcards (*) for matching purposes.\n *\n * @param route - The route path to insert into the tree.\n * @param metadata - Metadata associated with the route, excluding the route path itself.\n */\n insert(route: string, metadata: RouteTreeNodeMetadataWithoutRoute & AdditionalMetadata): void {\n let node = this.root;\n const segments = this.getPathSegments(route);\n const normalizedSegments: string[] = [];\n\n for (const segment of segments) {\n // Replace parameterized segments (e.g., :id) with a wildcard (*) for matching\n const normalizedSegment = segment[0] === ':' ? '*' : segment;\n let childNode = node.children.get(normalizedSegment);\n if (!childNode) {\n childNode = this.createEmptyRouteTreeNode();\n node.children.set(normalizedSegment, childNode);\n }\n\n node = childNode;\n normalizedSegments.push(normalizedSegment);\n }\n\n // At the leaf node, store the full route and its associated metadata\n node.metadata = {\n ...metadata,\n route: addLeadingSlash(normalizedSegments.join('/')),\n };\n }\n\n /**\n * Matches a given route against the route tree and returns the best matching route's metadata.\n * The best match is determined by the lowest insertion index, meaning the earliest defined route\n * takes precedence.\n *\n * @param route - The route path to match against the route tree.\n * @returns The metadata of the best matching route or `undefined` if no match is found.\n */\n match(route: string): (RouteTreeNodeMetadata & AdditionalMetadata) | undefined {\n const segments = this.getPathSegments(route);\n\n return this.traverseBySegments(segments)?.metadata;\n }\n\n /**\n * Converts the route tree into a serialized format representation.\n * This method converts the route tree into an array of metadata objects that describe the structure of the tree.\n * The array represents the routes in a nested manner where each entry includes the route and its associated metadata.\n *\n * @returns An array of `RouteTreeNodeMetadata` objects representing the route tree structure.\n * Each object includes the `route` and associated metadata of a route.\n */\n toObject(): SerializableRouteTreeNode {\n return Array.from(this.traverse());\n }\n\n /**\n * Constructs a `RouteTree` from an object representation.\n * This method is used to recreate a `RouteTree` instance from an array of metadata objects.\n * The array should be in the format produced by `toObject`, allowing for the reconstruction of the route tree\n * with the same routes and metadata.\n *\n * @param value - An array of `RouteTreeNodeMetadata` objects that represent the serialized format of the route tree.\n * Each object should include a `route` and its associated metadata.\n * @returns A new `RouteTree` instance constructed from the provided metadata objects.\n */\n static fromObject(value: SerializableRouteTreeNode): RouteTree {\n const tree = new RouteTree();\n\n for (const { route, ...metadata } of value) {\n tree.insert(route, metadata);\n }\n\n return tree;\n }\n\n /**\n * A generator function that recursively traverses the route tree and yields the metadata of each node.\n * This allows for easy and efficient iteration over all nodes in the tree.\n *\n * @param node - The current node to start the traversal from. Defaults to the root node of the tree.\n */\n *traverse(node = this.root): Generator<RouteTreeNodeMetadata & AdditionalMetadata> {\n if (node.metadata) {\n yield node.metadata;\n }\n\n for (const childNode of node.children.values()) {\n yield* this.traverse(childNode);\n }\n }\n\n /**\n * Extracts the path segments from a given route string.\n *\n * @param route - The route string from which to extract segments.\n * @returns An array of path segments.\n */\n private getPathSegments(route: string): string[] {\n return route.split('/').filter(Boolean);\n }\n\n /**\n * Recursively traverses the route tree from a given node, attempting to match the remaining route segments.\n * If the node is a leaf node (no more segments to match) and contains metadata, the node is yielded.\n *\n * This function prioritizes exact segment matches first, followed by wildcard matches (`*`),\n * and finally deep wildcard matches (`**`) that consume all segments.\n *\n * @param segments - The array of route path segments to match against the route tree.\n * @param node - The current node in the route tree to start traversal from. Defaults to the root node.\n * @param currentIndex - The index of the segment in `remainingSegments` currently being matched.\n * Defaults to `0` (the first segment).\n *\n * @returns The node that best matches the remaining segments or `undefined` if no match is found.\n */\n private traverseBySegments(\n segments: string[],\n node = this.root,\n currentIndex = 0,\n ): RouteTreeNode<AdditionalMetadata> | undefined {\n if (currentIndex >= segments.length) {\n return node.metadata ? node : node.children.get('**');\n }\n\n if (!node.children.size) {\n return undefined;\n }\n\n const segment = segments[currentIndex];\n\n // 1. Attempt exact match with the current segment.\n const exactMatch = node.children.get(segment);\n if (exactMatch) {\n const match = this.traverseBySegments(segments, exactMatch, currentIndex + 1);\n if (match) {\n return match;\n }\n }\n\n // 2. Attempt wildcard match ('*').\n const wildcardMatch = node.children.get('*');\n if (wildcardMatch) {\n const match = this.traverseBySegments(segments, wildcardMatch, currentIndex + 1);\n if (match) {\n return match;