Skip to content

Commit bc17cf0

Browse files
committedNov 22, 2021
feat(@angular-devkit/build-angular): colorize file raw sizes based on failing budgets
1 parent 50944d4 commit bc17cf0

File tree

5 files changed

+106
-43
lines changed

5 files changed

+106
-43
lines changed
 

‎goldens/circular-deps/packages.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
[]
1+
[
2+
[
3+
"packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts",
4+
"packages/angular_devkit/build_angular/src/webpack/utils/stats.ts"
5+
]
6+
]

‎packages/angular_devkit/build_angular/src/builders/browser/index.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import {
2222
normalizeOptimization,
2323
urlJoin,
2424
} from '../../utils';
25-
import { ThresholdSeverity, checkBudgets } from '../../utils/bundle-calculator';
25+
import {
26+
BudgetCalculatorResult,
27+
ThresholdSeverity,
28+
checkBudgets,
29+
} from '../../utils/bundle-calculator';
2630
import { colors } from '../../utils/color';
2731
import { copyAssets } from '../../utils/copy-assets';
2832
import { i18nInlineEmittedFiles } from '../../utils/i18n-inlining';
@@ -234,8 +238,9 @@ export function buildWebpackBrowser(
234238

235239
// Check for budget errors and display them to the user.
236240
const budgets = options.budgets;
241+
let budgetFailures: BudgetCalculatorResult[] | undefined;
237242
if (budgets?.length) {
238-
const budgetFailures = checkBudgets(budgets, webpackStats);
243+
budgetFailures = [...checkBudgets(budgets, webpackStats)];
239244
for (const { severity, message } of budgetFailures) {
240245
switch (severity) {
241246
case ThresholdSeverity.Warning:
@@ -354,7 +359,7 @@ export function buildWebpackBrowser(
354359
}
355360
}
356361

357-
webpackStatsLogger(context.logger, webpackStats, config);
362+
webpackStatsLogger(context.logger, webpackStats, config, budgetFailures);
358363

359364
return { success: buildSuccess };
360365
}

‎packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export enum ThresholdSeverity {
3131
Error = 'error',
3232
}
3333

34+
export interface BudgetCalculatorResult {
35+
severity: ThresholdSeverity;
36+
message: string;
37+
label?: string;
38+
}
39+
3440
export function* calculateThresholds(budget: Budget): IterableIterator<Threshold> {
3541
if (budget.maximumWarning) {
3642
yield {
@@ -181,7 +187,7 @@ class BundleCalculator extends Calculator {
181187
.map((chunk) => this.calculateChunkSize(chunk))
182188
.reduce((l, r) => l + r, 0);
183189

184-
return [{ size, label: `bundle ${this.budget.name}` }];
190+
return [{ size, label: this.budget.name }];
185191
}
186192
}
187193

@@ -295,7 +301,7 @@ function calculateBytes(input: string, baseline?: string, factor: 1 | -1 = 1): n
295301
export function* checkBudgets(
296302
budgets: Budget[],
297303
webpackStats: StatsCompilation,
298-
): IterableIterator<{ severity: ThresholdSeverity; message: string }> {
304+
): IterableIterator<BudgetCalculatorResult> {
299305
// Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker`.
300306
const computableBudgets = budgets.filter((budget) => budget.type !== Type.AnyComponentStyle);
301307

@@ -311,7 +317,7 @@ export function* checkThresholds(
311317
thresholds: IterableIterator<Threshold>,
312318
size: number,
313319
label?: string,
314-
): IterableIterator<{ severity: ThresholdSeverity; message: string }> {
320+
): IterableIterator<BudgetCalculatorResult> {
315321
for (const threshold of thresholds) {
316322
switch (threshold.type) {
317323
case ThresholdType.Max: {
@@ -322,6 +328,7 @@ export function* checkThresholds(
322328
const sizeDifference = formatSize(size - threshold.limit);
323329
yield {
324330
severity: threshold.severity,
331+
label,
325332
message: `${label} exceeded maximum budget. Budget ${formatSize(
326333
threshold.limit,
327334
)} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`,
@@ -336,6 +343,7 @@ export function* checkThresholds(
336343
const sizeDifference = formatSize(threshold.limit - size);
337344
yield {
338345
severity: threshold.severity,
346+
label,
339347
message: `${label} failed to meet minimum budget. Budget ${formatSize(
340348
threshold.limit,
341349
)} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`,

‎packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('bundle-calculator', () => {
4040
expect(failures.length).toBe(1);
4141
expect(failures).toContain({
4242
severity: ThresholdSeverity.Error,
43+
label: 'foo.js',
4344
message: jasmine.stringMatching('foo.js exceeded maximum budget.'),
4445
});
4546
});
@@ -70,6 +71,7 @@ describe('bundle-calculator', () => {
7071
expect(failures.length).toBe(1);
7172
expect(failures).toContain({
7273
severity: ThresholdSeverity.Error,
74+
label: 'bar.js',
7375
message: jasmine.stringMatching('bar.js failed to meet minimum budget.'),
7476
});
7577
});
@@ -107,6 +109,7 @@ describe('bundle-calculator', () => {
107109
expect(failures.length).toBe(1);
108110
expect(failures).toContain({
109111
severity: ThresholdSeverity.Error,
112+
label: 'foo',
110113
message: jasmine.stringMatching('foo exceeded maximum budget.'),
111114
});
112115
});
@@ -144,6 +147,7 @@ describe('bundle-calculator', () => {
144147
expect(failures.length).toBe(1);
145148
expect(failures).toContain({
146149
severity: ThresholdSeverity.Error,
150+
label: 'bundle initial',
147151
message: jasmine.stringMatching('initial exceeded maximum budget.'),
148152
});
149153
});
@@ -185,6 +189,7 @@ describe('bundle-calculator', () => {
185189
expect(failures.length).toBe(1);
186190
expect(failures).toContain({
187191
severity: ThresholdSeverity.Error,
192+
label: 'total scripts',
188193
message: jasmine.stringMatching('total scripts exceeded maximum budget.'),
189194
});
190195
});
@@ -222,6 +227,7 @@ describe('bundle-calculator', () => {
222227
expect(failures.length).toBe(1);
223228
expect(failures).toContain({
224229
severity: ThresholdSeverity.Error,
230+
label: 'total',
225231
message: jasmine.stringMatching('total exceeded maximum budget.'),
226232
});
227233
});
@@ -292,6 +298,7 @@ describe('bundle-calculator', () => {
292298
expect(failures.length).toBe(1);
293299
expect(failures).toContain({
294300
severity: ThresholdSeverity.Error,
301+
label: 'foo.js',
295302
message: jasmine.stringMatching('foo.js exceeded maximum budget.'),
296303
});
297304
});
@@ -329,6 +336,7 @@ describe('bundle-calculator', () => {
329336
expect(failures.length).toBe(1);
330337
expect(failures).toContain({
331338
severity: ThresholdSeverity.Error,
339+
label: 'foo.ext',
332340
message: jasmine.stringMatching('foo.ext exceeded maximum budget.'),
333341
});
334342
});

‎packages/angular_devkit/build_angular/src/webpack/utils/stats.ts

+73-36
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as path from 'path';
1212
import textTable from 'text-table';
1313
import { Configuration, StatsCompilation } from 'webpack';
1414
import { Schema as BrowserBuilderOptions } from '../../builders/browser/schema';
15+
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
1516
import { colors as ansiColors, removeColor } from '../../utils/color';
1617
import { markAsyncChunksNonInitial } from './async-chunks';
1718
import { getStatsOptions, normalizeExtraEntryPoints } from './helpers';
@@ -71,36 +72,67 @@ function generateBuildStatsTable(
7172
colors: boolean,
7273
showTotalSize: boolean,
7374
showEstimatedTransferSize: boolean,
75+
budgetFailures?: BudgetCalculatorResult[],
7476
): string {
7577
const g = (x: string) => (colors ? ansiColors.greenBright(x) : x);
7678
const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x);
79+
const r = (x: string) => (colors ? ansiColors.redBright(x) : x);
80+
const y = (x: string) => (colors ? ansiColors.yellowBright(x) : x);
7781
const bold = (x: string) => (colors ? ansiColors.bold(x) : x);
7882
const dim = (x: string) => (colors ? ansiColors.dim(x) : x);
7983

84+
const getSizeColor = (name: string, file?: string, defaultColor = c) => {
85+
const severity = budgets.get(name) || (file && budgets.get(file));
86+
switch (severity) {
87+
case 'warning':
88+
return y;
89+
case 'error':
90+
return r;
91+
default:
92+
return defaultColor;
93+
}
94+
};
95+
8096
const changedEntryChunksStats: BundleStatsData[] = [];
8197
const changedLazyChunksStats: BundleStatsData[] = [];
8298

8399
let initialTotalRawSize = 0;
84100
let initialTotalEstimatedTransferSize;
85101

102+
const budgets = new Map<string, string>();
103+
if (budgetFailures) {
104+
for (const { label, severity } of budgetFailures) {
105+
// In some cases a file can have multiple budget failures.
106+
// Favor error.
107+
if (label && (!budgets.has(label) || budgets.get(label) === 'warning')) {
108+
budgets.set(label, severity);
109+
}
110+
}
111+
}
112+
86113
for (const { initial, stats } of data) {
87114
const [files, names, rawSize, estimatedTransferSize] = stats;
88-
115+
const getRawSizeColor = getSizeColor(names, files);
89116
let data: BundleStatsData;
90117

91118
if (showEstimatedTransferSize) {
92119
data = [
93120
g(files),
94121
names,
95-
c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
122+
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
96123
c(
97124
typeof estimatedTransferSize === 'number'
98125
? formatSize(estimatedTransferSize)
99126
: estimatedTransferSize,
100127
),
101128
];
102129
} else {
103-
data = [g(files), names, c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), ''];
130+
data = [
131+
g(files),
132+
names,
133+
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
134+
'',
135+
];
104136
}
105137

106138
if (initial) {
@@ -135,7 +167,12 @@ function generateBuildStatsTable(
135167
if (showTotalSize) {
136168
bundleInfo.push([]);
137169

138-
const totalSizeElements = [' ', 'Initial Total', formatSize(initialTotalRawSize)];
170+
const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x);
171+
const totalSizeElements = [
172+
' ',
173+
'Initial Total',
174+
initialSizeTotalColor(formatSize(initialTotalRawSize)),
175+
];
139176
if (showEstimatedTransferSize) {
140177
totalSizeElements.push(
141178
typeof initialTotalEstimatedTransferSize === 'number'
@@ -180,7 +217,7 @@ function statsToString(
180217
json: StatsCompilation,
181218
// eslint-disable-next-line @typescript-eslint/no-explicit-any
182219
statsConfig: any,
183-
bundleState?: BundleStats[],
220+
budgetFailures?: BudgetCalculatorResult[],
184221
): string {
185222
if (!json.chunks?.length) {
186223
return '';
@@ -189,45 +226,44 @@ function statsToString(
189226
const colors = statsConfig.colors;
190227
const rs = (x: string) => (colors ? ansiColors.reset(x) : x);
191228

192-
const changedChunksStats: BundleStats[] = bundleState ?? [];
229+
const changedChunksStats: BundleStats[] = [];
193230
let unchangedChunkNumber = 0;
194231
let hasEstimatedTransferSizes = false;
195-
if (!bundleState?.length) {
196-
const isFirstRun = !runsCache.has(json.outputPath || '');
197-
198-
for (const chunk of json.chunks) {
199-
// During first build we want to display unchanged chunks
200-
// but unchanged cached chunks are always marked as not rendered.
201-
if (!isFirstRun && !chunk.rendered) {
202-
continue;
203-
}
204232

205-
const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name));
206-
let rawSize = 0;
207-
let estimatedTransferSize;
208-
if (assets) {
209-
for (const asset of assets) {
210-
if (asset.name.endsWith('.map')) {
211-
continue;
212-
}
233+
const isFirstRun = !runsCache.has(json.outputPath || '');
234+
235+
for (const chunk of json.chunks) {
236+
// During first build we want to display unchanged chunks
237+
// but unchanged cached chunks are always marked as not rendered.
238+
if (!isFirstRun && !chunk.rendered) {
239+
continue;
240+
}
241+
242+
const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name));
243+
let rawSize = 0;
244+
let estimatedTransferSize;
245+
if (assets) {
246+
for (const asset of assets) {
247+
if (asset.name.endsWith('.map')) {
248+
continue;
249+
}
213250

214-
rawSize += asset.size;
251+
rawSize += asset.size;
215252

216-
if (typeof asset.info.estimatedTransferSize === 'number') {
217-
if (estimatedTransferSize === undefined) {
218-
estimatedTransferSize = 0;
219-
hasEstimatedTransferSizes = true;
220-
}
221-
estimatedTransferSize += asset.info.estimatedTransferSize;
253+
if (typeof asset.info.estimatedTransferSize === 'number') {
254+
if (estimatedTransferSize === undefined) {
255+
estimatedTransferSize = 0;
256+
hasEstimatedTransferSizes = true;
222257
}
258+
estimatedTransferSize += asset.info.estimatedTransferSize;
223259
}
224260
}
225-
changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
226261
}
227-
unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
228-
229-
runsCache.add(json.outputPath || '');
262+
changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
230263
}
264+
unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
265+
266+
runsCache.add(json.outputPath || '');
231267

232268
// Sort chunks by size in descending order
233269
changedChunksStats.sort((a, b) => {
@@ -247,6 +283,7 @@ function statsToString(
247283
colors,
248284
unchangedChunkNumber === 0,
249285
hasEstimatedTransferSizes,
286+
budgetFailures,
250287
);
251288

252289
// In some cases we do things outside of webpack context
@@ -387,9 +424,9 @@ export function webpackStatsLogger(
387424
logger: logging.LoggerApi,
388425
json: StatsCompilation,
389426
config: Configuration,
390-
bundleStats?: BundleStats[],
427+
budgetFailures?: BudgetCalculatorResult[],
391428
): void {
392-
logger.info(statsToString(json, config.stats, bundleStats));
429+
logger.info(statsToString(json, config.stats, budgetFailures));
393430

394431
if (statsHasWarnings(json)) {
395432
logger.warn(statsWarningsToString(json, config.stats));

0 commit comments

Comments
 (0)
Please sign in to comment.