From 7ac0aa00fc6efb7864dfc378b70b3ee2ed25a583 Mon Sep 17 00:00:00 2001 From: Hexix23 Date: Mon, 24 Aug 2026 00:24:50 +0200 Subject: [PATCH] fix(@angular/build): serialize server manifest asset paths Serialize route-derived asset paths before embedding them in the executable server manifest. Keep object keys and dynamic import specifiers as JavaScript string data. --- .../src/utils/server-rendering/manifest.ts | 6 ++- .../utils/server-rendering/manifest_spec.ts | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 packages/angular/build/src/utils/server-rendering/manifest_spec.ts diff --git a/packages/angular/build/src/utils/server-rendering/manifest.ts b/packages/angular/build/src/utils/server-rendering/manifest.ts index 1ee430a76333..cf602d370787 100644 --- a/packages/angular/build/src/utils/server-rendering/manifest.ts +++ b/packages/angular/build/src/utils/server-rendering/manifest.ts @@ -183,8 +183,10 @@ export function generateAngularServerAppManifest( pos = file.text.indexOf('\r\n', pos + 2); } + // Asset paths can contain prerendered route values. Serialize both path uses before embedding + // them in the executable server manifest so they remain JavaScript string data. serverAssets[file.path] = - `{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`; + `{size: ${size}, hash: '${file.hash}', text: () => import(${JSON.stringify(`./${jsChunkFilePath}`)}).then(m => m.default)}`; } } @@ -203,7 +205,7 @@ export default { entryPointToBrowserMapping: ${JSON.stringify(entryPointToBrowserMapping, undefined, 2)}, assets: { ${Object.entries(serverAssets) - .map(([key, value]) => `'${key}': ${value}`) + .map(([key, value]) => `${JSON.stringify(key)}: ${value}`) .join(',\n ')} }, }; diff --git a/packages/angular/build/src/utils/server-rendering/manifest_spec.ts b/packages/angular/build/src/utils/server-rendering/manifest_spec.ts new file mode 100644 index 000000000000..f9ee48ee9a68 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/manifest_spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { transform } from 'esbuild'; +import { BuildOutputFileType, createOutputFile } from '../../tools/esbuild/bundler-files'; +import { generateAngularServerAppManifest } from './manifest'; + +describe('generateAngularServerAppManifest', () => { + it('serializes asset paths embedded in the executable manifest', async () => { + const assetPath = "products/quote's-name/index.html"; + const chunkPath = `assets-chunks/${assetPath.replace(/[./]/g, '_')}.mjs`; + const asset = createOutputFile(assetPath, '', BuildOutputFileType.Browser); + + const { manifestContent } = generateAngularServerAppManifest( + new Map([[assetPath, asset]]), + [], + false, + undefined, + undefined, + '/', + new Set(), + { inputs: {}, outputs: {} }, + undefined, + ); + + expect(manifestContent).toContain(`${JSON.stringify(assetPath)}: {`); + expect(manifestContent).toContain(`import(${JSON.stringify(`./${chunkPath}`)})`); + expect(manifestContent).not.toContain(`'${assetPath}':`); + + const { warnings } = await transform(manifestContent); + expect(warnings).toEqual([]); + }); +});