Skip to content

Commit 2d141fe

Browse files
clydinangular-robot[bot]
authored andcommitted
feat(@angular-devkit/build-angular): show estimated transfer size with esbuild builder
When using the esbuild-based browser application builder, the console build stats output will now show the estimated transfer size of JavaScript and CSS files when optimizations are enabled. This provides similar behavior to the default Webpack-based builder.
1 parent c2bc6ff commit 2d141fe

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import type { BuildOptions, Metafile, OutputFile } from 'esbuild';
1111
import { constants as fsConstants } from 'node:fs';
1212
import fs from 'node:fs/promises';
1313
import path from 'node:path';
14+
import { promisify } from 'node:util';
15+
import { brotliCompress } from 'node:zlib';
1416
import { copyAssets } from '../../utils/copy-assets';
1517
import { assertIsError } from '../../utils/error';
1618
import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets';
@@ -33,6 +35,8 @@ import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
3335
import { shutdownSassWorkerPool } from './stylesheets/sass-plugin';
3436
import type { ChangedFiles } from './watcher';
3537

38+
const compressAsync = promisify(brotliCompress);
39+
3640
interface RebuildState {
3741
rebuildContexts: BundlerContext[];
3842
codeBundleCache?: SourceFileCache;
@@ -259,7 +263,12 @@ async function execute(
259263
}
260264
}
261265

262-
logBuildStats(context, metafile, initialFiles);
266+
// Calculate estimated transfer size if scripts are optimized
267+
let estimatedTransferSizes;
268+
if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
269+
estimatedTransferSizes = await calculateEstimatedTransferSizes(executionResult.outputFiles);
270+
}
271+
logBuildStats(context, metafile, initialFiles, estimatedTransferSizes);
263272

264273
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
265274
context.logger.info(`Application bundle generation complete. [${buildTime.toFixed(3)} seconds]`);
@@ -700,7 +709,12 @@ export async function* buildEsbuildBrowserInternal(
700709

701710
export default createBuilder(buildEsbuildBrowser);
702711

703-
function logBuildStats(context: BuilderContext, metafile: Metafile, initialFiles: FileInfo[]) {
712+
function logBuildStats(
713+
context: BuilderContext,
714+
metafile: Metafile,
715+
initialFiles: FileInfo[],
716+
estimatedTransferSizes?: Map<string, number>,
717+
) {
704718
const initial = new Map(initialFiles.map((info) => [info.file, info.name]));
705719
const stats: BundleStats[] = [];
706720
for (const [file, output] of Object.entries(metafile.outputs)) {
@@ -716,11 +730,45 @@ function logBuildStats(context: BuilderContext, metafile: Metafile, initialFiles
716730

717731
stats.push({
718732
initial: initial.has(file),
719-
stats: [file, initial.get(file) ?? '-', output.bytes, ''],
733+
stats: [
734+
file,
735+
initial.get(file) ?? '-',
736+
output.bytes,
737+
estimatedTransferSizes?.get(file) ?? '-',
738+
],
720739
});
721740
}
722741

723-
const tableText = generateBuildStatsTable(stats, true, true, false, undefined);
742+
const tableText = generateBuildStatsTable(stats, true, true, !!estimatedTransferSizes, undefined);
724743

725744
context.logger.info('\n' + tableText + '\n');
726745
}
746+
747+
async function calculateEstimatedTransferSizes(outputFiles: OutputFile[]) {
748+
const sizes = new Map<string, number>();
749+
750+
const pendingCompression = [];
751+
for (const outputFile of outputFiles) {
752+
// Only calculate JavaScript and CSS files
753+
if (!outputFile.path.endsWith('.js') && !outputFile.path.endsWith('.css')) {
754+
continue;
755+
}
756+
757+
// Skip compressing small files which may end being larger once compressed and will most likely not be
758+
// compressed in actual transit.
759+
if (outputFile.contents.byteLength < 1024) {
760+
sizes.set(outputFile.path, outputFile.contents.byteLength);
761+
continue;
762+
}
763+
764+
pendingCompression.push(
765+
compressAsync(outputFile.contents).then((result) =>
766+
sizes.set(outputFile.path, result.byteLength),
767+
),
768+
);
769+
}
770+
771+
await Promise.all(pendingCompression);
772+
773+
return sizes;
774+
}

tests/legacy-cli/e2e/tests/basic/build.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@ import { ng } from '../../utils/process';
44

55
export default async function () {
66
// Development build
7-
const { stdout } = await ng('build', '--configuration=development');
7+
const { stdout: stdout1 } = await ng('build', '--configuration=development');
88
await expectFileToMatch('dist/test-project/index.html', 'main.js');
99

10-
if (stdout.includes('Estimated Transfer Size')) {
10+
if (stdout1.includes('Estimated Transfer Size')) {
1111
throw new Error(
12-
`Expected stdout not to contain 'Estimated Transfer Size' but it did.\n${stdout}`,
12+
`Expected stdout not to contain 'Estimated Transfer Size' but it did.\n${stdout1}`,
1313
);
1414
}
1515

1616
// Production build
17-
await ng('build');
17+
const { stdout: stdout2 } = await ng('build');
1818
if (getGlobalVariable('argv')['esbuild']) {
1919
// esbuild uses an 8 character hash
2020
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{8}\.js/);
2121
} else {
2222
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{16}\.js/);
2323
}
24+
25+
if (!stdout2.includes('Estimated Transfer Size')) {
26+
throw new Error(
27+
`Expected stdout to contain 'Estimated Transfer Size' but it did not.\n${stdout2}`,
28+
);
29+
}
2430
}

tests/legacy-cli/e2e/tests/build/progress-and-stats.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import { getGlobalVariable } from '../../utils/env';
22
import { ng } from '../../utils/process';
33

44
export default async function () {
5-
if (getGlobalVariable('argv')['esbuild']) {
6-
// EXPERIMENTAL_ESBUILD: esbuild does not yet output build stats
7-
return;
8-
}
9-
105
const { stderr: stderrProgress, stdout } = await ng('build', '--progress');
116
if (!stdout.includes('Initial Total')) {
127
throw new Error(`Expected stdout to contain 'Initial Total' but it did not.\n${stdout}`);
@@ -18,11 +13,16 @@ export default async function () {
1813
);
1914
}
2015

21-
const logs: string[] = [
22-
'Browser application bundle generation complete',
23-
'Copying assets complete',
24-
'Index html generation complete',
25-
];
16+
let logs;
17+
if (getGlobalVariable('argv')['esbuild']) {
18+
logs = ['Application bundle generation complete.'];
19+
} else {
20+
logs = [
21+
'Browser application bundle generation complete',
22+
'Copying assets complete',
23+
'Index html generation complete',
24+
];
25+
}
2626

2727
for (const log of logs) {
2828
if (!stderrProgress.includes(log)) {

0 commit comments

Comments
 (0)