Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): support component style budgets …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
clydin authored and alan-agius4 committed Oct 9, 2023
1 parent c920802 commit 9e42530
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 17 deletions.
Expand Up @@ -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);
Expand Down
Expand Up @@ -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';
Expand Down
Expand Up @@ -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; }
Expand Down Expand Up @@ -118,7 +118,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
level: 'warn',
message: jasmine.stringMatching(new RegExp(`Warning.+app.component.${ext}`)),
message: jasmine.stringMatching(new RegExp(`app.component.${ext}`)),
}),
);
});
Expand Down
Expand Up @@ -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;
Expand Down
Expand Up @@ -45,6 +45,7 @@ export interface BudgetChunk {
export interface BudgetAsset {
name: string;
size: number;
componentStyle?: boolean;
}

export interface BudgetStats {
Expand Down Expand Up @@ -118,23 +119,15 @@ export function* calculateThresholds(budget: Budget): IterableIterator<Threshold
* Calculates the sizes for bundles in the budget type provided.
*/
function calculateSizes(budget: Budget, stats: BudgetStats): Size[] {
if (budget.type === Type.AnyComponentStyle) {
// Component style size information is not available post-build, this must
// be checked mid-build via the `AnyComponentStyleBudgetChecker` plugin.
throw new Error(
'Can not calculate size of AnyComponentStyle. Use `AnyComponentStyleBudgetChecker` instead.',
);
}

type NonComponentStyleBudgetTypes = Exclude<Budget['type'], Type.AnyComponentStyle>;
type CalculatorTypes = {
new (budget: Budget, chunks: BudgetChunk[], assets: BudgetAsset[]): Calculator;
};
const calculatorMap: Record<NonComponentStyleBudgetTypes, CalculatorTypes> = {
const calculatorMap: Record<Budget['type'], CalculatorTypes> = {
all: AllCalculator,
allScript: AllScriptCalculator,
any: AnyCalculator,
anyScript: AnyScriptCalculator,
anyComponentStyle: AnyComponentStyleCalculator,
bundle: BundleCalculator,
initial: InitialCalculator,
};
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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<BudgetCalculatorResult> {
// 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);
Expand Down

0 comments on commit 9e42530

Please sign in to comment.