diff --git a/packages/angular/build/src/tools/esbuild/bundler-context.ts b/packages/angular/build/src/tools/esbuild/bundler-context.ts index c9168aa591da..8bfd1343f340 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-context.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-context.ts @@ -43,6 +43,7 @@ export interface InitialFileRecord { type: 'script' | 'style'; external?: boolean; serverFile: boolean; + depth: number; } export enum BuildOutputFileType { @@ -298,6 +299,7 @@ export class BundlerContext { type, entrypoint: true, serverFile: this.#platformIsServer, + depth: 0, }; if (!this.initialFilter || this.initialFilter(record)) { @@ -308,10 +310,19 @@ export class BundlerContext { } // Analyze for transitive initial files - const files = [...initialFiles.keys()]; - for (const file of files) { - for (const initialImport of result.metafile.outputs[file].imports) { - if (initialFiles.has(initialImport.path)) { + const entriesToAnalyze = [...initialFiles]; + let currentEntry; + while ((currentEntry = entriesToAnalyze.pop())) { + const [entryPath, entryRecord] = currentEntry; + + for (const initialImport of result.metafile.outputs[entryPath].imports) { + const existingRecord = initialFiles.get(initialImport.path); + if (existingRecord) { + // Store the smallest value depth + if (existingRecord.depth > entryRecord.depth + 1) { + existingRecord.depth = entryRecord.depth + 1; + } + continue; } @@ -321,6 +332,7 @@ export class BundlerContext { entrypoint: false, external: initialImport.external, serverFile: this.#platformIsServer, + depth: entryRecord.depth + 1, }; if (!this.initialFilter || this.initialFilter(record)) { @@ -328,7 +340,7 @@ export class BundlerContext { } if (!initialImport.external) { - files.push(initialImport.path); + entriesToAnalyze.push([initialImport.path, record]); } } } diff --git a/packages/angular/build/src/tools/esbuild/index-html-generator.ts b/packages/angular/build/src/tools/esbuild/index-html-generator.ts index ffa330260b67..62a2cc5ffb91 100644 --- a/packages/angular/build/src/tools/esbuild/index-html-generator.ts +++ b/packages/angular/build/src/tools/esbuild/index-html-generator.ts @@ -12,6 +12,12 @@ import { NormalizedApplicationBuildOptions } from '../../builders/application/op import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator'; import { BuildOutputFile, BuildOutputFileType, InitialFileRecord } from './bundler-context'; +/** + * The maximum number of module preload link elements that should be added for + * initial scripts. + */ +const MODULE_PRELOAD_MAX = 3; + export async function generateIndexHtml( initialFiles: Map, outputFiles: BuildOutputFile[], @@ -39,13 +45,20 @@ export async function generateIndexHtml( assert(indexHtmlOptions, 'indexHtmlOptions cannot be undefined.'); if (!externalPackages && indexHtmlOptions.preloadInitial) { + let modulePreloadCount = 0; for (const [key, value] of initialFiles) { if (value.entrypoint || value.serverFile) { // Entry points are already referenced in the HTML continue; } - if (value.type === 'script') { + // Only add shallow preloads + if (value.depth > 1) { + continue; + } + + if (value.type === 'script' && modulePreloadCount < MODULE_PRELOAD_MAX) { + modulePreloadCount++; hints.push({ url: key, mode: 'modulepreload' as const }); } else if (value.type === 'style') { // Provide an "as" value of "style" to ensure external URLs which may not have a