Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): add estimated transfer size to b…
Browse files Browse the repository at this point in the history
…uild output report

When optimizations are enabled (either scripts or styles), an additional column will now be present in the output report shown in the console for an application build. This additonal column will display the estimated transfer size for each file as well as the total initial estimated transfer size for the initial files. The estimated transfer size is determined by calculating the compressed size of the file using brotli's default settings. In a development configuration (a configuration with optimizations disabled), the calculations are not performed to avoid any potential increase in rebuild speed due to the large size of unoptimized files.

Closes: #21394
  • Loading branch information
clydin authored and dgp1130 committed Nov 17, 2021
1 parent 3aa3bfc commit bc85637
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ScriptsWebpackPlugin,
} from '../plugins';
import { ProgressPlugin } from '../plugins/progress-plugin';
import { TransferSizePlugin } from '../plugins/transfer-size-plugin';
import { createIvyPlugin } from '../plugins/typescript';
import {
assetPatterns,
Expand Down Expand Up @@ -287,6 +288,10 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
);
}

if (platform === 'browser' && (scriptsOptimization || stylesOptimization.minify)) {
extraMinimizers.push(new TransferSizePlugin());
}

const externals: Configuration['externals'] = [...externalDependencies];
if (isPlatformServer && !bundleDependencies) {
externals.push(({ context, request }, callback) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @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
*/

import { promisify } from 'util';
import { Compiler } from 'webpack';
import { brotliCompress } from 'zlib';

const brotliCompressAsync = promisify(brotliCompress);

const PLUGIN_NAME = 'angular-transfer-size-estimator';

export class TransferSizePlugin {
constructor() {}

apply(compiler: Compiler) {
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: PLUGIN_NAME,
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
},
async (compilationAssets) => {
const actions = [];
for (const assetName of Object.keys(compilationAssets)) {
if (!assetName.endsWith('.js') && !assetName.endsWith('.css')) {
continue;
}

const scriptAsset = compilation.getAsset(assetName);
if (!scriptAsset || scriptAsset.source.size() <= 0) {
continue;
}

actions.push(
brotliCompressAsync(scriptAsset.source.source())
.then((result) => {
compilation.updateAsset(assetName, (s) => s, {
estimatedTransferSize: result.length,
});
})
.catch((error) => {
compilation.warnings.push(
new compilation.compiler.webpack.WebpackError(
`Unable to calculate estimated transfer size for '${assetName}'. Reason: ${error.message}`,
),
);
}),
);
}

await Promise.all(actions);
},
);
});
}
}
105 changes: 83 additions & 22 deletions packages/angular_devkit/build_angular/src/webpack/utils/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,28 @@ export function formatSize(size: number): string {
return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
}

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

