From b92497fdce8c29ed2e1cc1167d1415c494196829 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:44:40 -0400 Subject: [PATCH] feat(@angular-devkit/build-angular): support component style budgets in esbuild builders The `anyComponentStyle` budget type is now supported when using bundle budgets in the esbuild- based builders (`browser-esbuild`/`application`). The configuration and behavior are intended to match that of the Webpack-based builder (`browser`). With this addition, the bundle budget feature is now at feature parity with the Webpack-based builder. The implementation of this budget type requires less code than the Webpack implementation due to the component stylesheet information being present in the application's output metadata. This removes the need for a custom budget plugin to extract the stylesheet size information during the build. --- .../src/builders/application/execute-build.ts | 2 +- .../src/builders/application/options.ts | 1 - .../tests/options/bundle-budgets_spec.ts | 4 +-- .../src/tools/esbuild/budget-stats.ts | 12 ++++++- .../src/utils/bundle-calculator.ts | 34 ++++++++++++------- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts b/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts index 0e74e13a9b4f..b41b52763eb7 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts @@ -257,7 +257,7 @@ export async function executeBuild( let budgetFailures; if (options.budgets) { const compatStats = generateBudgetStats(metafile, initialFiles); - budgetFailures = [...checkBudgets(options.budgets, compatStats)]; + budgetFailures = [...checkBudgets(options.budgets, compatStats, true)]; for (const { severity, message } of budgetFailures) { if (severity === 'error') { context.logger.error(message); diff --git a/packages/angular_devkit/build_angular/src/builders/application/options.ts b/packages/angular_devkit/build_angular/src/builders/application/options.ts index b95ff6b1c5da..c1ae9214158b 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/options.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/options.ts @@ -14,7 +14,6 @@ import { normalizeGlobalStyles, } from '../../tools/webpack/utils/helpers'; import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils'; -import { calculateThresholds } from '../../utils/bundle-calculator'; import { I18nOptions, createI18nOptions } from '../../utils/i18n-options'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { generateEntryPoints } from '../../utils/package-chunk-sort'; diff --git a/packages/angular_devkit/build_angular/src/builders/application/tests/options/bundle-budgets_spec.ts b/packages/angular_devkit/build_angular/src/builders/application/tests/options/bundle-budgets_spec.ts index 08347eab7dfe..3d8cbf39b63c 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/tests/options/bundle-budgets_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/tests/options/bundle-budgets_spec.ts @@ -88,7 +88,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { }); CSS_EXTENSIONS.forEach((ext) => { - xit(`shows warnings for large component ${ext} when using 'anyComponentStyle' when AOT`, async () => { + it(`shows warnings for large component ${ext} when using 'anyComponentStyle' when AOT`, async () => { const cssContent = ` .foo { color: white; padding: 1px; } .buz { color: white; padding: 2px; } @@ -118,7 +118,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { expect(logs).toContain( jasmine.objectContaining({ level: 'warn', - message: jasmine.stringMatching(new RegExp(`Warning.+app.component.${ext}`)), + message: jasmine.stringMatching(new RegExp(`app.component.${ext}`)), }), ); }); diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/budget-stats.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/budget-stats.ts index 7f4ba0cc21fb..0737e9825f24 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/budget-stats.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/budget-stats.ts @@ -47,7 +47,17 @@ export function generateBudgetStats( initial: !!initialRecord, names: name ? [name] : undefined, }); - stats.assets.push({ name: file, size: entry.bytes }); + + // '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']; + + 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, + size: entry.bytes, + componentStyle, + }); } return stats; diff --git a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts index 6ceec0ce24bb..1d661a97c46b 100644 --- a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts @@ -45,6 +45,7 @@ export interface BudgetChunk { export interface BudgetAsset { name: string; size: number; + componentStyle?: boolean; } export interface BudgetStats { @@ -118,23 +119,15 @@ export function* calculateThresholds(budget: Budget): IterableIterator; type CalculatorTypes = { new (budget: Budget, chunks: BudgetChunk[], assets: BudgetAsset[]): Calculator; }; - const calculatorMap: Record = { + const calculatorMap: Record = { all: AllCalculator, allScript: AllScriptCalculator, any: AnyCalculator, anyScript: AnyScriptCalculator, + anyComponentStyle: AnyComponentStyleCalculator, bundle: BundleCalculator, initial: InitialCalculator, }; @@ -279,6 +272,20 @@ class AnyCalculator extends Calculator { } } +/** + * Any compoonent stylesheet + */ +class AnyComponentStyleCalculator extends Calculator { + calculate() { + return this.assets + .filter((asset) => asset.componentStyle) + .map((asset) => ({ + size: this.getAssetSize(asset), + label: asset.name, + })); + } +} + /** * Calculate the bytes given a string value. */ @@ -316,9 +323,12 @@ function calculateBytes(input: string, baseline?: string, factor: 1 | -1 = 1): n export function* checkBudgets( budgets: Budget[], stats: BudgetStats, + checkComponentStyles?: boolean, ): IterableIterator { - // Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker`. - const computableBudgets = budgets.filter((budget) => budget.type !== Type.AnyComponentStyle); + // Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker` unless requested + const computableBudgets = checkComponentStyles + ? budgets + : budgets.filter((budget) => budget.type !== Type.AnyComponentStyle); for (const budget of computableBudgets) { const sizes = calculateSizes(budget, stats);