Skip to content

Commit 5c6cff8

Browse files
committed
refactor(@angular/build): split SSR server assets into separate chunks
This commit refactors the build process for server-side rendering (SSR) by dividing server assets into separate, importable chunks rather than bundling them into a single output file. (cherry picked from commit d21e511)
1 parent a7e6025 commit 5c6cff8

File tree

4 files changed

+57
-37
lines changed

4 files changed

+57
-37
lines changed

goldens/circular-deps/packages.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts",
88
"packages/angular/build/src/tools/esbuild/bundler-context.ts",
99
"packages/angular/build/src/tools/esbuild/utils.ts",
10-
"packages/angular/build/src/utils/server-rendering/manifest.ts",
1110
"packages/angular/build/src/tools/esbuild/bundler-execution-result.ts"
1211
],
1312
[
@@ -17,16 +16,18 @@
1716
[
1817
"packages/angular/build/src/tools/esbuild/bundler-context.ts",
1918
"packages/angular/build/src/tools/esbuild/utils.ts",
20-
"packages/angular/build/src/utils/server-rendering/manifest.ts"
19+
"packages/angular/build/src/tools/esbuild/bundler-execution-result.ts"
2120
],
2221
[
2322
"packages/angular/build/src/tools/esbuild/bundler-context.ts",
2423
"packages/angular/build/src/tools/esbuild/utils.ts",
25-
"packages/angular/build/src/utils/server-rendering/manifest.ts",
26-
"packages/angular/build/src/tools/esbuild/bundler-execution-result.ts"
24+
"packages/angular/build/src/utils/server-rendering/manifest.ts"
2725
],
2826
[
2927
"packages/angular/build/src/tools/esbuild/bundler-execution-result.ts",
28+
"packages/angular/build/src/tools/esbuild/utils.ts"
29+
],
30+
[
3031
"packages/angular/build/src/tools/esbuild/utils.ts",
3132
"packages/angular/build/src/utils/server-rendering/manifest.ts"
3233
],

packages/angular/build/src/builders/application/execute-post-bundle.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { OutputMode } from './schema';
4040
* @param initialFiles A map containing initial file information for the executed build.
4141
* @param locale A language locale to insert in the index.html.
4242
*/
43+
// eslint-disable-next-line max-lines-per-function
4344
export async function executePostBundleSteps(
4445
options: NormalizedApplicationBuildOptions,
4546
outputFiles: BuildOutputFile[],
@@ -107,16 +108,19 @@ export async function executePostBundleSteps(
107108

108109
// Create server manifest
109110
if (serverEntryPoint) {
111+
const { manifestContent, serverAssetsChunks } = generateAngularServerAppManifest(
112+
additionalHtmlOutputFiles,
113+
outputFiles,
114+
optimizationOptions.styles.inlineCritical ?? false,
115+
undefined,
116+
locale,
117+
);
118+
110119
additionalOutputFiles.push(
120+
...serverAssetsChunks,
111121
createOutputFile(
112122
SERVER_APP_MANIFEST_FILENAME,
113-
generateAngularServerAppManifest(
114-
additionalHtmlOutputFiles,
115-
outputFiles,
116-
optimizationOptions.styles.inlineCritical ?? false,
117-
undefined,
118-
locale,
119-
),
123+
manifestContent,
120124
BuildOutputFileType.ServerApplication,
121125
),
122126
);
@@ -194,15 +198,24 @@ export async function executePostBundleSteps(
194198
const manifest = additionalOutputFiles.find((f) => f.path === SERVER_APP_MANIFEST_FILENAME);
195199
assert(manifest, `${SERVER_APP_MANIFEST_FILENAME} was not found in output files.`);
196200

197-
manifest.contents = new TextEncoder().encode(
198-
generateAngularServerAppManifest(
199-
additionalHtmlOutputFiles,
200-
outputFiles,
201-
optimizationOptions.styles.inlineCritical ?? false,
202-
serializableRouteTreeNodeForManifest,
203-
locale,
204-
),
201+
const { manifestContent, serverAssetsChunks } = generateAngularServerAppManifest(
202+
additionalHtmlOutputFiles,
203+
outputFiles,
204+
optimizationOptions.styles.inlineCritical ?? false,
205+
serializableRouteTreeNodeForManifest,
206+
locale,
205207
);
208+
209+
for (const chunk of serverAssetsChunks) {
210+
const idx = additionalOutputFiles.findIndex(({ path }) => path === chunk.path);
211+
if (idx === -1) {
212+
additionalOutputFiles.push(chunk);
213+
} else {
214+
additionalOutputFiles[idx] = chunk;
215+
}
216+
}
217+
218+
manifest.contents = new TextEncoder().encode(manifestContent);
206219
}
207220
}
208221

packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import assert from 'node:assert';
1010
import { readFile } from 'node:fs/promises';
11-
import { basename, dirname, join, relative } from 'node:path';
11+
import { dirname, join, relative } from 'node:path';
1212
import type { Plugin } from 'vite';
1313
import { loadEsmModule } from '../../../utils/load-esm';
1414
import { AngularMemoryOutputFiles } from '../utils';
@@ -24,8 +24,6 @@ export async function createAngularMemoryPlugin(
2424
): Promise<Plugin> {
2525
const { virtualProjectRoot, outputFiles, external } = options;
2626
const { normalizePath } = await loadEsmModule<typeof import('vite')>('vite');
27-
// See: https://github.com/vitejs/vite/blob/a34a73a3ad8feeacc98632c0f4c643b6820bbfda/packages/vite/src/node/server/pluginContainer.ts#L331-L334
28-
const defaultImporter = join(virtualProjectRoot, 'index.html');
2927

3028
return {
3129
name: 'vite:angular-memory',
@@ -40,16 +38,10 @@ export async function createAngularMemoryPlugin(
4038
}
4139

4240
if (importer) {
43-
let normalizedSource: string | undefined;
4441
if (source[0] === '.' && normalizePath(importer).startsWith(virtualProjectRoot)) {
4542
// Remove query if present
4643
const [importerFile] = importer.split('?', 1);
47-
normalizedSource = join(dirname(relative(virtualProjectRoot, importerFile)), source);
48-
} else if (source[0] === '/' && importer === defaultImporter) {
49-
normalizedSource = basename(source);
50-
}
51-
if (normalizedSource) {
52-
source = '/' + normalizePath(normalizedSource);
44+
source = '/' + join(dirname(relative(virtualProjectRoot, importerFile)), source);
5345
}
5446
}
5547

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
NormalizedApplicationBuildOptions,
1212
getLocaleBaseHref,
1313
} from '../../builders/application/options';
14-
import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
15-
import type { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
14+
import { type BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
15+
import { createOutputFile } from '../../tools/esbuild/utils';
1616

1717
export const SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
1818
export const SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
@@ -104,22 +104,36 @@ export default {
104104
* @param locale - An optional string representing the locale or language code to be used for
105105
* the application, helping with localization and rendering content specific to the locale.
106106
*
107-
* @returns A string representing the content of the SSR server manifest for the Node.js
108-
* environment.
107+
* @returns An object containing:
108+
* - `manifestContent`: A string of the SSR manifest content.
109+
* - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
109110
*/
110111
export function generateAngularServerAppManifest(
111112
additionalHtmlOutputFiles: Map<string, BuildOutputFile>,
112113
outputFiles: BuildOutputFile[],
113114
inlineCriticalCss: boolean,
114115
routes: readonly unknown[] | undefined,
115116
locale: string | undefined,
116-
): string {
117+
): {
118+
manifestContent: string;
119+
serverAssetsChunks: BuildOutputFile[];
120+
} {
121+
const serverAssetsChunks: BuildOutputFile[] = [];
117122
const serverAssetsContent: string[] = [];
118123
for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
119124
const extension = extname(file.path);
120125
if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
126+
const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
127+
serverAssetsChunks.push(
128+
createOutputFile(
129+
jsChunkFilePath,
130+
`export default \`${escapeUnsafeChars(file.text)}\`;`,
131+
BuildOutputFileType.ServerApplication,
132+
),
133+
);
134+
121135
serverAssetsContent.push(
122-
`['${file.path}', { size: ${file.size}, hash: '${file.hash}', text: async () => \`${escapeUnsafeChars(file.text)}\`}]`,
136+
`['${file.path}', {size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}]`,
123137
);
124138
}
125139
}
@@ -129,10 +143,10 @@ export default {
129143
bootstrap: () => import('./main.server.mjs').then(m => m.default),
130144
inlineCriticalCss: ${inlineCriticalCss},
131145
routes: ${JSON.stringify(routes, undefined, 2)},
132-
assets: new Map([${serverAssetsContent.join(', \n')}]),
146+
assets: new Map([\n${serverAssetsContent.join(', \n')}\n]),
133147
locale: ${locale !== undefined ? `'${locale}'` : undefined},
134148
};
135149
`;
136150

137-
return manifestContent;
151+
return { manifestContent, serverAssetsChunks };
138152
}

0 commit comments

Comments
 (0)