export function generateBundleStats(info: {
size?: number;
rawSize?: number;
estimatedTransferSize?: number;
files?: string[];
names?: string[];
initial?: boolean;
rendered?: boolean;
}): BundleStats {
const size = typeof info.size === 'number' ? info.size : '-';
const rawSize = typeof info.rawSize === 'number' ? info.rawSize : '-';
const estimatedTransferSize =
typeof info.estimatedTransferSize === 'number' ? info.estimatedTransferSize : '-';
const files =
info.files
?.filter((f) => !f.endsWith('.map'))
Expand All @@ -54,14 +62,15 @@ export function generateBundleStats(info: {

return {
initial,
stats: [files, names, size],
stats: [files, names, rawSize, estimatedTransferSize],
};
}

function generateBuildStatsTable(
data: BundleStats[],
colors: boolean,
showTotalSize: boolean,
showEstimatedTransferSize: boolean,
): string {
const g = (x: string) => (colors ? ansiColors.greenBright(x) : x);
const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x);
Expand All @@ -71,36 +80,70 @@ function generateBuildStatsTable(
const changedEntryChunksStats: BundleStatsData[] = [];
const changedLazyChunksStats: BundleStatsData[] = [];

let initialTotalSize = 0;
let initialTotalRawSize = 0;
let initialTotalEstimatedTransferSize;

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

const data: BundleStatsData = [
g(files),
names,
c(typeof size === 'number' ? formatSize(size) : size),
];
const [files, names, rawSize, estimatedTransferSize] = stats;

let data: BundleStatsData;

if (showEstimatedTransferSize) {
data = [
g(files),
names,
c(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), ''];
}

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

const bundleInfo: (string | number)[][] = [];
const baseTitles = ['Names', 'Raw Size'];
const tableAlign: ('l' | 'r')[] = ['l', 'l', 'r'];

if (showEstimatedTransferSize) {
baseTitles.push('Estimated Transfer Size');
tableAlign.push('r');
}

// Entry chunks
if (changedEntryChunksStats.length) {
bundleInfo.push(['Initial Chunk Files', 'Names', 'Size'].map(bold), ...changedEntryChunksStats);
bundleInfo.push(['Initial Chunk Files', ...baseTitles].map(bold), ...changedEntryChunksStats);

if (showTotalSize) {
bundleInfo.push([]);
bundleInfo.push([' ', 'Initial Total', formatSize(initialTotalSize)].map(bold));

const totalSizeElements = [' ', 'Initial Total', formatSize(initialTotalRawSize)];
if (showEstimatedTransferSize) {
totalSizeElements.push(
typeof initialTotalEstimatedTransferSize === 'number'
? formatSize(initialTotalEstimatedTransferSize)
: '-',
);
}
bundleInfo.push(totalSizeElements.map(bold));
}
}

Expand All @@ -111,13 +154,13 @@ function generateBuildStatsTable(

// Lazy chunks
if (changedLazyChunksStats.length) {
bundleInfo.push(['Lazy Chunk Files', 'Names', 'Size'].map(bold), ...changedLazyChunksStats);
bundleInfo.push(['Lazy Chunk Files', ...baseTitles].map(bold), ...changedLazyChunksStats);
}

return textTable(bundleInfo, {
hsep: dim(' | '),
stringLength: (s) => removeColor(s).length,
align: ['l', 'l', 'r'],
align: tableAlign,
});
}

Expand Down Expand Up @@ -148,6 +191,7 @@ function statsToString(

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

Expand All @@ -159,10 +203,26 @@ function statsToString(
}

const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name));
const summedSize = assets
?.filter((asset) => !asset.name.endsWith('.map'))
.reduce((total, asset) => total + asset.size, 0);
changedChunksStats.push(generateBundleStats({ ...chunk, size: summedSize }));
let rawSize = 0;
let estimatedTransferSize;
if (assets) {
for (const asset of assets) {
if (asset.name.endsWith('.map')) {
continue;
}

rawSize += asset.size;

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;

Expand All @@ -186,6 +246,7 @@ function statsToString(
changedChunksStats,
colors,
unchangedChunkNumber === 0,
hasEstimatedTransferSizes,
);

// In some cases we do things outside of webpack context
Expand Down
13 changes: 12 additions & 1 deletion tests/legacy-cli/e2e/tests/basic/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import { ng } from '../../utils/process';

export default async function () {
// Development build
await ng('build', '--configuration=development');
const { stdout: stdoutDev } = await ng('build', '--configuration=development');
await expectFileToMatch('dist/test-project/index.html', 'main.js');
if (stdoutDev.includes('Estimated Transfer Size')) {
throw new Error(
`Expected stdout not to contain 'Estimated Transfer Size' but it did.\n${stdoutDev}`,
);
}

// Named Development build
await ng('build', 'test-project', '--configuration=development');
Expand All @@ -19,6 +24,12 @@ export default async function () {
throw new Error(`Expected stdout to contain 'Initial Total' but it did not.\n${stdout}`);
}

if (!stdout.includes('Estimated Transfer Size')) {
throw new Error(
`Expected stdout to contain 'Estimated Transfer Size' but it did not.\n${stdout}`,
);
}

const logs: string[] = [
'Browser application bundle generation complete',
'Copying assets complete',
Expand Down

0 comments on commit bc85637

Please sign in to comment.