From c7bb75c4351e53024dec5e4fe40e993605bf8b62 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 19 Jun 2025 07:28:05 +0000 Subject: [PATCH] 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 --- .../src/utils/server-rendering/manifest.ts | 22 +++++-------------- packages/angular/ssr/src/manifest.ts | 11 +++------- packages/angular/ssr/src/routes/ng-routes.ts | 18 ++++----------- .../server-routes-preload-links.ts | 6 ++--- 4 files changed, 14 insertions(+), 43 deletions(-) diff --git a/packages/angular/build/src/utils/server-rendering/manifest.ts b/packages/angular/build/src/utils/server-rendering/manifest.ts index 4d1459e221c2..2dfad0ff2dfb 100644 --- a/packages/angular/build/src/utils/server-rendering/manifest.ts +++ b/packages/angular/build/src/utils/server-rendering/manifest.ts @@ -201,34 +201,22 @@ function generateLazyLoadedFilesMappings( metafile: Metafile, initialFiles: Set, publicPath = '', -): Record { - const entryPointToBundles: Record = {}; +): Record { + const entryPointToBundles: Record = {}; for (const [fileName, { entryPoint, exports, imports }] of Object.entries(metafile.outputs)) { // Skip files that don't have an entryPoint, no exports, or are not .js if (!entryPoint || exports?.length < 1 || !fileName.endsWith('.js')) { continue; } - const importedPaths: FilesMapping[] = [ - { - path: `${publicPath}${fileName}`, - dynamicImport: false, - }, - ]; + const importedPaths: string[] = [`${publicPath}${fileName}`]; for (const { kind, external, path } of imports) { - if ( - external || - initialFiles.has(path) || - (kind !== 'dynamic-import' && kind !== 'import-statement') - ) { + if (external || initialFiles.has(path) || kind !== 'import-statement') { continue; } - importedPaths.push({ - path: `${publicPath}${path}`, - dynamicImport: kind === 'dynamic-import', - }); + importedPaths.push(`${publicPath}${path}`); } entryPointToBundles[entryPoint] = importedPaths; diff --git a/packages/angular/ssr/src/manifest.ts b/packages/angular/ssr/src/manifest.ts index ae33dc979577..d0f9032ec8b1 100644 --- a/packages/angular/ssr/src/manifest.ts +++ b/packages/angular/ssr/src/manifest.ts @@ -123,21 +123,16 @@ export interface AngularAppManifest { * Maps entry-point names to their corresponding browser bundles and loading strategies. * * - **Key**: The entry-point name, typically the value of `ɵentryName`. - * - **Value**: An array of objects, each representing a browser bundle with: - * - `path`: The filename or URL of the associated JavaScript bundle to preload. - * - `dynamicImport`: A boolean indicating whether the bundle is loaded via a dynamic `import()`. - * If `true`, the bundle is lazily loaded, impacting its preloading behavior. + * - **Value**: A readonly array of JavaScript bundle paths or `undefined` if no bundles are associated. * * ### Example * ```ts * { - * 'src/app/lazy/lazy.ts': [{ path: 'src/app/lazy/lazy.js', dynamicImport: true }] + * 'src/app/lazy/lazy.ts': ['src/app/lazy/lazy.js'] * } * ``` */ - readonly entryPointToBrowserMapping?: Readonly< - Record | undefined> - >; + readonly entryPointToBrowserMapping?: Readonly>; } /** diff --git a/packages/angular/ssr/src/routes/ng-routes.ts b/packages/angular/ssr/src/routes/ng-routes.ts index a904c16fbae7..e2454896f832 100644 --- a/packages/angular/ssr/src/routes/ng-routes.ts +++ b/packages/angular/ssr/src/routes/ng-routes.ts @@ -148,7 +148,7 @@ async function* handleRoute(options: { const { redirectTo, loadChildren, loadComponent, children, ɵentryName } = route; if (ɵentryName && loadComponent) { - appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, true); + appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata); } if (metadata.renderMode === RenderMode.Prerender) { @@ -192,11 +192,7 @@ async function* handleRoute(options: { // Load and process lazy-loaded child routes if (loadChildren) { if (ɵentryName) { - // When using `loadChildren`, the entire feature area (including multiple routes) is loaded. - // As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies - // across different child routes. In contrast, `loadComponent` only loads a single component, which allows - // for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route. - appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false); + appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata); } const loadedChildRoutes = await loadChildrenHelper( @@ -336,7 +332,6 @@ function appendPreloadToMetadata( entryName: string, entryPointToBrowserMapping: EntryPointToBrowserMapping, metadata: ServerConfigRouteTreeNodeMetadata, - includeDynamicImports: boolean, ): void { const existingPreloads = metadata.preload ?? []; if (!entryPointToBrowserMapping || existingPreloads.length >= MODULE_PRELOAD_MAX) { @@ -350,13 +345,8 @@ function appendPreloadToMetadata( // Merge existing preloads with new ones, ensuring uniqueness and limiting the total to the maximum allowed. const combinedPreloads: Set = new Set(existingPreloads); - for (const { dynamicImport, path } of preload) { - if (dynamicImport && !includeDynamicImports) { - continue; - } - - combinedPreloads.add(path); - + for (const href of preload) { + combinedPreloads.add(href); if (combinedPreloads.size === MODULE_PRELOAD_MAX) { break; } diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts index 19697ace5657..f1437392492d 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts @@ -143,17 +143,15 @@ const RESPONSE_EXPECTS: Record< matches: [ //, //, - //, ], - notMatches: [/home/, /ssr/, /csr/, /ssg-two/, /ssg\-component/], + notMatches: [/home/, /ssr/, /csr/, /ssg-two/, /ssg\-component/, /cross-dep-/], }, '/ssg/two': { matches: [ //, //, - //, ], - notMatches: [/home/, /ssr/, /csr/, /ssg-one/, /ssg\-component/], + notMatches: [/home/, /ssr/, /csr/, /ssg-one/, /ssg\-component/, /cross-dep-/], }, '/ssr': { matches: [//],