Skip to content

Commit f5d9745

Browse files
committed
fix(@angular/ssr): accurately calculate content length for static pages with \r\n
JS engines convert `\r\n` to `\n` in template literals, potentially leading to incorrect byte length calculations. This fix ensures the correct content length is determined. Closes #29567 (cherry picked from commit 414736b)
1 parent f4de3d2 commit f5d9745

File tree

2 files changed

+18
-3
lines changed

2 files changed

+18
-3
lines changed

packages/angular/build/src/utils/server-rendering/manifest.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import type { Metafile } from 'esbuild';
1010
import { extname } from 'node:path';
11+
import { runInThisContext } from 'node:vm';
1112
import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
1213
import { type BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
1314
import { createOutputFile } from '../../tools/esbuild/utils';
@@ -139,20 +140,27 @@ export function generateAngularServerAppManifest(
139140
} {
140141
const serverAssetsChunks: BuildOutputFile[] = [];
141142
const serverAssets: Record<string, string> = {};
143+
142144
for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
143145
const extension = extname(file.path);
144146
if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
145147
const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
148+
const escapedContent = escapeUnsafeChars(file.text);
149+
146150
serverAssetsChunks.push(
147151
createOutputFile(
148152
jsChunkFilePath,
149-
`export default \`${escapeUnsafeChars(file.text)}\`;`,
153+
`export default \`${escapedContent}\`;`,
150154
BuildOutputFileType.ServerApplication,
151155
),
152156
);
153157

158+
// This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals,
159+
// which can result in an incorrect byte length.
160+
const size = runInThisContext(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`);
161+
154162
serverAssets[file.path] =
155-
`{size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
163+
`{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
156164
}
157165
}
158166

tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { join } from 'node:path';
22
import { existsSync } from 'node:fs';
33
import assert from 'node:assert';
4-
import { expectFileToMatch, writeFile } from '../../../utils/fs';
4+
import { expectFileToMatch, readFile, replaceInFile, writeFile } from '../../../utils/fs';
55
import { execAndWaitForOutputToMatch, ng, noSilentNg, silentNg } from '../../../utils/process';
66
import { installWorkspacePackages, uninstallPackage } from '../../../utils/packages';
77
import { useSha } from '../../../utils/project';
@@ -20,6 +20,12 @@ export default async function () {
2020
await useSha();
2121
await installWorkspacePackages();
2222

23+
// Test scenario to verify that the content length, including \r\n, is accurate
24+
await replaceInFile('src/app/app.component.ts', "title = '", "title = 'Title\\r\\n");
25+
26+
// Ensure text has been updated.
27+
assert.match(await readFile('src/app/app.component.ts'), /title = 'Title/);
28+
2329
// Add routes
2430
await writeFile(
2531
'src/app/app.routes.ts',
@@ -165,6 +171,7 @@ export default async function () {
165171

166172
const port = await spawnServer();
167173
for (const [pathname, { content, headers, serverContext }] of Object.entries(responseExpects)) {
174+
// NOTE: A global 'UND_ERR_SOCKET' may occur due to an incorrect Content-Length header value.
168175
const res = await fetch(`http://localhost:${port}${pathname}`);
169176
const text = await res.text();
170177

0 commit comments

Comments
 (0)