Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions goldens/circular-deps/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
"packages/angular_devkit/build_angular/src/tools/esbuild/bundler-execution-result.ts",
"packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts"
],
[
"packages/angular_devkit/build_angular/src/tools/webpack/utils/stats.ts",
"packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts"
],
[
"packages/angular/cli/src/analytics/analytics-collector.ts",
"packages/angular/cli/src/command-builder/command-module.ts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../../builders/application/options';
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
import { Spinner } from '../../utils/spinner';
import { BundleStats, generateEsbuildBuildStatsTable } from '../webpack/utils/stats';
import { BundleStats, generateEsbuildBuildStatsTable } from '../../utils/stats-table';
import { BuildOutputFile, BuildOutputFileType, InitialFileRecord } from './bundler-context';
import { BuildOutputAsset, ExecutionResult } from './bundler-execution-result';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,15 @@ import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
import { logging } from '@angular-devkit/core';
import assert from 'node:assert';
import * as path from 'node:path';
import { stripVTControlCharacters } from 'node:util';
import { Configuration, StatsCompilation } from 'webpack';
import { Schema as BrowserBuilderOptions } from '../../../builders/browser/schema';
import { normalizeOptimization } from '../../../utils';
import { BudgetCalculatorResult } from '../../../utils/bundle-calculator';
import { colors as ansiColors } from '../../../utils/color';
import { BundleStats, generateBuildStatsTable } from '../../../utils/stats-table';
import { markAsyncChunksNonInitial } from './async-chunks';
import { WebpackStatsOptions, getStatsOptions, normalizeExtraEntryPoints } from './helpers';

export function formatSize(size: number): string {
if (size <= 0) {
return '0 bytes';
}

const abbreviations = ['bytes', 'kB', 'MB', 'GB'];
const index = Math.floor(Math.log(size) / Math.log(1024));
const roundedSize = size / Math.pow(1024, index);
// bytes don't have a fraction
const fractionDigits = index === 0 ? 0 : 2;

return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
}

export type BundleStatsData = [
files: string,
names: string,
rawSize: number | string,
estimatedTransferSize: number | string,
];
export interface BundleStats {
initial: boolean;
stats: BundleStatsData;
}

function getBuildDuration(webpackStats: StatsCompilation): number {
assert(webpackStats.builtAt, 'buildAt cannot be undefined');
assert(webpackStats.time, 'time cannot be undefined');
Expand Down Expand Up @@ -76,273 +51,6 @@ function generateBundleStats(info: {
};
}

export function generateEsbuildBuildStatsTable(
[browserStats, serverStats]: [browserStats: BundleStats[], serverStats: BundleStats[]],
colors: boolean,
showTotalSize: boolean,
showEstimatedTransferSize: boolean,
budgetFailures?: BudgetCalculatorResult[],
verbose?: boolean,
): string {
const bundleInfo = generateBuildStatsData(
browserStats,
colors,
showTotalSize,
showEstimatedTransferSize,
budgetFailures,
verbose,
);

if (serverStats.length) {
const m = (x: string) => (colors ? ansiColors.magenta(x) : x);
if (browserStats.length) {
bundleInfo.unshift([m('Browser bundles')]);
// Add seperators between browser and server logs
bundleInfo.push([], []);
}

bundleInfo.push(
[m('Server bundles')],
...generateBuildStatsData(serverStats, colors, false, false, undefined, verbose),
);
}

return generateTableText(bundleInfo, colors);
}

export function generateBuildStatsTable(
data: BundleStats[],
colors: boolean,
showTotalSize: boolean,
showEstimatedTransferSize: boolean,
budgetFailures?: BudgetCalculatorResult[],
): string {
const bundleInfo = generateBuildStatsData(
data,
colors,
showTotalSize,
showEstimatedTransferSize,
budgetFailures,
true,
);

return generateTableText(bundleInfo, colors);
}

function generateBuildStatsData(
data: BundleStats[],
colors: boolean,
showTotalSize: boolean,
showEstimatedTransferSize: boolean,
budgetFailures?: BudgetCalculatorResult[],
verbose?: boolean,
): (string | number)[][] {
if (data.length === 0) {
return [];
}

const g = (x: string) => (colors ? ansiColors.green(x) : x);
const c = (x: string) => (colors ? ansiColors.cyan(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 changedLazyChunksCount = 0;
let initialTotalEstimatedTransferSize;
const maxLazyChunksWithoutBudgetFailures = 15;

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);
}
}
}

// Sort descending by raw size
data.sort((a, b) => {
if (a.stats[2] > b.stats[2]) {
return -1;
}

if (a.stats[2] < b.stats[2]) {
return 1;
}

return 0;
});

for (const { initial, stats } of data) {
const [files, names, rawSize, estimatedTransferSize] = stats;
if (
!initial &&
!verbose &&
changedLazyChunksStats.length >= maxLazyChunksWithoutBudgetFailures &&
!budgets.has(names) &&
!budgets.has(files)
) {
// Limit the number of lazy chunks displayed in the stats table when there is no budget failure and not in verbose mode.
changedLazyChunksCount++;
continue;
}

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

if (initial) {
changedEntryChunksStats.push(data);
if (typeof rawSize === 'number') {
initialTotalRawSize += rawSize;
}
if (showEstimatedTransferSize && typeof estimatedTransferSize === 'number') {
if (initialTotalEstimatedTransferSize === undefined) {
initialTotalEstimatedTransferSize = 0;
}
initialTotalEstimatedTransferSize += estimatedTransferSize;
}
} else {
changedLazyChunksStats.push(data);
changedLazyChunksCount++;
}
}

const bundleInfo: (string | number)[][] = [];
const baseTitles = ['Names', 'Raw size'];

if (showEstimatedTransferSize) {
baseTitles.push('Estimated transfer size');
}

// Entry chunks
if (changedEntryChunksStats.length) {
bundleInfo.push(['Initial chunk files', ...baseTitles].map(bold), ...changedEntryChunksStats);

if (showTotalSize) {
const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x);
const totalSizeElements = [
' ',
'Initial total',
initialSizeTotalColor(formatSize(initialTotalRawSize)),
];
if (showEstimatedTransferSize) {
totalSizeElements.push(
typeof initialTotalEstimatedTransferSize === 'number'
? formatSize(initialTotalEstimatedTransferSize)
: '-',
);
}
bundleInfo.push([], totalSizeElements.map(bold));
}
}

