Skip to content

Commit 861a61a

Browse files
alan-agius4jkrems
authored andcommitted
fix(@angular/ssr): avoid preloading unnecessary dynamic bundles
This change to `@angular/ssr` prevents the preloading of dynamically imported bundles that aren't directly associated with routing. Previously, Angular SSR might preload all dynamic bundles, even those not immediately required, such as deferred chunks within components. Closes #30541
1 parent 20e23f1 commit 861a61a

File tree

4 files changed

+14
-43
lines changed

4 files changed

+14
-43
lines changed

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

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -201,34 +201,22 @@ function generateLazyLoadedFilesMappings(
201201
metafile: Metafile,
202202
initialFiles: Set<string>,
203203
publicPath = '',
204-
): Record<string, FilesMapping[]> {
205-
const entryPointToBundles: Record<string, FilesMapping[]> = {};
204+
): Record<string, string[]> {
205+
const entryPointToBundles: Record<string, string[]> = {};
206206
for (const [fileName, { entryPoint, exports, imports }] of Object.entries(metafile.outputs)) {
207207
// Skip files that don't have an entryPoint, no exports, or are not .js
208208
if (!entryPoint || exports?.length < 1 || !fileName.endsWith('.js')) {
209209
continue;
210210
}
211211

212-
const importedPaths: FilesMapping[] = [
213-
{
214-
path: `${publicPath}${fileName}`,
215-
dynamicImport: false,
216-
},
217-
];
212+
const importedPaths: string[] = [`${publicPath}${fileName}`];
218213

219214
for (const { kind, external, path } of imports) {
220-
if (
221-
external ||
222-
initialFiles.has(path) ||
223-
(kind !== 'dynamic-import' && kind !== 'import-statement')
224-
) {
215+
if (external || initialFiles.has(path) || kind !== 'import-statement') {
225216
continue;
226217
}
227218

228-
importedPaths.push({
229-
path: `${publicPath}${path}`,
230-
dynamicImport: kind === 'dynamic-import',
231-
});
219+
importedPaths.push(`${publicPath}${path}`);
232220
}
233221

234222
entryPointToBundles[entryPoint] = importedPaths;

packages/angular/ssr/src/manifest.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,16 @@ export interface AngularAppManifest {
123123
* Maps entry-point names to their corresponding browser bundles and loading strategies.
124124
*
125125
* - **Key**: The entry-point name, typically the value of `ɵentryName`.
126-
* - **Value**: An array of objects, each representing a browser bundle with:
127-
* - `path`: The filename or URL of the associated JavaScript bundle to preload.
128-
* - `dynamicImport`: A boolean indicating whether the bundle is loaded via a dynamic `import()`.
129-
* If `true`, the bundle is lazily loaded, impacting its preloading behavior.
126+
* - **Value**: A readonly array of JavaScript bundle paths or `undefined` if no bundles are associated.
130127
*
131128
* ### Example
132129
* ```ts
133130
* {
134-
* 'src/app/lazy/lazy.ts': [{ path: 'src/app/lazy/lazy.js', dynamicImport: true }]
131+
* 'src/app/lazy/lazy.ts': ['src/app/lazy/lazy.js']
135132
* }
136133
* ```
137134
*/
138-
readonly entryPointToBrowserMapping?: Readonly<
139-
Record<string, ReadonlyArray<{ path: string; dynamicImport: boolean }> | undefined>
140-
>;
135+
readonly entryPointToBrowserMapping?: Readonly<Record<string, readonly string[] | undefined>>;
141136
}
142137

143138
/**

packages/angular/ssr/src/routes/ng-routes.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ async function* handleRoute(options: {
148148

149149
const { redirectTo, loadChildren, loadComponent, children, ɵentryName } = route;
150150
if (ɵentryName && loadComponent) {
151-
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, true);
151+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata);
152152
}
153153

154154
if (metadata.renderMode === RenderMode.Prerender) {
@@ -192,11 +192,7 @@ async function* handleRoute(options: {
192192
// Load and process lazy-loaded child routes
193193
if (loadChildren) {
194194
if (ɵentryName) {
195-
// When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
196-
// As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
197-
// across different child routes. In contrast, `loadComponent` only loads a single component, which allows
198-
// for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
199-
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false);
195+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata);
200196
}
201197

202198
const loadedChildRoutes = await loadChildrenHelper(
@@ -336,7 +332,6 @@ function appendPreloadToMetadata(
336332
entryName: string,
337333
entryPointToBrowserMapping: EntryPointToBrowserMapping,
338334
metadata: ServerConfigRouteTreeNodeMetadata,
339-
includeDynamicImports: boolean,
340335
): void {
341336
const existingPreloads = metadata.preload ?? [];
342337
if (!entryPointToBrowserMapping || existingPreloads.length >= MODULE_PRELOAD_MAX) {
@@ -350,13 +345,8 @@ function appendPreloadToMetadata(
350345

351346
// Merge existing preloads with new ones, ensuring uniqueness and limiting the total to the maximum allowed.
352347
const combinedPreloads: Set<string> = new Set(existingPreloads);
353-
for (const { dynamicImport, path } of preload) {
354-
if (dynamicImport && !includeDynamicImports) {
355-
continue;
356-
}
357-
358-
combinedPreloads.add(path);
359-
348+
for (const href of preload) {
349+
combinedPreloads.add(href);
360350
if (combinedPreloads.size === MODULE_PRELOAD_MAX) {
361351
break;
362352
}

tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,15 @@ const RESPONSE_EXPECTS: Record<
143143
matches: [
144144
/<link rel="modulepreload" href="(ssg\.routes-[a-zA-Z0-9]{8}\.js)">/,
145145
/<link rel="modulepreload" href="(ssg-one-[a-zA-Z0-9]{8}\.js)">/,
146-
/<link rel="modulepreload" href="(cross-dep-[a-zA-Z0-9]{8}\.js)">/,
147146
],
148-
notMatches: [/home/, /ssr/, /csr/, /ssg-two/, /ssg\-component/],
147+
notMatches: [/home/, /ssr/, /csr/, /ssg-two/, /ssg\-component/, /cross-dep-/],
149148
},
150149
'/ssg/two': {
151150
matches: [
152151
/<link rel="modulepreload" href="(ssg\.routes-[a-zA-Z0-9]{8}\.js)">/,
153152
/<link rel="modulepreload" href="(ssg-two-[a-zA-Z0-9]{8}\.js)">/,
154-
/<link rel="modulepreload" href="(cross-dep-[a-zA-Z0-9]{8}\.js)">/,
155153
],
156-
notMatches: [/home/, /ssr/, /csr/, /ssg-one/, /ssg\-component/],
154+
notMatches: [/home/, /ssr/, /csr/, /ssg-one/, /ssg\-component/, /cross-dep-/],
157155
},
158156
'/ssr': {
159157
matches: [/<link rel="modulepreload" href="(ssr-[a-zA-Z0-9]{8}\.js)">/],

0 commit comments

Comments
 (0)