Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): show estimated transfer size wit…
Browse files Browse the repository at this point in the history
…h 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.
  • Loading branch information
clydin authored and angular-robot[bot] committed May 16, 2023
1 parent c2bc6ff commit 2d141fe
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type { BuildOptions, Metafile, OutputFile } from 'esbuild';
import { constants as fsConstants } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import { promisify } from 'node:util';
import { brotliCompress } from 'node:zlib';
import { copyAssets } from '../../utils/copy-assets';
import { assertIsError } from '../../utils/error';
import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets';
Expand All @@ -33,6 +35,8 @@ import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
import { shutdownSassWorkerPool } from './stylesheets/sass-plugin';
import type { ChangedFiles } from './watcher';

const compressAsync = promisify(brotliCompress);

interface RebuildState {
rebuildContexts: BundlerContext[];
codeBundleCache?: SourceFileCache;
Expand Down Expand Up @@ -259,7 +263,12 @@ async function execute(
}
}

logBuildStats(context, metafile, initialFiles);
// Calculate estimated transfer size if scripts are optimized
let estimatedTransferSizes;
if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
estimatedTransferSizes = await calculateEstimatedTransferSizes(executionResult.outputFiles);
}
logBuildStats(context, metafile, initialFiles, estimatedTransferSizes);

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

export default createBuilder(buildEsbuildBrowser);

function logBuildStats(context: BuilderContext, metafile: Metafile, initialFiles: FileInfo[]) {
function logBuildStats(
context: BuilderContext,
metafile: Metafile,
initialFiles: FileInfo[],
estimatedTransferSizes?: Map<string, number>,
) {
const initial = new Map(initialFiles.map((info) => [info.file, info.name]));
const stats: BundleStats[] = [];
for (const [file, output] of Object.entries(metafile.outputs)) {
Expand All @@ -716,11 +730,45 @@ function logBuildStats(context: BuilderContext, metafile: Metafile, initialFiles

stats.push({
initial: initial.has(file),
stats: [file, initial.get(file) ?? '-', output.bytes, ''],
stats: [
file,
initial.get(file) ?? '-',
output.bytes,
estimatedTransferSizes?.get(file) ?? '-',
],
});
}

const tableText = generateBuildStatsTable(stats, true, true, false, undefined);
const tableText = generateBuildStatsTable(stats, true, true, !!estimatedTransferSizes, undefined);

context.logger.info('\n' + tableText + '\n');
}

async function calculateEstimatedTransferSizes(outputFiles: OutputFile[]) {
const sizes = new Map<string, number>();

const pendingCompression = [];
for (const outputFile of outputFiles) {
// Only calculate JavaScript and CSS files
if (!outputFile.path.endsWith('.js') && !outputFile.path.endsWith('.css')) {
continue;
}

// Skip compressing small files which may end being larger once compressed and will most likely not be
// compressed in actual transit.
if (outputFile.contents.byteLength < 1024) {
sizes.set(outputFile.path, outputFile.contents.byteLength);
continue;
}

pendingCompression.push(
compressAsync(outputFile.contents).then((result) =>
sizes.set(outputFile.path, result.byteLength),
),
);
}

await Promise.all(pendingCompression);

return sizes;
}
14 changes: 10 additions & 4 deletions tests/legacy-cli/e2e/tests/basic/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import { ng } from '../../utils/process';

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

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

// Production build
await ng('build');
const { stdout: stdout2 } = await ng('build');
if (getGlobalVariable('argv')['esbuild']) {
// esbuild uses an 8 character hash
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{8}\.js/);
} else {
await expectFileToMatch('dist/test-project/index.html', /main\.[a-zA-Z0-9]{16}\.js/);
}

if (!stdout2.includes('Estimated Transfer Size')) {
throw new Error(
`Expected stdout to contain 'Estimated Transfer Size' but it did not.\n${stdout2}`,
);
}
}
20 changes: 10 additions & 10 deletions tests/legacy-cli/e2e/tests/build/progress-and-stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { getGlobalVariable } from '../../utils/env';
import { ng } from '../../utils/process';

export default async function () {
if (getGlobalVariable('argv')['esbuild']) {
// EXPERIMENTAL_ESBUILD: esbuild does not yet output build stats
return;
}

const { stderr: stderrProgress, stdout } = await ng('build', '--progress');
if (!stdout.includes('Initial Total')) {
throw new Error(`Expected stdout to contain 'Initial Total' but it did not.\n${stdout}`);
Expand All @@ -18,11 +13,16 @@ export default async function () {
);
}

const logs: string[] = [
'Browser application bundle generation complete',
'Copying assets complete',
'Index html generation complete',
];
let logs;
if (getGlobalVariable('argv')['esbuild']) {
logs = ['Application bundle generation complete.'];
} else {
logs = [
'Browser application bundle generation complete',
'Copying assets complete',
'Index html generation complete',
];
}

for (const log of logs) {
if (!stderrProgress.includes(log)) {
Expand Down

0 comments on commit 2d141fe

Please sign in to comment.