// Seperator
if (changedEntryChunksStats.length && changedLazyChunksStats.length) {
bundleInfo.push([]);
}

// Lazy chunks
if (changedLazyChunksStats.length) {
bundleInfo.push(['Lazy chunk files', ...baseTitles].map(bold), ...changedLazyChunksStats);

if (changedLazyChunksCount > changedLazyChunksStats.length) {
bundleInfo.push([
dim(
`...and ${changedLazyChunksCount - changedLazyChunksStats.length} more lazy chunks files. ` +
'Use "--verbose" to show all the files.',
),
]);
}
}

return bundleInfo;
}

function generateTableText(bundleInfo: (string | number)[][], colors: boolean): string {
const skipText = (value: string) => value.includes('...and ');
const longest: number[] = [];
for (const item of bundleInfo) {
for (let i = 0; i < item.length; i++) {
if (item[i] === undefined) {
continue;
}

const currentItem = item[i].toString();
if (skipText(currentItem)) {
continue;
}

const currentLongest = (longest[i] ??= 0);
const currentItemLength = stripVTControlCharacters(currentItem).length;
if (currentLongest < currentItemLength) {
longest[i] = currentItemLength;
}
}
}

const seperator = colors ? ansiColors.dim(' | ') : ' | ';
const outputTable: string[] = [];
for (const item of bundleInfo) {
for (let i = 0; i < longest.length; i++) {
if (item[i] === undefined) {
continue;
}

const currentItem = item[i].toString();
if (skipText(currentItem)) {
continue;
}

const currentItemLength = stripVTControlCharacters(currentItem).length;
const stringPad = ' '.repeat(longest[i] - currentItemLength);
// Values in columns at index 2 and 3 (Raw and Estimated sizes) are always right aligned.
item[i] = i >= 2 ? stringPad + currentItem : currentItem + stringPad;
}

outputTable.push(item.join(seperator));
}

return outputTable.join('\n');
}

// We use this cache because we can have multiple builders running in the same process,
// where each builder has different output path.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { Budget, Type } from '../builders/browser/schema';
import { formatSize } from '../tools/webpack/utils/stats';
import { formatSize } from './format-bytes';

interface Size {
size: number;
Expand Down
21 changes: 21 additions & 0 deletions packages/angular_devkit/build_angular/src/utils/format-bytes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export function formatSize(size: number): string {
if (size <= 0) {
return '0 bytes';
}

const abbreviations = ['bytes', 'kB', 'MB', 'GB'];
const index = Math.floor(Math.log(size) / Math.log(1024));
const roundedSize = size / Math.pow(1024, index);
// bytes don't have a fraction
const fractionDigits = index === 0 ? 0 : 2;

return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
}
Loading