UNPKG

@angular/ssr

Version:

Angular server side rendering utilities

1 lines 203 kB
{"version":3,"file":"ssr.mjs","sources":["../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/assets.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/console.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/manifest.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/url.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/ng.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/promise.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/redirect.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/routes/route-config.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/routes/route-tree.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/routes/ng-routes.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/hooks.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/routes/router.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/crypto.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/inline-critical-css.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/lru-cache.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/app.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/i18n.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/app-engine.ts","../../../../../../k8-fastbuild-ST-fdfa778d11ba/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 { 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>>;\n\n /**\n * A readonly array of allowed hostnames.\n */\n readonly allowedHosts: Readonly<string[]>;\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.at(-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.at(-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 normalizedParts: string[] = [];\n\n for (const part of parts) {\n if (part === '') {\n // Skip any empty parts\n continue;\n }\n\n let start = 0;\n let end = part.length;\n\n // Use \"Pointers\" to avoid intermediate slices\n while (start < end && part[start] === '/') {\n start++;\n }\n\n while (end > start && part[end - 1] === '/') {\n end--;\n }\n\n if (start < end) {\n normalizedParts.push(part.slice(start, end));\n }\n }\n\n return addLeadingSlash(normalizedParts.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\nconst MATRIX_PARAMS_REGEX = /;[^/]+/g;\n\n/**\n * Removes Angular matrix parameters from a given URL path.\n *\n * This function takes a URL path string and removes any matrix parameters.\n * Matrix parameters are parts of a URL segment that start with a semicolon `;`.\n *\n * @param pathname - The URL path to remove matrix parameters from.\n * @returns The URL path with matrix parameters removed.\n *\n * @example\n * ```ts\n * stripMatrixParams('/path;param=value'); // returns '/path'\n * stripMatrixParams('/path;param=value/to;p=1/resource'); // returns '/path/to/resource'\n * stripMatrixParams('/path/to/resource'); // returns '/path/to/resource'\n * ```\n */\nexport function stripMatrixParams(pathname: string): string {\n // Use a regular expression to remove matrix parameters.\n // This regex finds all occurrences of a semicolon followed by any characters\n return pathname.includes(';') ? pathname.replace(MATRIX_PARAMS_REGEX, '') : pathname;\n}\n\n/**\n * Constructs a decoded URL string from its components.\n *\n * This function joins the pathname (with trailing slash removed), search, and hash,\n * and then decodes the result.\n *\n * @param pathname - The path of the URL.\n * @param search - The query string of the URL (including '?').\n * @param hash - The hash fragment of the URL (including '#').\n * @returns The constructed and decoded URL string.\n */\nexport function constructUrl(pathname: string, search: string, hash: string): string {\n return decodeURIComponent([stripTrailingSlash(pathname), search, hash].join(''));\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 REQUEST,\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 { addTrailingSlash, joinUrlParts, stripIndexHtmlFromURL, stripTrailingSlash } 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 supports both Angular modules and bootstrap functions for application initialization.\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<\n | { hasNavigationError: true }\n | { hasNavigationError: boolean; redirectTo?: string; content: () => Promise<string> }\n> {\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);\n const platformRef = platformServer([\n {\n provide: INITIAL_CONFIG,\n useValue: {\n url: urlToRender.href,\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 // This code protect against app destruction during bootstrapping which is a\n // valid case. We should not assume the `applicationRef` is not in destroyed state.\n // Calling `envInjector.get` would throw `NG0205: Injector has already been destroyed`.\n if (applicationRef.destroyed) {\n return { hasNavigationError: true };\n }\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 requestPrefix =\n envInjector.get(APP_BASE_HREF, null, { optional: true }) ??\n envInjector.get(REQUEST, null, { optional: true })?.headers.get('X-Forwarded-Prefix');\n\n const { pathname, search, hash } = envInjector.get(PlatformLocation);\n const finalUrl = constructSerializedUrl(router, { pathname, search, hash }, requestPrefix);\n const urlToRenderString = constructSerializedUrl(router, urlToRender, requestPrefix);\n\n if (urlToRenderString !== finalUrl) {\n redirectTo = [pathname, search, hash].join('');\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/**\n * Constructs a normalized and serialized URL string from its components.\n *\n * This function uses the provided `Router` instance to parse and serialize the URL,\n * ensuring that the resulting string is consistent with the router's configuration.\n * It also handles the optional `prefix` parameter to ensure proper URL construction.\n *\n * @param router - The `Router` instance to use for parsing and serializing the URL.\n * @param url - An object containing the URL components:\n * - `pathname`: The path of the URL.\n * - `search`: The query string of the URL (including '?').\n * - `hash`: The hash fragment of the URL (including '#').\n * @param prefix - An optional prefix (e.g., `APP_BASE_HREF`) to prepend to the pathname\n * if it is not already present.\n * @returns The normalized and serialized URL string.\n *\n * @note\n * We use the Angular `Router` to construct the URL, so that the URL is consistent with the router's configuration.\n * This is important for the URL to be correctly parsed and serialized by the router as it might have different encodings.\n */\nfunction constructSerializedUrl(\n router: Router,\n url: { pathname: string; search: string; hash: string },\n prefix?: string | null,\n): string {\n const { pathname, hash, search } = url;\n const urlParts: string[] = [];\n if (prefix && !addTrailingSlash(pathname).startsWith(addTrailingSlash(prefix))) {\n urlParts.push(joinUrlParts(prefix, pathname));\n } else {\n urlParts.push(stripTrailingSlash(pathname));\n }\n\n urlParts.push(search, hash);\n\n const urlTree = router.parseUrl(urlParts.join(''));\n\n return router.serializeUrl(urlTree);\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\n/**\n * An set of HTTP status codes that are considered valid for redirect responses.\n */\nexport const VALID_REDIRECT_RESPONSE_CODES: ReadonlySet<number> = new Set([\n 301, 302, 303, 307, 308,\n]);\n\n/**\n * Checks if the given HTTP status code is a valid redirect response code.\n *\n * @param code The HTTP status code to check.\n * @returns `true` if the code is a valid redirect response code, `false` otherwise.\n */\nexport function isValidRedirectResponseCode(code: number): boolean {\n return VALID_REDIRECT_RESPONSE_CODES.has(code);\n}\n\n/**\n * Creates an HTTP redirect response with a specified location and status code.\n *\n * @param location - The URL to which the response should redirect.\n * @param status - The HTTP status code for the redirection. Defaults to 302 (Found).\n * See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status\n * @param headers - Additional headers to include in the response.\n * @returns A `Response` object representing the HTTP redirect.\n */\nexport function createRedirectResponse(\n location: string,\n status = 302,\n headers?: Record<string, string> | Headers,\n): Response {\n if (ngDevMode && !isValidRedirectResponseCode(status)) {\n throw new Error(\n `Invalid redirect status code: ${status}. ` +\n `Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,\n );\n }\n\n const resHeaders = headers instanceof Headers ? headers : new Headers(headers);\n if (ngDevMode && resHeaders.has('location')) {\n // eslint-disable-next-line no-console\n console.warn(\n `Location header \"${resHeaders.get('location')}\" will be ignored and set to \"${location}\".`,\n );\n }\n\n // Ensure unique values for Vary header\n const varyArray = resHeaders.get('Vary')?.split(',') ?? [];\n const varySet = new Set(['X-Forwarded-Prefix']);\n for (const vary of varyArray) {\n const value = vary.trim();\n\n if (value) {\n varySet.add(value);\n }\n }\n\n resHeaders.set('Vary', [...varySet].join(', '));\n resHeaders.set('Location', location);\n\n return new Response(null, {\n status,\n headers: resHeaders,\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 * path: '', // This renders the \"/\" route on the client (CSR)\n * renderMode: RenderMode.Client,\n * },\n * {\n * path: 'about', // This page is static, so we prerender it (SSG)\n * renderMode: RenderMode.Prerender,\n * },\n * {\n * path: 'profile', // This page requires user-specific data, so we use SSR\n * renderMode: RenderMode.Server,\n * },\n * {\n * path: '**', // 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 * Options for configuring server-side rendering.\n */\nexport interface ServerRenderingOptions {\n /**\n * The maximum allowed response body size when using the Fetch API.\n * @default 1MB\n */\n maxResponseBodySize: number;\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\n/**\n * Configures server-side rendering for an Angular application with additional options.\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 options - Configuration options for server-side rendering.\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 with options 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 * { maxResponseBodySize: 1024 * 1024 }, // 1MB limit\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 options: ServerRenderingOptions,\n ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]\n): EnvironmentProviders;\nexport function provideServerRendering(\n ...args:\n | ServerRenderingFeature<ServerRenderingFeatureKind>[]\n | [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]]\n): EnvironmentProviders {\n let options: ServerRenderingOptions | undefined;\n let features: ServerRenderingFeature<ServerRenderingFeatureKind>[];\n if (hasOptions(args)) {\n const [first, ...rest] = args;\n options = first;\n features = rest;\n } else {\n features = args;\n }\n\n const providers: (Provider | EnvironmentProviders)[] = [\n provideServerRenderingPlatformServer(options),\n ];\n\n let hasAppShell = false;\n let hasServerRoutes = false;\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/**\n * Checks if the first element of args is a `ServerRenderingOptions` object.\n */\nfunction hasOptions(\n args:\n | ServerRenderingFeature<ServerRenderingFeatureKind>[]\n | [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]],\n): args is [ServerRenderingOptions, ...ServerRenderingFeature<ServerRenderingFeatureKind>[]] {\n const value = args[0];\n\n return !!value && typeof value === 'object' && !('ɵkind' in value);\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, excludi