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([]); + }); +});