From 3aff3511ab4a35d71e576680daedb0fca0f19ee4 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 27 Jun 2024 07:32:54 -0400 Subject: [PATCH] refactor(@angular/build): use build output files directly in stats and budgets The bundle budget calculators and the console build stats output are now calculated directly from the build output file information instead of the esbuild metafile where possible. This provides a more generic method of accessing the information and can more accurately account for post-processing steps that may alter the output files. The metafile is still used for component style budgets and lazy chunk name information. (cherry picked from commit 75abe2cc52282fdb8c32cb74082b9daea996e510) --- .../src/builders/application/execute-build.ts | 3 +- .../build/src/tools/esbuild/budget-stats.ts | 36 ++++++++++++------- .../angular/build/src/tools/esbuild/utils.ts | 26 ++++++-------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index 7ac168d2768c..5bb6ace1cc02 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -104,7 +104,7 @@ export async function executeBuild( // Analyze files for bundle budget failures if present let budgetFailures: BudgetCalculatorResult[] | undefined; if (options.budgets) { - const compatStats = generateBudgetStats(metafile, initialFiles); + const compatStats = generateBudgetStats(metafile, outputFiles, initialFiles); budgetFailures = [...checkBudgets(options.budgets, compatStats, true)]; for (const { message, severity } of budgetFailures) { if (severity === 'error') { @@ -191,6 +191,7 @@ export async function executeBuild( executionResult.addLog( logBuildStats( metafile, + outputFiles, initialFiles, budgetFailures, colors, diff --git a/packages/angular/build/src/tools/esbuild/budget-stats.ts b/packages/angular/build/src/tools/esbuild/budget-stats.ts index 981d9bf5604b..1adc7672c50c 100644 --- a/packages/angular/build/src/tools/esbuild/budget-stats.ts +++ b/packages/angular/build/src/tools/esbuild/budget-stats.ts @@ -8,8 +8,12 @@ import type { Metafile } from 'esbuild'; import type { BudgetStats } from '../../utils/bundle-calculator'; -import type { InitialFileRecord } from './bundler-context'; -import { getEntryPointName } from './utils'; +import { + type BuildOutputFile, + BuildOutputFileType, + type InitialFileRecord, +} from './bundler-context'; +import { getChunkNameFromMetafile } from './utils'; /** * Generates a bundle budget calculator compatible stats object that provides @@ -21,6 +25,7 @@ import { getEntryPointName } from './utils'; */ export function generateBudgetStats( metafile: Metafile, + outputFiles: BuildOutputFile[], initialFiles: Map, ): BudgetStats { const stats: Required = { @@ -28,23 +33,18 @@ export function generateBudgetStats( assets: [], }; - for (const [file, entry] of Object.entries(metafile.outputs)) { + for (const { path: file, size, type } of outputFiles) { if (!file.endsWith('.js') && !file.endsWith('.css')) { continue; } // Exclude server bundles - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((entry as any)['ng-platform-server']) { + if (type === BuildOutputFileType.Server) { continue; } const initialRecord = initialFiles.get(file); - let name = initialRecord?.name; - if (name === undefined && entry.entryPoint) { - // For non-initial lazy modules, convert the entry point file into a Webpack compatible name - name = getEntryPointName(entry.entryPoint); - } + const name = initialRecord?.name ?? getChunkNameFromMetafile(metafile, file); stats.chunks.push({ files: [file], @@ -52,13 +52,25 @@ export function generateBudgetStats( names: name ? [name] : undefined, }); + stats.assets.push({ + name: file, + size, + }); + } + + // Add component styles from metafile + // TODO: Provide this information directly from the AOT compiler + for (const entry of Object.values(metafile.outputs)) { // 'ng-component' is set by the angular plugin's component stylesheet bundler // eslint-disable-next-line @typescript-eslint/no-explicit-any const componentStyle: boolean = (entry as any)['ng-component']; + if (!componentStyle) { + continue; + } stats.assets.push({ - // Component styles use the input file while all other outputs use the result file - name: (componentStyle && Object.keys(entry.inputs)[0]) || file, + // Component styles use the input file + name: Object.keys(entry.inputs)[0], size: entry.bytes, componentStyle, }); diff --git a/packages/angular/build/src/tools/esbuild/utils.ts b/packages/angular/build/src/tools/esbuild/utils.ts index 8a68edca3424..3b64ddd60e83 100644 --- a/packages/angular/build/src/tools/esbuild/utils.ts +++ b/packages/angular/build/src/tools/esbuild/utils.ts @@ -27,6 +27,7 @@ import { BuildOutputAsset, ExecutionResult } from './bundler-execution-result'; export function logBuildStats( metafile: Metafile, + outputFiles: BuildOutputFile[], initial: Map, budgetFailures: BudgetCalculatorResult[] | undefined, colors: boolean, @@ -39,39 +40,28 @@ export function logBuildStats( const serverStats: BundleStats[] = []; let unchangedCount = 0; - for (const [file, output] of Object.entries(metafile.outputs)) { + for (const { path: file, size, type } of outputFiles) { // Only display JavaScript and CSS files if (!/\.(?:css|m?js)$/.test(file)) { continue; } - // Skip internal component resources - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((output as any)['ng-component']) { - continue; - } - // Show only changed files if a changed list is provided if (changedFiles && !changedFiles.has(file)) { ++unchangedCount; continue; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const isPlatformServer = (output as any)['ng-platform-server']; + const isPlatformServer = type === BuildOutputFileType.Server; if (isPlatformServer && !ssrOutputEnabled) { // Only log server build stats when SSR is enabled. continue; } - let name = initial.get(file)?.name; - if (name === undefined && output.entryPoint) { - name = getEntryPointName(output.entryPoint); - } - + const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafile, file); const stat: BundleStats = { initial: initial.has(file), - stats: [file, name ?? '-', output.bytes, estimatedTransferSizes?.get(file) ?? '-'], + stats: [file, name ?? '-', size, estimatedTransferSizes?.get(file) ?? '-'], }; if (isPlatformServer) { @@ -102,6 +92,12 @@ export function logBuildStats( return ''; } +export function getChunkNameFromMetafile(metafile: Metafile, file: string): string | undefined { + if (metafile.outputs[file]?.entryPoint) { + return getEntryPointName(metafile.outputs[file].entryPoint); + } +} + export async function calculateEstimatedTransferSizes( outputFiles: OutputFile[], ): Promise> {