Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): colorize file raw sizes based on…
Browse files Browse the repository at this point in the history
… failing budgets
  • Loading branch information
alan-agius4 committed Nov 22, 2021
1 parent 50944d4 commit bc17cf0
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 43 deletions.
7 changes: 6 additions & 1 deletion goldens/circular-deps/packages.json
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
[]
[
[
"packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts",
"packages/angular_devkit/build_angular/src/webpack/utils/stats.ts"
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
normalizeOptimization,
urlJoin,
} from '../../utils';
import { ThresholdSeverity, checkBudgets } from '../../utils/bundle-calculator';
import {
BudgetCalculatorResult,
ThresholdSeverity,
checkBudgets,
} from '../../utils/bundle-calculator';
import { colors } from '../../utils/color';
import { copyAssets } from '../../utils/copy-assets';
import { i18nInlineEmittedFiles } from '../../utils/i18n-inlining';
Expand Down Expand Up @@ -234,8 +238,9 @@ export function buildWebpackBrowser(

// Check for budget errors and display them to the user.
const budgets = options.budgets;
let budgetFailures: BudgetCalculatorResult[] | undefined;
if (budgets?.length) {
const budgetFailures = checkBudgets(budgets, webpackStats);
budgetFailures = [...checkBudgets(budgets, webpackStats)];
for (const { severity, message } of budgetFailures) {
switch (severity) {
case ThresholdSeverity.Warning:
Expand Down Expand Up @@ -354,7 +359,7 @@ export function buildWebpackBrowser(
}
}

webpackStatsLogger(context.logger, webpackStats, config);
webpackStatsLogger(context.logger, webpackStats, config, budgetFailures);

return { success: buildSuccess };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export enum ThresholdSeverity {
Error = 'error',
}

export interface BudgetCalculatorResult {
severity: ThresholdSeverity;
message: string;
label?: string;
}

export function* calculateThresholds(budget: Budget): IterableIterator<Threshold> {
if (budget.maximumWarning) {
yield {
Expand Down Expand Up @@ -181,7 +187,7 @@ class BundleCalculator extends Calculator {
.map((chunk) => this.calculateChunkSize(chunk))
.reduce((l, r) => l + r, 0);

return [{ size, label: `bundle ${this.budget.name}` }];
return [{ size, label: this.budget.name }];
}
}

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

Expand All @@ -311,7 +317,7 @@ export function* checkThresholds(
thresholds: IterableIterator<Threshold>,
size: number,
label?: string,
): IterableIterator<{ severity: ThresholdSeverity; message: string }> {
): IterableIterator<BudgetCalculatorResult> {
for (const threshold of thresholds) {
switch (threshold.type) {
case ThresholdType.Max: {
Expand All @@ -322,6 +328,7 @@ export function* checkThresholds(
const sizeDifference = formatSize(size - threshold.limit);
yield {
severity: threshold.severity,
label,
message: `${label} exceeded maximum budget. Budget ${formatSize(
threshold.limit,
)} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`,
Expand All @@ -336,6 +343,7 @@ export function* checkThresholds(
const sizeDifference = formatSize(threshold.limit - size);
yield {
severity: threshold.severity,
label,
message: `${label} failed to meet minimum budget. Budget ${formatSize(
threshold.limit,
)} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'foo.js',
message: jasmine.stringMatching('foo.js exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -70,6 +71,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'bar.js',
message: jasmine.stringMatching('bar.js failed to meet minimum budget.'),
});
});
Expand Down Expand Up @@ -107,6 +109,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'foo',
message: jasmine.stringMatching('foo exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -144,6 +147,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'bundle initial',
message: jasmine.stringMatching('initial exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -185,6 +189,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'total scripts',
message: jasmine.stringMatching('total scripts exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -222,6 +227,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'total',
message: jasmine.stringMatching('total exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -292,6 +298,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'foo.js',
message: jasmine.stringMatching('foo.js exceeded maximum budget.'),
});
});
Expand Down Expand Up @@ -329,6 +336,7 @@ describe('bundle-calculator', () => {
expect(failures.length).toBe(1);
expect(failures).toContain({
severity: ThresholdSeverity.Error,
label: 'foo.ext',
message: jasmine.stringMatching('foo.ext exceeded maximum budget.'),
});
});
Expand Down
109 changes: 73 additions & 36 deletions packages/angular_devkit/build_angular/src/webpack/utils/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as path from 'path';
import textTable from 'text-table';
import { Configuration, StatsCompilation } from 'webpack';
import { Schema as BrowserBuilderOptions } from '../../builders/browser/schema';
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
import { colors as ansiColors, removeColor } from '../../utils/color';
import { markAsyncChunksNonInitial } from './async-chunks';
import { getStatsOptions, normalizeExtraEntryPoints } from './helpers';
Expand Down Expand Up @@ -71,36 +72,67 @@ function generateBuildStatsTable(
colors: boolean,
showTotalSize: boolean,
showEstimatedTransferSize: boolean,
budgetFailures?: BudgetCalculatorResult[],
): string {
const g = (x: string) => (colors ? ansiColors.greenBright(x) : x);
const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x);
const r = (x: string) => (colors ? ansiColors.redBright(x) : x);
const y = (x: string) => (colors ? ansiColors.yellowBright(x) : x);
const bold = (x: string) => (colors ? ansiColors.bold(x) : x);
const dim = (x: string) => (colors ? ansiColors.dim(x) : x);

const getSizeColor = (name: string, file?: string, defaultColor = c) => {
const severity = budgets.get(name) || (file && budgets.get(file));
switch (severity) {
case 'warning':
return y;
case 'error':
return r;
default:
return defaultColor;
}
};

const changedEntryChunksStats: BundleStatsData[] = [];
const changedLazyChunksStats: BundleStatsData[] = [];

let initialTotalRawSize = 0;
let initialTotalEstimatedTransferSize;

const budgets = new Map<string, string>();
if (budgetFailures) {
for (const { label, severity } of budgetFailures) {
// In some cases a file can have multiple budget failures.
// Favor error.
if (label && (!budgets.has(label) || budgets.get(label) === 'warning')) {
budgets.set(label, severity);
}
}
}

for (const { initial, stats } of data) {
const [files, names, rawSize, estimatedTransferSize] = stats;

const getRawSizeColor = getSizeColor(names, files);
let data: BundleStatsData;

if (showEstimatedTransferSize) {
data = [
g(files),
names,
c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
c(
typeof estimatedTransferSize === 'number'
? formatSize(estimatedTransferSize)
: estimatedTransferSize,
),
];
} else {
data = [g(files), names, c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), ''];
data = [
g(files),
names,
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
'',
];
}

if (initial) {
Expand Down Expand Up @@ -135,7 +167,12 @@ function generateBuildStatsTable(
if (showTotalSize) {
bundleInfo.push([]);

const totalSizeElements = [' ', 'Initial Total', formatSize(initialTotalRawSize)];
const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x);
const totalSizeElements = [
' ',
'Initial Total',
initialSizeTotalColor(formatSize(initialTotalRawSize)),
];
if (showEstimatedTransferSize) {
totalSizeElements.push(
typeof initialTotalEstimatedTransferSize === 'number'
Expand Down Expand Up @@ -180,7 +217,7 @@ function statsToString(
json: StatsCompilation,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
statsConfig: any,
bundleState?: BundleStats[],
budgetFailures?: BudgetCalculatorResult[],
): string {
if (!json.chunks?.length) {
return '';
Expand All @@ -189,45 +226,44 @@ function statsToString(
const colors = statsConfig.colors;
const rs = (x: string) => (colors ? ansiColors.reset(x) : x);

const changedChunksStats: BundleStats[] = bundleState ?? [];
const changedChunksStats: BundleStats[] = [];
let unchangedChunkNumber = 0;
let hasEstimatedTransferSizes = false;
if (!bundleState?.length) {
const isFirstRun = !runsCache.has(json.outputPath || '');

for (const chunk of json.chunks) {
// During first build we want to display unchanged chunks
// but unchanged cached chunks are always marked as not rendered.
if (!isFirstRun && !chunk.rendered) {
continue;
}

const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name));
let rawSize = 0;
let estimatedTransferSize;
if (assets) {
for (const asset of assets) {
if (asset.name.endsWith('.map')) {
continue;
}
const isFirstRun = !runsCache.has(json.outputPath || '');

for (const chunk of json.chunks) {
// During first build we want to display unchanged chunks
// but unchanged cached chunks are always marked as not rendered.
if (!isFirstRun && !chunk.rendered) {
continue;
}

const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name));
let rawSize = 0;
let estimatedTransferSize;
if (assets) {
for (const asset of assets) {
if (asset.name.endsWith('.map')) {
continue;
}

rawSize += asset.size;
rawSize += asset.size;

if (typeof asset.info.estimatedTransferSize === 'number') {
if (estimatedTransferSize === undefined) {
estimatedTransferSize = 0;
hasEstimatedTransferSizes = true;
}
estimatedTransferSize += asset.info.estimatedTransferSize;
if (typeof asset.info.estimatedTransferSize === 'number') {
if (estimatedTransferSize === undefined) {
estimatedTransferSize = 0;
hasEstimatedTransferSizes = true;
}
estimatedTransferSize += asset.info.estimatedTransferSize;
}
}
changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
}
unchangedChunkNumber = json.chunks.length - changedChunksStats.length;

runsCache.add(json.outputPath || '');
changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
}
unchangedChunkNumber = json.chunks.length - changedChunksStats.length;

runsCache.add(json.outputPath || '');

// Sort chunks by size in descending order
changedChunksStats.sort((a, b) => {
Expand All @@ -247,6 +283,7 @@ function statsToString(
colors,
unchangedChunkNumber === 0,
hasEstimatedTransferSizes,
budgetFailures,
);

// In some cases we do things outside of webpack context
Expand Down Expand Up @@ -387,9 +424,9 @@ export function webpackStatsLogger(
logger: logging.LoggerApi,
json: StatsCompilation,
config: Configuration,
bundleStats?: BundleStats[],
budgetFailures?: BudgetCalculatorResult[],
): void {
logger.info(statsToString(json, config.stats, bundleStats));
logger.info(statsToString(json, config.stats, budgetFailures));

if (statsHasWarnings(json)) {
logger.warn(statsWarningsToString(json, config.stats));
Expand Down

0 comments on commit bc17cf0

Please sign in to comment.