@angular/ssr 19.0.5 → 19.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/node.mjs.map +1 -1
- package/fesm2022/ssr.mjs +263 -15
- package/fesm2022/ssr.mjs.map +1 -1
- package/index.d.ts +48 -4
- package/package.json +16 -11
- package/third_party/beasties/index.js +95 -31
- package/third_party/beasties/index.js.map +1 -1
package/fesm2022/node.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.mjs","sources":["../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/inline-css-processor.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/peformance-profiler.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/common-engine.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/request.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/app-engine.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/handler.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/response.mjs","../../../../../../darwin_arm64-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/module.mjs"],"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 */\nimport { ɵInlineCriticalCssProcessor as InlineCriticalCssProcessor } from '@angular/ssr';\nimport { readFile } from 'node:fs/promises';\nexport class CommonEngineInlineCriticalCssProcessor {\n resourceCache = new Map();\n async process(html, outputPath) {\n const beasties = new InlineCriticalCssProcessor(async (path) => {\n let resourceContent = this.resourceCache.get(path);\n if (resourceContent === undefined) {\n resourceContent = await readFile(path, 'utf-8');\n this.resourceCache.set(path, resourceContent);\n }\n return resourceContent;\n }, outputPath);\n return beasties.process(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 */\nconst PERFORMANCE_MARK_PREFIX = '🅰️';\nexport function printPerformanceLogs() {\n let maxWordLength = 0;\n const benchmarks = [];\n for (const { name, duration } of performance.getEntriesByType('measure')) {\n if (!name.startsWith(PERFORMANCE_MARK_PREFIX)) {\n continue;\n }\n // `🅰️:Retrieve SSG Page` -> `Retrieve SSG Page:`\n const step = name.slice(PERFORMANCE_MARK_PREFIX.length + 1) + ':';\n if (step.length > maxWordLength) {\n maxWordLength = step.length;\n }\n benchmarks.push([step, `${duration.toFixed(1)}ms`]);\n performance.clearMeasures(name);\n }\n /* eslint-disable no-console */\n console.log('********** Performance results **********');\n for (const [step, value] of benchmarks) {\n const spaces = maxWordLength - step.length + 5;\n console.log(step + ' '.repeat(spaces) + value);\n }\n console.log('*****************************************');\n /* eslint-enable no-console */\n}\nexport async function runMethodAndMeasurePerf(label, asyncMethod) {\n const labelName = `${PERFORMANCE_MARK_PREFIX}:${label}`;\n const startLabel = `start:${labelName}`;\n const endLabel = `end:${labelName}`;\n try {\n performance.mark(startLabel);\n return await asyncMethod();\n }\n finally {\n performance.mark(endLabel);\n performance.measure(labelName, startLabel, endLabel);\n performance.clearMarks(startLabel);\n performance.clearMarks(endLabel);\n }\n}\nexport function noopRunMethodAndMeasurePerf(label, asyncMethod) {\n return asyncMethod();\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 */\nimport { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';\nimport * as fs from 'node:fs';\nimport { dirname, join, normalize, resolve } from 'node:path';\nimport { URL } from 'node:url';\nimport { CommonEngineInlineCriticalCssProcessor } from './inline-css-processor';\nimport { noopRunMethodAndMeasurePerf, printPerformanceLogs, runMethodAndMeasurePerf, } from './peformance-profiler';\nconst SSG_MARKER_REGEXP = /ng-server-context=[\"']\\w*\\|?ssg\\|?\\w*[\"']/;\n/**\n * A common engine to use to server render an application.\n */\nexport class CommonEngine {\n options;\n templateCache = new Map();\n inlineCriticalCssProcessor = new CommonEngineInlineCriticalCssProcessor();\n pageIsSSG = new Map();\n constructor(options) {\n this.options = options;\n }\n /**\n * Render an HTML document for a specific URL with specified\n * render options\n */\n async render(opts) {\n const enablePerformanceProfiler = this.options?.enablePerformanceProfiler;\n const runMethod = enablePerformanceProfiler\n ? runMethodAndMeasurePerf\n : noopRunMethodAndMeasurePerf;\n let html = await runMethod('Retrieve SSG Page', () => this.retrieveSSGPage(opts));\n if (html === undefined) {\n html = await runMethod('Render Page', () => this.renderApplication(opts));\n if (opts.inlineCriticalCss !== false) {\n const content = await runMethod('Inline Critical CSS', () => \n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.inlineCriticalCss(html, opts));\n html = content;\n }\n }\n if (enablePerformanceProfiler) {\n printPerformanceLogs();\n }\n return html;\n }\n inlineCriticalCss(html, opts) {\n const outputPath = opts.publicPath ?? (opts.documentFilePath ? dirname(opts.documentFilePath) : '');\n return this.inlineCriticalCssProcessor.process(html, outputPath);\n }\n async retrieveSSGPage(opts) {\n const { publicPath, documentFilePath, url } = opts;\n if (!publicPath || !documentFilePath || url === undefined) {\n return undefined;\n }\n const { pathname } = new URL(url, 'resolve://');\n // Do not use `resolve` here as otherwise it can lead to path traversal vulnerability.\n // See: https://portswigger.net/web-security/file-path-traversal\n const pagePath = join(publicPath, pathname, 'index.html');\n if (this.pageIsSSG.get(pagePath)) {\n // Serve pre-rendered page.\n return fs.promises.readFile(pagePath, 'utf-8');\n }\n if (!pagePath.startsWith(normalize(publicPath))) {\n // Potential path traversal detected.\n return undefined;\n }\n if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {\n // View matches with prerender path or file does not exist.\n this.pageIsSSG.set(pagePath, false);\n return undefined;\n }\n // Static file exists.\n const content = await fs.promises.readFile(pagePath, 'utf-8');\n const isSSG = SSG_MARKER_REGEXP.test(content);\n this.pageIsSSG.set(pagePath, isSSG);\n return isSSG ? content : undefined;\n }\n async renderApplication(opts) {\n const moduleOrFactory = this.options?.bootstrap ?? opts.bootstrap;\n if (!moduleOrFactory) {\n throw new Error('A module or bootstrap option must be provided.');\n }\n const extraProviders = [\n { provide: ɵSERVER_CONTEXT, useValue: 'ssr' },\n ...(opts.providers ?? []),\n ...(this.options?.providers ?? []),\n ];\n let document = opts.document;\n if (!document && opts.documentFilePath) {\n document = await this.getDocument(opts.documentFilePath);\n }\n const commonRenderingOptions = {\n url: opts.url,\n document,\n };\n return isBootstrapFn(moduleOrFactory)\n ? renderApplication(moduleOrFactory, {\n platformProviders: extraProviders,\n ...commonRenderingOptions,\n })\n : renderModule(moduleOrFactory, { extraProviders, ...commonRenderingOptions });\n }\n /** Retrieve the document from the cache or the filesystem */\n async getDocument(filePath) {\n let doc = this.templateCache.get(filePath);\n if (!doc) {\n doc = await fs.promises.readFile(filePath, 'utf-8');\n this.templateCache.set(filePath, doc);\n }\n return doc;\n }\n}\nasync function exists(path) {\n try {\n await fs.promises.access(path, fs.constants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\nfunction isBootstrapFn(value) {\n // We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:\n return typeof value === 'function' && !('ɵmod' 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/**\n * A set containing all the pseudo-headers defined in the HTTP/2 specification.\n *\n * This set can be used to filter out pseudo-headers from a list of headers,\n * as they are not allowed to be set directly using the `Node.js` Undici API or\n * the web `Headers` API.\n */\nconst HTTP2_PSEUDO_HEADERS = new Set([':method', ':scheme', ':authority', ':path', ':status']);\n/**\n * Converts a Node.js `IncomingMessage` or `Http2ServerRequest` into a\n * Web Standard `Request` object.\n *\n * This function adapts the Node.js request objects to a format that can\n * be used by web platform APIs.\n *\n * @param nodeRequest - The Node.js request object (`IncomingMessage` or `Http2ServerRequest`) to convert.\n * @returns A Web Standard `Request` object.\n * @developerPreview\n */\nexport function createWebRequestFromNodeRequest(nodeRequest) {\n const { headers, method = 'GET' } = nodeRequest;\n const withBody = method !== 'GET' && method !== 'HEAD';\n return new Request(createRequestUrl(nodeRequest), {\n method,\n headers: createRequestHeaders(headers),\n body: withBody ? nodeRequest : undefined,\n duplex: withBody ? 'half' : undefined,\n });\n}\n/**\n * Creates a `Headers` object from Node.js `IncomingHttpHeaders`.\n *\n * @param nodeHeaders - The Node.js `IncomingHttpHeaders` object to convert.\n * @returns A `Headers` object containing the converted headers.\n */\nfunction createRequestHeaders(nodeHeaders) {\n const headers = new Headers();\n for (const [name, value] of Object.entries(nodeHeaders)) {\n if (HTTP2_PSEUDO_HEADERS.has(name)) {\n continue;\n }\n if (typeof value === 'string') {\n headers.append(name, value);\n }\n else if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n }\n }\n return headers;\n}\n/**\n * Creates a `URL` object from a Node.js `IncomingMessage`, taking into account the protocol, host, and port.\n *\n * @param nodeRequest - The Node.js `IncomingMessage` or `Http2ServerRequest` object to extract URL information from.\n * @returns A `URL` object representing the request URL.\n */\nfunction createRequestUrl(nodeRequest) {\n const { headers, socket, url = '', originalUrl, } = nodeRequest;\n const protocol = headers['x-forwarded-proto'] ?? ('encrypted' in socket && socket.encrypted ? 'https' : 'http');\n const hostname = headers['x-forwarded-host'] ?? headers.host ?? headers[':authority'];\n const port = headers['x-forwarded-port'] ?? socket.localPort;\n if (Array.isArray(hostname)) {\n throw new Error('host value cannot be an array.');\n }\n let hostnameWithPort = hostname;\n if (port && !hostname?.includes(':')) {\n hostnameWithPort += `:${port}`;\n }\n return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);\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 */\nimport { AngularAppEngine } from '@angular/ssr';\nimport { createWebRequestFromNodeRequest } from './request';\n/**\n * Angular server application engine.\n * Manages Angular server applications (including localized ones), handles rendering requests,\n * and optionally transforms index HTML before rendering.\n *\n * @remarks This class should be instantiated once and used as a singleton across the server-side\n * application to ensure consistent handling of rendering requests and resource management.\n *\n * @developerPreview\n */\nexport class AngularNodeAppEngine {\n angularAppEngine = new AngularAppEngine();\n /**\n * Handles an incoming HTTP request by serving prerendered content, performing server-side rendering,\n * or delivering a static file for client-side rendered routes based on the `RenderMode` setting.\n *\n * This method adapts Node.js's `IncomingMessage` or `Http2ServerRequest`\n * to a format compatible with the `AngularAppEngine` and delegates the handling logic to it.\n *\n * @param request - The incoming HTTP request (`IncomingMessage` or `Http2ServerRequest`).\n * @param requestContext - Optional context for rendering, such as metadata associated with the request.\n * @returns A promise that resolves to the resulting HTTP response object, or `null` if no matching Angular route is found.\n *\n * @remarks A request to `https://www.example.com/page/index.html` will serve or render the Angular route\n * corresponding to `https://www.example.com/page`.\n */\n async handle(request, requestContext) {\n const webRequest = createWebRequestFromNodeRequest(request);\n return this.angularAppEngine.handle(webRequest, requestContext);\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 * Attaches metadata to the handler function to mark it as a special handler for Node.js environments.\n *\n * @typeParam T - The type of the handler function.\n * @param handler - The handler function to be defined and annotated.\n * @returns The same handler function passed as an argument, with metadata attached.\n *\n * @example\n * Usage in an Express application:\n * ```ts\n * const app = express();\n * export default createNodeRequestHandler(app);\n * ```\n *\n * @example\n * Usage in a Hono application:\n * ```ts\n * const app = new Hono();\n * export default createNodeRequestHandler(async (req, res, next) => {\n * try {\n * const webRes = await app.fetch(createWebRequestFromNodeRequest(req));\n * if (webRes) {\n * await writeResponseToNodeResponse(webRes, res);\n * } else {\n * next();\n * }\n * } catch (error) {\n * next(error);\n * }\n * }));\n * ```\n *\n * @example\n * Usage in a Fastify application:\n * ```ts\n * const app = Fastify();\n * export default createNodeRequestHandler(async (req, res) => {\n * await app.ready();\n * app.server.emit('request', req, res);\n * res.send('Hello from Fastify with Node Next Handler!');\n * }));\n * ```\n * @developerPreview\n */\nexport function createNodeRequestHandler(handler) {\n handler['__ng_node_request_handler__'] = true;\n return handler;\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 * Streams a web-standard `Response` into a Node.js `ServerResponse`\n * or `Http2ServerResponse`.\n *\n * This function adapts the web `Response` object to write its content\n * to a Node.js response object, handling both HTTP/1.1 and HTTP/2.\n *\n * @param source - The web-standard `Response` object to stream from.\n * @param destination - The Node.js response object (`ServerResponse` or `Http2ServerResponse`) to stream into.\n * @returns A promise that resolves once the streaming operation is complete.\n * @developerPreview\n */\nexport async function writeResponseToNodeResponse(source, destination) {\n const { status, headers, body } = source;\n destination.statusCode = status;\n let cookieHeaderSet = false;\n for (const [name, value] of headers.entries()) {\n if (name === 'set-cookie') {\n if (cookieHeaderSet) {\n continue;\n }\n // Sets the 'set-cookie' header only once to ensure it is correctly applied.\n // Concatenating 'set-cookie' values can lead to incorrect behavior, so we use a single value from `headers.getSetCookie()`.\n destination.setHeader(name, headers.getSetCookie());\n cookieHeaderSet = true;\n }\n else {\n destination.setHeader(name, value);\n }\n }\n if (!body) {\n destination.end();\n return;\n }\n try {\n const reader = body.getReader();\n destination.on('close', () => {\n reader.cancel().catch((error) => {\n // eslint-disable-next-line no-console\n console.error(`An error occurred while writing the response body for: ${destination.req.url}.`, error);\n });\n });\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n destination.end();\n break;\n }\n destination.write(value);\n }\n }\n catch {\n destination.end('Internal server error.');\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 */\nimport { argv } from 'node:process';\nimport { fileURLToPath } from 'node:url';\n/**\n * Determines whether the provided URL represents the main entry point module.\n *\n * This function checks if the provided URL corresponds to the main ESM module being executed directly.\n * It's useful for conditionally executing code that should only run when a module is the entry point,\n * such as starting a server or initializing an application.\n *\n * It performs two key checks:\n * 1. Verifies if the URL starts with 'file:', ensuring it is a local file.\n * 2. Compares the URL's resolved file path with the first command-line argument (`process.argv[1]`),\n * which points to the file being executed.\n *\n * @param url The URL of the module to check. This should typically be `import.meta.url`.\n * @returns `true` if the provided URL represents the main entry point, otherwise `false`.\n * @developerPreview\n */\nexport function isMainModule(url) {\n return url.startsWith('file:') && argv[1] === fileURLToPath(url);\n}\n"],"names":["InlineCriticalCssProcessor","URL","ɵSERVER_CONTEXT"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.mjs","sources":["../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/inline-css-processor.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/peformance-profiler.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/common-engine/common-engine.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/request.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/app-engine.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/handler.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/response.mjs","../../../../../../k8-fastbuild-ST-70f2edae98f4/bin/packages/angular/ssr/node/src/module.mjs"],"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 */\nimport { ɵInlineCriticalCssProcessor as InlineCriticalCssProcessor } from '@angular/ssr';\nimport { readFile } from 'node:fs/promises';\nexport class CommonEngineInlineCriticalCssProcessor {\n resourceCache = new Map();\n async process(html, outputPath) {\n const beasties = new InlineCriticalCssProcessor(async (path) => {\n let resourceContent = this.resourceCache.get(path);\n if (resourceContent === undefined) {\n resourceContent = await readFile(path, 'utf-8');\n this.resourceCache.set(path, resourceContent);\n }\n return resourceContent;\n }, outputPath);\n return beasties.process(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 */\nconst PERFORMANCE_MARK_PREFIX = '🅰️';\nexport function printPerformanceLogs() {\n let maxWordLength = 0;\n const benchmarks = [];\n for (const { name, duration } of performance.getEntriesByType('measure')) {\n if (!name.startsWith(PERFORMANCE_MARK_PREFIX)) {\n continue;\n }\n // `🅰️:Retrieve SSG Page` -> `Retrieve SSG Page:`\n const step = name.slice(PERFORMANCE_MARK_PREFIX.length + 1) + ':';\n if (step.length > maxWordLength) {\n maxWordLength = step.length;\n }\n benchmarks.push([step, `${duration.toFixed(1)}ms`]);\n performance.clearMeasures(name);\n }\n /* eslint-disable no-console */\n console.log('********** Performance results **********');\n for (const [step, value] of benchmarks) {\n const spaces = maxWordLength - step.length + 5;\n console.log(step + ' '.repeat(spaces) + value);\n }\n console.log('*****************************************');\n /* eslint-enable no-console */\n}\nexport async function runMethodAndMeasurePerf(label, asyncMethod) {\n const labelName = `${PERFORMANCE_MARK_PREFIX}:${label}`;\n const startLabel = `start:${labelName}`;\n const endLabel = `end:${labelName}`;\n try {\n performance.mark(startLabel);\n return await asyncMethod();\n }\n finally {\n performance.mark(endLabel);\n performance.measure(labelName, startLabel, endLabel);\n performance.clearMarks(startLabel);\n performance.clearMarks(endLabel);\n }\n}\nexport function noopRunMethodAndMeasurePerf(label, asyncMethod) {\n return asyncMethod();\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 */\nimport { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';\nimport * as fs from 'node:fs';\nimport { dirname, join, normalize, resolve } from 'node:path';\nimport { URL } from 'node:url';\nimport { CommonEngineInlineCriticalCssProcessor } from './inline-css-processor';\nimport { noopRunMethodAndMeasurePerf, printPerformanceLogs, runMethodAndMeasurePerf, } from './peformance-profiler';\nconst SSG_MARKER_REGEXP = /ng-server-context=[\"']\\w*\\|?ssg\\|?\\w*[\"']/;\n/**\n * A common engine to use to server render an application.\n */\nexport class CommonEngine {\n options;\n templateCache = new Map();\n inlineCriticalCssProcessor = new CommonEngineInlineCriticalCssProcessor();\n pageIsSSG = new Map();\n constructor(options) {\n this.options = options;\n }\n /**\n * Render an HTML document for a specific URL with specified\n * render options\n */\n async render(opts) {\n const enablePerformanceProfiler = this.options?.enablePerformanceProfiler;\n const runMethod = enablePerformanceProfiler\n ? runMethodAndMeasurePerf\n : noopRunMethodAndMeasurePerf;\n let html = await runMethod('Retrieve SSG Page', () => this.retrieveSSGPage(opts));\n if (html === undefined) {\n html = await runMethod('Render Page', () => this.renderApplication(opts));\n if (opts.inlineCriticalCss !== false) {\n const content = await runMethod('Inline Critical CSS', () => \n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.inlineCriticalCss(html, opts));\n html = content;\n }\n }\n if (enablePerformanceProfiler) {\n printPerformanceLogs();\n }\n return html;\n }\n inlineCriticalCss(html, opts) {\n const outputPath = opts.publicPath ?? (opts.documentFilePath ? dirname(opts.documentFilePath) : '');\n return this.inlineCriticalCssProcessor.process(html, outputPath);\n }\n async retrieveSSGPage(opts) {\n const { publicPath, documentFilePath, url } = opts;\n if (!publicPath || !documentFilePath || url === undefined) {\n return undefined;\n }\n const { pathname } = new URL(url, 'resolve://');\n // Do not use `resolve` here as otherwise it can lead to path traversal vulnerability.\n // See: https://portswigger.net/web-security/file-path-traversal\n const pagePath = join(publicPath, pathname, 'index.html');\n if (this.pageIsSSG.get(pagePath)) {\n // Serve pre-rendered page.\n return fs.promises.readFile(pagePath, 'utf-8');\n }\n if (!pagePath.startsWith(normalize(publicPath))) {\n // Potential path traversal detected.\n return undefined;\n }\n if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {\n // View matches with prerender path or file does not exist.\n this.pageIsSSG.set(pagePath, false);\n return undefined;\n }\n // Static file exists.\n const content = await fs.promises.readFile(pagePath, 'utf-8');\n const isSSG = SSG_MARKER_REGEXP.test(content);\n this.pageIsSSG.set(pagePath, isSSG);\n return isSSG ? content : undefined;\n }\n async renderApplication(opts) {\n const moduleOrFactory = this.options?.bootstrap ?? opts.bootstrap;\n if (!moduleOrFactory) {\n throw new Error('A module or bootstrap option must be provided.');\n }\n const extraProviders = [\n { provide: ɵSERVER_CONTEXT, useValue: 'ssr' },\n ...(opts.providers ?? []),\n ...(this.options?.providers ?? []),\n ];\n let document = opts.document;\n if (!document && opts.documentFilePath) {\n document = await this.getDocument(opts.documentFilePath);\n }\n const commonRenderingOptions = {\n url: opts.url,\n document,\n };\n return isBootstrapFn(moduleOrFactory)\n ? renderApplication(moduleOrFactory, {\n platformProviders: extraProviders,\n ...commonRenderingOptions,\n })\n : renderModule(moduleOrFactory, { extraProviders, ...commonRenderingOptions });\n }\n /** Retrieve the document from the cache or the filesystem */\n async getDocument(filePath) {\n let doc = this.templateCache.get(filePath);\n if (!doc) {\n doc = await fs.promises.readFile(filePath, 'utf-8');\n this.templateCache.set(filePath, doc);\n }\n return doc;\n }\n}\nasync function exists(path) {\n try {\n await fs.promises.access(path, fs.constants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\nfunction isBootstrapFn(value) {\n // We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:\n return typeof value === 'function' && !('ɵmod' 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/**\n * A set containing all the pseudo-headers defined in the HTTP/2 specification.\n *\n * This set can be used to filter out pseudo-headers from a list of headers,\n * as they are not allowed to be set directly using the `Node.js` Undici API or\n * the web `Headers` API.\n */\nconst HTTP2_PSEUDO_HEADERS = new Set([':method', ':scheme', ':authority', ':path', ':status']);\n/**\n * Converts a Node.js `IncomingMessage` or `Http2ServerRequest` into a\n * Web Standard `Request` object.\n *\n * This function adapts the Node.js request objects to a format that can\n * be used by web platform APIs.\n *\n * @param nodeRequest - The Node.js request object (`IncomingMessage` or `Http2ServerRequest`) to convert.\n * @returns A Web Standard `Request` object.\n * @developerPreview\n */\nexport function createWebRequestFromNodeRequest(nodeRequest) {\n const { headers, method = 'GET' } = nodeRequest;\n const withBody = method !== 'GET' && method !== 'HEAD';\n return new Request(createRequestUrl(nodeRequest), {\n method,\n headers: createRequestHeaders(headers),\n body: withBody ? nodeRequest : undefined,\n duplex: withBody ? 'half' : undefined,\n });\n}\n/**\n * Creates a `Headers` object from Node.js `IncomingHttpHeaders`.\n *\n * @param nodeHeaders - The Node.js `IncomingHttpHeaders` object to convert.\n * @returns A `Headers` object containing the converted headers.\n */\nfunction createRequestHeaders(nodeHeaders) {\n const headers = new Headers();\n for (const [name, value] of Object.entries(nodeHeaders)) {\n if (HTTP2_PSEUDO_HEADERS.has(name)) {\n continue;\n }\n if (typeof value === 'string') {\n headers.append(name, value);\n }\n else if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n }\n }\n return headers;\n}\n/**\n * Creates a `URL` object from a Node.js `IncomingMessage`, taking into account the protocol, host, and port.\n *\n * @param nodeRequest - The Node.js `IncomingMessage` or `Http2ServerRequest` object to extract URL information from.\n * @returns A `URL` object representing the request URL.\n */\nfunction createRequestUrl(nodeRequest) {\n const { headers, socket, url = '', originalUrl, } = nodeRequest;\n const protocol = headers['x-forwarded-proto'] ?? ('encrypted' in socket && socket.encrypted ? 'https' : 'http');\n const hostname = headers['x-forwarded-host'] ?? headers.host ?? headers[':authority'];\n const port = headers['x-forwarded-port'] ?? socket.localPort;\n if (Array.isArray(hostname)) {\n throw new Error('host value cannot be an array.');\n }\n let hostnameWithPort = hostname;\n if (port && !hostname?.includes(':')) {\n hostnameWithPort += `:${port}`;\n }\n return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);\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 */\nimport { AngularAppEngine } from '@angular/ssr';\nimport { createWebRequestFromNodeRequest } from './request';\n/**\n * Angular server application engine.\n * Manages Angular server applications (including localized ones), handles rendering requests,\n * and optionally transforms index HTML before rendering.\n *\n * @remarks This class should be instantiated once and used as a singleton across the server-side\n * application to ensure consistent handling of rendering requests and resource management.\n *\n * @developerPreview\n */\nexport class AngularNodeAppEngine {\n angularAppEngine = new AngularAppEngine();\n /**\n * Handles an incoming HTTP request by serving prerendered content, performing server-side rendering,\n * or delivering a static file for client-side rendered routes based on the `RenderMode` setting.\n *\n * This method adapts Node.js's `IncomingMessage` or `Http2ServerRequest`\n * to a format compatible with the `AngularAppEngine` and delegates the handling logic to it.\n *\n * @param request - The incoming HTTP request (`IncomingMessage` or `Http2ServerRequest`).\n * @param requestContext - Optional context for rendering, such as metadata associated with the request.\n * @returns A promise that resolves to the resulting HTTP response object, or `null` if no matching Angular route is found.\n *\n * @remarks A request to `https://www.example.com/page/index.html` will serve or render the Angular route\n * corresponding to `https://www.example.com/page`.\n */\n async handle(request, requestContext) {\n const webRequest = createWebRequestFromNodeRequest(request);\n return this.angularAppEngine.handle(webRequest, requestContext);\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 * Attaches metadata to the handler function to mark it as a special handler for Node.js environments.\n *\n * @typeParam T - The type of the handler function.\n * @param handler - The handler function to be defined and annotated.\n * @returns The same handler function passed as an argument, with metadata attached.\n *\n * @example\n * Usage in an Express application:\n * ```ts\n * const app = express();\n * export default createNodeRequestHandler(app);\n * ```\n *\n * @example\n * Usage in a Hono application:\n * ```ts\n * const app = new Hono();\n * export default createNodeRequestHandler(async (req, res, next) => {\n * try {\n * const webRes = await app.fetch(createWebRequestFromNodeRequest(req));\n * if (webRes) {\n * await writeResponseToNodeResponse(webRes, res);\n * } else {\n * next();\n * }\n * } catch (error) {\n * next(error);\n * }\n * }));\n * ```\n *\n * @example\n * Usage in a Fastify application:\n * ```ts\n * const app = Fastify();\n * export default createNodeRequestHandler(async (req, res) => {\n * await app.ready();\n * app.server.emit('request', req, res);\n * res.send('Hello from Fastify with Node Next Handler!');\n * }));\n * ```\n * @developerPreview\n */\nexport function createNodeRequestHandler(handler) {\n handler['__ng_node_request_handler__'] = true;\n return handler;\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 * Streams a web-standard `Response` into a Node.js `ServerResponse`\n * or `Http2ServerResponse`.\n *\n * This function adapts the web `Response` object to write its content\n * to a Node.js response object, handling both HTTP/1.1 and HTTP/2.\n *\n * @param source - The web-standard `Response` object to stream from.\n * @param destination - The Node.js response object (`ServerResponse` or `Http2ServerResponse`) to stream into.\n * @returns A promise that resolves once the streaming operation is complete.\n * @developerPreview\n */\nexport async function writeResponseToNodeResponse(source, destination) {\n const { status, headers, body } = source;\n destination.statusCode = status;\n let cookieHeaderSet = false;\n for (const [name, value] of headers.entries()) {\n if (name === 'set-cookie') {\n if (cookieHeaderSet) {\n continue;\n }\n // Sets the 'set-cookie' header only once to ensure it is correctly applied.\n // Concatenating 'set-cookie' values can lead to incorrect behavior, so we use a single value from `headers.getSetCookie()`.\n destination.setHeader(name, headers.getSetCookie());\n cookieHeaderSet = true;\n }\n else {\n destination.setHeader(name, value);\n }\n }\n if (!body) {\n destination.end();\n return;\n }\n try {\n const reader = body.getReader();\n destination.on('close', () => {\n reader.cancel().catch((error) => {\n // eslint-disable-next-line no-console\n console.error(`An error occurred while writing the response body for: ${destination.req.url}.`, error);\n });\n });\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n destination.end();\n break;\n }\n destination.write(value);\n }\n }\n catch {\n destination.end('Internal server error.');\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 */\nimport { argv } from 'node:process';\nimport { fileURLToPath } from 'node:url';\n/**\n * Determines whether the provided URL represents the main entry point module.\n *\n * This function checks if the provided URL corresponds to the main ESM module being executed directly.\n * It's useful for conditionally executing code that should only run when a module is the entry point,\n * such as starting a server or initializing an application.\n *\n * It performs two key checks:\n * 1. Verifies if the URL starts with 'file:', ensuring it is a local file.\n * 2. Compares the URL's resolved file path with the first command-line argument (`process.argv[1]`),\n * which points to the file being executed.\n *\n * @param url The URL of the module to check. This should typically be `import.meta.url`.\n * @returns `true` if the provided URL represents the main entry point, otherwise `false`.\n * @developerPreview\n */\nexport function isMainModule(url) {\n return url.startsWith('file:') && argv[1] === fileURLToPath(url);\n}\n"],"names":["InlineCriticalCssProcessor","URL","ɵSERVER_CONTEXT"],"mappings":"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"}
|
package/fesm2022/ssr.mjs
CHANGED
|
@@ -655,6 +655,11 @@ class RouteTree {
|
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
/**
|
|
659
|
+
* The maximum number of module preload link elements that should be added for
|
|
660
|
+
* initial scripts.
|
|
661
|
+
*/
|
|
662
|
+
const MODULE_PRELOAD_MAX = 10;
|
|
658
663
|
/**
|
|
659
664
|
* Regular expression to match segments preceded by a colon in a string.
|
|
660
665
|
*/
|
|
@@ -673,10 +678,10 @@ const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
|
|
|
673
678
|
* @returns An async iterable iterator yielding either route tree node metadata or an error object with an error message.
|
|
674
679
|
*/
|
|
675
680
|
async function* traverseRoutesConfig(options) {
|
|
676
|
-
const { routes, compiler, parentInjector, parentRoute, serverConfigRouteTree, invokeGetPrerenderParams, includePrerenderFallbackRoutes, } = options;
|
|
681
|
+
const { routes, compiler, parentInjector, parentRoute, serverConfigRouteTree, entryPointToBrowserMapping, parentPreloads, invokeGetPrerenderParams, includePrerenderFallbackRoutes, } = options;
|
|
677
682
|
for (const route of routes) {
|
|
678
683
|
try {
|
|
679
|
-
const { path = '', redirectTo, loadChildren, children } = route;
|
|
684
|
+
const { path = '', redirectTo, loadChildren, loadComponent, children, ɵentryName } = route;
|
|
680
685
|
const currentRoutePath = joinUrlParts(parentRoute, path);
|
|
681
686
|
// Get route metadata from the server config route tree, if available
|
|
682
687
|
let matchedMetaData;
|
|
@@ -694,12 +699,16 @@ async function* traverseRoutesConfig(options) {
|
|
|
694
699
|
const metadata = {
|
|
695
700
|
renderMode: RenderMode.Prerender,
|
|
696
701
|
...matchedMetaData,
|
|
702
|
+
preload: parentPreloads,
|
|
697
703
|
// Match Angular router behavior
|
|
698
704
|
// ['one', 'two', ''] -> 'one/two/'
|
|
699
705
|
// ['one', 'two', 'three'] -> 'one/two/three'
|
|
700
706
|
route: path === '' ? addTrailingSlash(currentRoutePath) : currentRoutePath,
|
|
707
|
+
presentInClientRouter: undefined,
|
|
701
708
|
};
|
|
702
|
-
|
|
709
|
+
if (ɵentryName && loadComponent) {
|
|
710
|
+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, true);
|
|
711
|
+
}
|
|
703
712
|
if (metadata.renderMode === RenderMode.Prerender) {
|
|
704
713
|
// Handle SSG routes
|
|
705
714
|
yield* handleSSGRoute(typeof redirectTo === 'string' ? redirectTo : undefined, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes);
|
|
@@ -724,10 +733,18 @@ async function* traverseRoutesConfig(options) {
|
|
|
724
733
|
...options,
|
|
725
734
|
routes: children,
|
|
726
735
|
parentRoute: currentRoutePath,
|
|
736
|
+
parentPreloads: metadata.preload,
|
|
727
737
|
});
|
|
728
738
|
}
|
|
729
739
|
// Load and process lazy-loaded child routes
|
|
730
740
|
if (loadChildren) {
|
|
741
|
+
if (ɵentryName) {
|
|
742
|
+
// When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
|
|
743
|
+
// As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
|
|
744
|
+
// across different child routes. In contrast, `loadComponent` only loads a single component, which allows
|
|
745
|
+
// for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
|
|
746
|
+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false);
|
|
747
|
+
}
|
|
731
748
|
const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
|
|
732
749
|
if (loadedChildRoutes) {
|
|
733
750
|
const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
|
|
@@ -736,6 +753,7 @@ async function* traverseRoutesConfig(options) {
|
|
|
736
753
|
routes: childRoutes,
|
|
737
754
|
parentInjector: injector,
|
|
738
755
|
parentRoute: currentRoutePath,
|
|
756
|
+
parentPreloads: metadata.preload,
|
|
739
757
|
});
|
|
740
758
|
}
|
|
741
759
|
}
|
|
@@ -747,6 +765,27 @@ async function* traverseRoutesConfig(options) {
|
|
|
747
765
|
}
|
|
748
766
|
}
|
|
749
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* Appends preload information to the metadata object based on the specified entry-point and chunk mappings.
|
|
770
|
+
*
|
|
771
|
+
* This function extracts preload data for a given entry-point from the provided chunk mappings. It adds the
|
|
772
|
+
* corresponding browser bundles to the metadata's preload list, ensuring no duplicates and limiting the total
|
|
773
|
+
* preloads to a predefined maximum.
|
|
774
|
+
*/
|
|
775
|
+
function appendPreloadToMetadata(entryName, entryPointToBrowserMapping, metadata, includeDynamicImports) {
|
|
776
|
+
if (!entryPointToBrowserMapping) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const preload = entryPointToBrowserMapping[entryName];
|
|
780
|
+
if (preload?.length) {
|
|
781
|
+
// Merge existing preloads with new ones, ensuring uniqueness and limiting the total to the maximum allowed.
|
|
782
|
+
const preloadPaths = preload
|
|
783
|
+
.filter(({ dynamicImport }) => includeDynamicImports || !dynamicImport)
|
|
784
|
+
.map(({ path }) => path) ?? [];
|
|
785
|
+
const combinedPreloads = [...(metadata.preload ?? []), ...preloadPaths];
|
|
786
|
+
metadata.preload = Array.from(new Set(combinedPreloads)).slice(0, MODULE_PRELOAD_MAX);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
750
789
|
/**
|
|
751
790
|
* Handles SSG (Static Site Generation) routes by invoking `getPrerenderParams` and yielding
|
|
752
791
|
* all parameterized paths, returning any errors encountered.
|
|
@@ -891,10 +930,11 @@ function buildServerConfigRouteTree({ routes, appShellRoute }) {
|
|
|
891
930
|
* @param invokeGetPrerenderParams - A boolean flag indicating whether to invoke `getPrerenderParams` for parameterized SSG routes
|
|
892
931
|
* to handle prerendering paths. Defaults to `false`.
|
|
893
932
|
* @param includePrerenderFallbackRoutes - A flag indicating whether to include fallback routes in the result. Defaults to `true`.
|
|
933
|
+
* @param entryPointToBrowserMapping - Maps the entry-point name to the associated JavaScript browser bundles.
|
|
894
934
|
*
|
|
895
935
|
* @returns A promise that resolves to an object of type `AngularRouterConfigResult` or errors.
|
|
896
936
|
*/
|
|
897
|
-
async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invokeGetPrerenderParams = false, includePrerenderFallbackRoutes = true) {
|
|
937
|
+
async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invokeGetPrerenderParams = false, includePrerenderFallbackRoutes = true, entryPointToBrowserMapping = undefined) {
|
|
898
938
|
const { protocol, host } = url;
|
|
899
939
|
// Create and initialize the Angular platform for server-side rendering.
|
|
900
940
|
const platformRef = platformServer([
|
|
@@ -968,6 +1008,7 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
968
1008
|
serverConfigRouteTree,
|
|
969
1009
|
invokeGetPrerenderParams,
|
|
970
1010
|
includePrerenderFallbackRoutes,
|
|
1011
|
+
entryPointToBrowserMapping,
|
|
971
1012
|
});
|
|
972
1013
|
for await (const result of traverseRoutes) {
|
|
973
1014
|
if ('error' in result) {
|
|
@@ -1040,7 +1081,7 @@ function extractRoutesAndCreateRouteTree(options) {
|
|
|
1040
1081
|
const routeTree = new RouteTree();
|
|
1041
1082
|
const document = await new ServerAssets(manifest).getIndexServerHtml().text();
|
|
1042
1083
|
const bootstrap = await manifest.bootstrap();
|
|
1043
|
-
const { baseHref, appShellRoute, routes, errors } = await getRoutesFromAngularRouterConfig(bootstrap, document, url, invokeGetPrerenderParams, includePrerenderFallbackRoutes);
|
|
1084
|
+
const { baseHref, appShellRoute, routes, errors } = await getRoutesFromAngularRouterConfig(bootstrap, document, url, invokeGetPrerenderParams, includePrerenderFallbackRoutes, manifest.entryPointToBrowserMapping);
|
|
1044
1085
|
for (const { route, ...metadata } of routes) {
|
|
1045
1086
|
if (metadata.redirectTo !== undefined) {
|
|
1046
1087
|
metadata.redirectTo = joinUrlParts(baseHref, metadata.redirectTo);
|
|
@@ -1692,7 +1733,7 @@ class AngularServerApp {
|
|
|
1692
1733
|
* @returns A promise that resolves to the rendered response, or null if no matching route is found.
|
|
1693
1734
|
*/
|
|
1694
1735
|
async handleRendering(request, matchedRoute, requestContext) {
|
|
1695
|
-
const { renderMode, headers, status } = matchedRoute;
|
|
1736
|
+
const { renderMode, headers, status, preload } = matchedRoute;
|
|
1696
1737
|
if (!this.allowStaticRouteRender && renderMode === RenderMode.Prerender) {
|
|
1697
1738
|
return null;
|
|
1698
1739
|
}
|
|
@@ -1723,8 +1764,8 @@ class AngularServerApp {
|
|
|
1723
1764
|
}
|
|
1724
1765
|
else if (renderMode === RenderMode.Client) {
|
|
1725
1766
|
// Serve the client-side rendered version if the route is configured for CSR.
|
|
1726
|
-
let html = await assets.getServerAsset('index.csr.html').text();
|
|
1727
|
-
html = await this.runTransformsOnHtml(html, url);
|
|
1767
|
+
let html = await this.assets.getServerAsset('index.csr.html').text();
|
|
1768
|
+
html = await this.runTransformsOnHtml(html, url, preload);
|
|
1728
1769
|
return new Response(html, responseInit);
|
|
1729
1770
|
}
|
|
1730
1771
|
if (locale !== undefined) {
|
|
@@ -1735,7 +1776,7 @@ class AngularServerApp {
|
|
|
1735
1776
|
}
|
|
1736
1777
|
this.boostrap ??= await bootstrap();
|
|
1737
1778
|
let html = await assets.getIndexServerHtml().text();
|
|
1738
|
-
html = await this.runTransformsOnHtml(html, url);
|
|
1779
|
+
html = await this.runTransformsOnHtml(html, url, preload);
|
|
1739
1780
|
html = await renderAngular(html, this.boostrap, url, platformProviders, SERVER_CONTEXT_VALUE[renderMode]);
|
|
1740
1781
|
if (inlineCriticalCss) {
|
|
1741
1782
|
// Optionally inline critical CSS.
|
|
@@ -1795,12 +1836,16 @@ class AngularServerApp {
|
|
|
1795
1836
|
*
|
|
1796
1837
|
* @param html - The raw HTML content to be transformed.
|
|
1797
1838
|
* @param url - The URL associated with the HTML content, used for context during transformations.
|
|
1839
|
+
* @param preload - An array of URLs representing the JavaScript resources to preload.
|
|
1798
1840
|
* @returns A promise that resolves to the transformed HTML string.
|
|
1799
1841
|
*/
|
|
1800
|
-
async runTransformsOnHtml(html, url) {
|
|
1842
|
+
async runTransformsOnHtml(html, url, preload) {
|
|
1801
1843
|
if (this.hooks.has('html:transform:pre')) {
|
|
1802
1844
|
html = await this.hooks.run('html:transform:pre', { html, url });
|
|
1803
1845
|
}
|
|
1846
|
+
if (preload?.length) {
|
|
1847
|
+
html = appendPreloadHintsToHtml(html, preload);
|
|
1848
|
+
}
|
|
1804
1849
|
return html;
|
|
1805
1850
|
}
|
|
1806
1851
|
}
|
|
@@ -1833,6 +1878,31 @@ function destroyAngularServerApp() {
|
|
|
1833
1878
|
}
|
|
1834
1879
|
angularServerApp = undefined;
|
|
1835
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Appends module preload hints to an HTML string for specified JavaScript resources.
|
|
1883
|
+
* This function enhances the HTML by injecting `<link rel="modulepreload">` elements
|
|
1884
|
+
* for each provided resource, allowing browsers to preload the specified JavaScript
|
|
1885
|
+
* modules for better performance.
|
|
1886
|
+
*
|
|
1887
|
+
* @param html - The original HTML string to which preload hints will be added.
|
|
1888
|
+
* @param preload - An array of URLs representing the JavaScript resources to preload.
|
|
1889
|
+
* @returns The modified HTML string with the preload hints injected before the closing `</body>` tag.
|
|
1890
|
+
* If `</body>` is not found, the links are not added.
|
|
1891
|
+
*/
|
|
1892
|
+
function appendPreloadHintsToHtml(html, preload) {
|
|
1893
|
+
const bodyCloseIdx = html.lastIndexOf('</body>');
|
|
1894
|
+
if (bodyCloseIdx === -1) {
|
|
1895
|
+
return html;
|
|
1896
|
+
}
|
|
1897
|
+
// Note: Module preloads should be placed at the end before the closing body tag to avoid a performance penalty.
|
|
1898
|
+
// Placing them earlier can cause the browser to prioritize downloading these modules
|
|
1899
|
+
// over other critical page resources like images, CSS, and fonts.
|
|
1900
|
+
return [
|
|
1901
|
+
html.slice(0, bodyCloseIdx),
|
|
1902
|
+
...preload.map((val) => `<link rel="modulepreload" href="${val}">`),
|
|
1903
|
+
html.slice(bodyCloseIdx),
|
|
1904
|
+
].join('\n');
|
|
1905
|
+
}
|
|
1836
1906
|
|
|
1837
1907
|
// ɵgetRoutesFromAngularRouterConfig is only used by the Webpack based server builder.
|
|
1838
1908
|
|
|
@@ -1870,6 +1940,144 @@ function getPotentialLocaleIdFromUrl(url, basePath) {
|
|
|
1870
1940
|
// Extract the potential locale id.
|
|
1871
1941
|
return pathname.slice(start, end);
|
|
1872
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Parses the `Accept-Language` header and returns a list of locale preferences with their respective quality values.
|
|
1945
|
+
*
|
|
1946
|
+
* The `Accept-Language` header is typically a comma-separated list of locales, with optional quality values
|
|
1947
|
+
* in the form of `q=<value>`. If no quality value is specified, a default quality of `1` is assumed.
|
|
1948
|
+
* Special case: if the header is `*`, it returns the default locale with a quality of `1`.
|
|
1949
|
+
*
|
|
1950
|
+
* @param header - The value of the `Accept-Language` header, typically a comma-separated list of locales
|
|
1951
|
+
* with optional quality values (e.g., `en-US;q=0.8,fr-FR;q=0.9`). If the header is `*`,
|
|
1952
|
+
* it represents a wildcard for any language, returning the default locale.
|
|
1953
|
+
*
|
|
1954
|
+
* @returns A `ReadonlyMap` where the key is the locale (e.g., `en-US`, `fr-FR`), and the value is
|
|
1955
|
+
* the associated quality value (a number between 0 and 1). If no quality value is provided,
|
|
1956
|
+
* a default of `1` is used.
|
|
1957
|
+
*
|
|
1958
|
+
* @example
|
|
1959
|
+
* ```js
|
|
1960
|
+
* parseLanguageHeader('en-US;q=0.8,fr-FR;q=0.9')
|
|
1961
|
+
* // returns new Map([['en-US', 0.8], ['fr-FR', 0.9]])
|
|
1962
|
+
|
|
1963
|
+
* parseLanguageHeader('*')
|
|
1964
|
+
* // returns new Map([['*', 1]])
|
|
1965
|
+
* ```
|
|
1966
|
+
*/
|
|
1967
|
+
function parseLanguageHeader(header) {
|
|
1968
|
+
if (header === '*') {
|
|
1969
|
+
return new Map([['*', 1]]);
|
|
1970
|
+
}
|
|
1971
|
+
const parsedValues = header
|
|
1972
|
+
.split(',')
|
|
1973
|
+
.map((item) => {
|
|
1974
|
+
const [locale, qualityValue] = item.split(';', 2).map((v) => v.trim());
|
|
1975
|
+
let quality = qualityValue?.startsWith('q=') ? parseFloat(qualityValue.slice(2)) : undefined;
|
|
1976
|
+
if (typeof quality !== 'number' || isNaN(quality) || quality < 0 || quality > 1) {
|
|
1977
|
+
quality = 1; // Invalid quality value defaults to 1
|
|
1978
|
+
}
|
|
1979
|
+
return [locale, quality];
|
|
1980
|
+
})
|
|
1981
|
+
.sort(([_localeA, qualityA], [_localeB, qualityB]) => qualityB - qualityA);
|
|
1982
|
+
return new Map(parsedValues);
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Gets the preferred locale based on the highest quality value from the provided `Accept-Language` header
|
|
1986
|
+
* and the set of available locales.
|
|
1987
|
+
*
|
|
1988
|
+
* This function adheres to the HTTP `Accept-Language` header specification as defined in
|
|
1989
|
+
* [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5), including:
|
|
1990
|
+
* - Case-insensitive matching of language tags.
|
|
1991
|
+
* - Quality value handling (e.g., `q=1`, `q=0.8`). If no quality value is provided, it defaults to `q=1`.
|
|
1992
|
+
* - Prefix matching (e.g., `en` matching `en-US` or `en-GB`).
|
|
1993
|
+
*
|
|
1994
|
+
* @param header - The `Accept-Language` header string to parse and evaluate. It may contain multiple
|
|
1995
|
+
* locales with optional quality values, for example: `'en-US;q=0.8,fr-FR;q=0.9'`.
|
|
1996
|
+
* @param supportedLocales - An array of supported locales (e.g., `['en-US', 'fr-FR']`),
|
|
1997
|
+
* representing the locales available in the application.
|
|
1998
|
+
* @returns The best matching locale from the supported languages, or `undefined` if no match is found.
|
|
1999
|
+
*
|
|
2000
|
+
* @example
|
|
2001
|
+
* ```js
|
|
2002
|
+
* getPreferredLocale('en-US;q=0.8,fr-FR;q=0.9', ['en-US', 'fr-FR', 'de-DE'])
|
|
2003
|
+
* // returns 'fr-FR'
|
|
2004
|
+
*
|
|
2005
|
+
* getPreferredLocale('en;q=0.9,fr-FR;q=0.8', ['en-US', 'fr-FR', 'de-DE'])
|
|
2006
|
+
* // returns 'en-US'
|
|
2007
|
+
*
|
|
2008
|
+
* getPreferredLocale('es-ES;q=0.7', ['en-US', 'fr-FR', 'de-DE'])
|
|
2009
|
+
* // returns undefined
|
|
2010
|
+
* ```
|
|
2011
|
+
*/
|
|
2012
|
+
function getPreferredLocale(header, supportedLocales) {
|
|
2013
|
+
if (supportedLocales.length < 2) {
|
|
2014
|
+
return supportedLocales[0];
|
|
2015
|
+
}
|
|
2016
|
+
const parsedLocales = parseLanguageHeader(header);
|
|
2017
|
+
// Handle edge cases:
|
|
2018
|
+
// - No preferred locales provided.
|
|
2019
|
+
// - Only one supported locale.
|
|
2020
|
+
// - Wildcard preference.
|
|
2021
|
+
if (parsedLocales.size === 0 || (parsedLocales.size === 1 && parsedLocales.has('*'))) {
|
|
2022
|
+
return supportedLocales[0];
|
|
2023
|
+
}
|
|
2024
|
+
// Create a map for case-insensitive lookup of supported locales.
|
|
2025
|
+
// Keys are normalized (lowercase) locale values, values are original casing.
|
|
2026
|
+
const normalizedSupportedLocales = new Map();
|
|
2027
|
+
for (const locale of supportedLocales) {
|
|
2028
|
+
normalizedSupportedLocales.set(normalizeLocale(locale), locale);
|
|
2029
|
+
}
|
|
2030
|
+
// Iterate through parsed locales in descending order of quality.
|
|
2031
|
+
let bestMatch;
|
|
2032
|
+
const qualityZeroNormalizedLocales = new Set();
|
|
2033
|
+
for (const [locale, quality] of parsedLocales) {
|
|
2034
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
2035
|
+
if (quality === 0) {
|
|
2036
|
+
qualityZeroNormalizedLocales.add(normalizedLocale);
|
|
2037
|
+
continue; // Skip locales with quality value of 0.
|
|
2038
|
+
}
|
|
2039
|
+
// Exact match found.
|
|
2040
|
+
if (normalizedSupportedLocales.has(normalizedLocale)) {
|
|
2041
|
+
return normalizedSupportedLocales.get(normalizedLocale);
|
|
2042
|
+
}
|
|
2043
|
+
// If an exact match is not found, try prefix matching (e.g., "en" matches "en-US").
|
|
2044
|
+
// Store the first prefix match encountered, as it has the highest quality value.
|
|
2045
|
+
if (bestMatch !== undefined) {
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
const [languagePrefix] = normalizedLocale.split('-', 1);
|
|
2049
|
+
for (const supportedLocale of normalizedSupportedLocales.keys()) {
|
|
2050
|
+
if (supportedLocale.startsWith(languagePrefix)) {
|
|
2051
|
+
bestMatch = normalizedSupportedLocales.get(supportedLocale);
|
|
2052
|
+
break; // No need to continue searching for this locale.
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
if (bestMatch !== undefined) {
|
|
2057
|
+
return bestMatch;
|
|
2058
|
+
}
|
|
2059
|
+
// Return the first locale that is not quality zero.
|
|
2060
|
+
for (const [normalizedLocale, locale] of normalizedSupportedLocales) {
|
|
2061
|
+
if (!qualityZeroNormalizedLocales.has(normalizedLocale)) {
|
|
2062
|
+
return locale;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Normalizes a locale string by converting it to lowercase.
|
|
2068
|
+
*
|
|
2069
|
+
* @param locale - The locale string to normalize.
|
|
2070
|
+
* @returns The normalized locale string in lowercase.
|
|
2071
|
+
*
|
|
2072
|
+
* @example
|
|
2073
|
+
* ```ts
|
|
2074
|
+
* const normalized = normalizeLocale('EN-US');
|
|
2075
|
+
* console.log(normalized); // Output: "en-us"
|
|
2076
|
+
* ```
|
|
2077
|
+
*/
|
|
2078
|
+
function normalizeLocale(locale) {
|
|
2079
|
+
return locale.toLowerCase();
|
|
2080
|
+
}
|
|
1873
2081
|
|
|
1874
2082
|
/**
|
|
1875
2083
|
* Angular server application engine.
|
|
@@ -1904,9 +2112,9 @@ class AngularAppEngine {
|
|
|
1904
2112
|
*/
|
|
1905
2113
|
manifest = getAngularAppEngineManifest();
|
|
1906
2114
|
/**
|
|
1907
|
-
*
|
|
2115
|
+
* A map of supported locales from the server application's manifest.
|
|
1908
2116
|
*/
|
|
1909
|
-
|
|
2117
|
+
supportedLocales = Object.keys(this.manifest.supportedLocales);
|
|
1910
2118
|
/**
|
|
1911
2119
|
* A cache that holds entry points, keyed by their potential locale string.
|
|
1912
2120
|
*/
|
|
@@ -1924,7 +2132,47 @@ class AngularAppEngine {
|
|
|
1924
2132
|
*/
|
|
1925
2133
|
async handle(request, requestContext) {
|
|
1926
2134
|
const serverApp = await this.getAngularServerAppForRequest(request);
|
|
1927
|
-
|
|
2135
|
+
if (serverApp) {
|
|
2136
|
+
return serverApp.handle(request, requestContext);
|
|
2137
|
+
}
|
|
2138
|
+
if (this.supportedLocales.length > 1) {
|
|
2139
|
+
// Redirect to the preferred language if i18n is enabled.
|
|
2140
|
+
return this.redirectBasedOnAcceptLanguage(request);
|
|
2141
|
+
}
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Handles requests for the base path when i18n is enabled.
|
|
2146
|
+
* Redirects the user to a locale-specific path based on the `Accept-Language` header.
|
|
2147
|
+
*
|
|
2148
|
+
* @param request The incoming request.
|
|
2149
|
+
* @returns A `Response` object with a 302 redirect, or `null` if i18n is not enabled
|
|
2150
|
+
* or the request is not for the base path.
|
|
2151
|
+
*/
|
|
2152
|
+
redirectBasedOnAcceptLanguage(request) {
|
|
2153
|
+
const { basePath, supportedLocales } = this.manifest;
|
|
2154
|
+
// If the request is not for the base path, it's not our responsibility to handle it.
|
|
2155
|
+
const url = new URL(request.url);
|
|
2156
|
+
if (url.pathname !== basePath) {
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
// For requests to the base path (typically '/'), attempt to extract the preferred locale
|
|
2160
|
+
// from the 'Accept-Language' header.
|
|
2161
|
+
const preferredLocale = getPreferredLocale(request.headers.get('Accept-Language') || '*', this.supportedLocales);
|
|
2162
|
+
if (preferredLocale) {
|
|
2163
|
+
const subPath = supportedLocales[preferredLocale];
|
|
2164
|
+
if (subPath !== undefined) {
|
|
2165
|
+
url.pathname = joinUrlParts(url.pathname, subPath);
|
|
2166
|
+
return new Response(null, {
|
|
2167
|
+
status: 302, // Use a 302 redirect as language preference may change.
|
|
2168
|
+
headers: {
|
|
2169
|
+
'Location': url.toString(),
|
|
2170
|
+
'Vary': 'Accept-Language',
|
|
2171
|
+
},
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return null;
|
|
1928
2176
|
}
|
|
1929
2177
|
/**
|
|
1930
2178
|
* Retrieves the Angular server application instance for a given request.
|
|
@@ -1986,11 +2234,11 @@ class AngularAppEngine {
|
|
|
1986
2234
|
*/
|
|
1987
2235
|
getEntryPointExportsForUrl(url) {
|
|
1988
2236
|
const { basePath } = this.manifest;
|
|
1989
|
-
if (this.
|
|
2237
|
+
if (this.supportedLocales.length === 1) {
|
|
1990
2238
|
return this.getEntryPointExports('');
|
|
1991
2239
|
}
|
|
1992
2240
|
const potentialLocale = getPotentialLocaleIdFromUrl(url, basePath);
|
|
1993
|
-
return this.getEntryPointExports(potentialLocale);
|
|
2241
|
+
return this.getEntryPointExports(potentialLocale) ?? this.getEntryPointExports('');
|
|
1994
2242
|
}
|
|
1995
2243
|
}
|
|
1996
2244
|
|