Skip to content

Commit 41ea985

Browse files
committed
fix(@angular-devkit/build-angular): display server bundles in build stats
This commit adds server bundle information to the build logs Closes #25855
1 parent 45820d2 commit 41ea985

File tree

10 files changed

+125
-66
lines changed

10 files changed

+125
-66
lines changed

packages/angular_devkit/build_angular/src/builders/application/execute-build.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export async function executeBuild(
3636
assets,
3737
cacheOptions,
3838
prerenderOptions,
39+
ssrOptions,
3940
} = options;
4041

4142
// TODO: Consider integrating into watch mode. Would require full rebuild on target changes.
@@ -188,6 +189,7 @@ export async function executeBuild(
188189
budgetFailures,
189190
changedFiles,
190191
estimatedTransferSizes,
192+
!!ssrOptions,
191193
);
192194

193195
// Write metafile if stats option is enabled

packages/angular_devkit/build_angular/src/builders/application/setup-bundling.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ export function setupBundlerContexts(
103103
nodeTargets,
104104
codeBundleCache,
105105
),
106-
() => false,
107106
),
108107
);
109108

@@ -116,12 +115,7 @@ export function setupBundlerContexts(
116115

117116
if (serverPolyfillBundleOptions) {
118117
bundlerContexts.push(
119-
new BundlerContext(
120-
workspaceRoot,
121-
!!options.watch,
122-
serverPolyfillBundleOptions,
123-
() => false,
124-
),
118+
new BundlerContext(workspaceRoot, !!options.watch, serverPolyfillBundleOptions),
125119
);
126120
}
127121
}

packages/angular_devkit/build_angular/src/builders/browser/specs/scripts-array_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,6 @@ describe('Browser Builder scripts array', () => {
143143
expect(joinedLogs).toMatch(/lazy-script.+\d+ bytes/);
144144
expect(joinedLogs).toMatch(/renamed-script.+\d+ bytes/);
145145
expect(joinedLogs).toMatch(/renamed-lazy-script.+\d+ bytes/);
146-
expect(joinedLogs).not.toContain('Lazy Chunks');
146+
expect(joinedLogs).not.toContain('Lazy chunks');
147147
});
148148
});

packages/angular_devkit/build_angular/src/tools/esbuild/budget-stats.ts

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export function generateBudgetStats(
3333
continue;
3434
}
3535

36+
// Exclude server bundles
37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
if ((entry as any)['ng-platform-server']) {
39+
continue;
40+
}
41+
3642
const initialRecord = initialFiles.get(file);
3743
let name = initialRecord?.name;
3844
if (name === undefined && entry.entryPoint) {

packages/angular_devkit/build_angular/src/tools/esbuild/bundler-context.ts

+7
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ export class BundlerContext {
221221
// For non-incremental builds, perform a single build
222222
result = await build(this.#esbuildOptions);
223223
}
224+
225+
if (this.#esbuildOptions?.platform === 'node') {
226+
for (const entry of Object.values(result.metafile.outputs)) {
227+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
228+
(entry as any)['ng-platform-server'] = true;
229+
}
230+
}
224231
} catch (failure) {
225232
// Build failures will throw an exception which contains errors/warnings
226233
if (isEsBuildFailure(failure)) {

packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { coerce } from 'semver';
1717
import { NormalizedOutputOptions } from '../../builders/application/options';
1818
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
1919
import { Spinner } from '../../utils/spinner';
20-
import { BundleStats, generateBuildStatsTable } from '../webpack/utils/stats';
20+
import { BundleStats, generateEsbuildBuildStatsTable } from '../webpack/utils/stats';
2121
import { BuildOutputFile, BuildOutputFileType, InitialFileRecord } from './bundler-context';
2222
import { BuildOutputAsset } from './bundler-execution-result';
2323

@@ -28,14 +28,18 @@ export function logBuildStats(
2828
budgetFailures: BudgetCalculatorResult[] | undefined,
2929
changedFiles?: Set<string>,
3030
estimatedTransferSizes?: Map<string, number>,
31+
ssrOutputEnabled?: boolean,
3132
): void {
32-
const stats: BundleStats[] = [];
33+
const browserStats: BundleStats[] = [];
34+
const serverStats: BundleStats[] = [];
3335
let unchangedCount = 0;
36+
3437
for (const [file, output] of Object.entries(metafile.outputs)) {
3538
// Only display JavaScript and CSS files
36-
if (!file.endsWith('.js') && !file.endsWith('.css')) {
39+
if (!/\.(?:css|m?js)$/.test(file)) {
3740
continue;
3841
}
42+
3943
// Skip internal component resources
4044
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4145
if ((output as any)['ng-component']) {
@@ -48,6 +52,13 @@ export function logBuildStats(
4852
continue;
4953
}
5054

55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56+
const isPlatformServer = (output as any)['ng-platform-server'];
57+
if (isPlatformServer && !ssrOutputEnabled) {
58+
// Only log server build stats when SSR is enabled.
59+
continue;
60+
}
61+
5162
let name = initial.get(file)?.name;
5263
if (name === undefined && output.entryPoint) {
5364
name = path
@@ -56,22 +67,28 @@ export function logBuildStats(
5667
.replace(/[\\/.]/g, '-');
5768
}
5869

59-
stats.push({
70+
const stat: BundleStats = {
6071
initial: initial.has(file),
6172
stats: [file, name ?? '-', output.bytes, estimatedTransferSizes?.get(file) ?? '-'],
62-
});
73+
};
74+
75+
if (isPlatformServer) {
76+
serverStats.push(stat);
77+
} else {
78+
browserStats.push(stat);
79+
}
6380
}
6481

65-
if (stats.length > 0) {
66-
const tableText = generateBuildStatsTable(
67-
stats,
82+
if (browserStats.length > 0 || serverStats.length > 0) {
83+
const tableText = generateEsbuildBuildStatsTable(
84+
[browserStats, serverStats],
6885
true,
6986
unchangedCount === 0,
7087
!!estimatedTransferSizes,
7188
budgetFailures,
7289
);
7390

74-
logger.info('\n' + tableText + '\n');
91+
logger.info(tableText + '\n');
7592
} else if (changedFiles !== undefined) {
7693
logger.info('\nNo output file changes.\n');
7794
}

packages/angular_devkit/build_angular/src/tools/webpack/utils/stats.ts

+72-39
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
10-
import { logging, tags } from '@angular-devkit/core';
10+
import { logging } from '@angular-devkit/core';
1111
import assert from 'node:assert';
1212
import * as path from 'node:path';
1313
import { Configuration, StatsCompilation } from 'webpack';
@@ -75,18 +75,73 @@ function generateBundleStats(info: {
7575
};
7676
}
7777

78+
export function generateEsbuildBuildStatsTable(
79+
[browserStats, serverStats]: [browserStats: BundleStats[], serverStats: BundleStats[]],
80+
colors: boolean,
81+
showTotalSize: boolean,
82+
showEstimatedTransferSize: boolean,
83+
budgetFailures?: BudgetCalculatorResult[],
84+
): string {
85+
const bundleInfo = generateBuildStatsData(
86+
browserStats,
87+
colors,
88+
showTotalSize,
89+
showEstimatedTransferSize,
90+
budgetFailures,
91+
);
92+
93+
if (serverStats.length) {
94+
const m = (x: string) => (colors ? ansiColors.magenta(x) : x);
95+
if (browserStats.length) {
96+
bundleInfo.unshift([m('Browser bundles')]);
97+
// Add seperators between browser and server logs
98+
bundleInfo.push([], []);
99+
}
100+
101+
bundleInfo.push(
102+
[m('Server bundles')],
103+
...generateBuildStatsData(serverStats, colors, false, false, undefined),
104+
);
105+
}
106+
107+
return generateTableText(bundleInfo, colors);
108+
}
109+
78110
export function generateBuildStatsTable(
79111
data: BundleStats[],
80112
colors: boolean,
81113
showTotalSize: boolean,
82114
showEstimatedTransferSize: boolean,
83115
budgetFailures?: BudgetCalculatorResult[],
84116
): string {
85-
const g = (x: string) => (colors ? ansiColors.greenBright(x) : x);
86-
const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x);
117+
const bundleInfo = generateBuildStatsData(
118+
data,
119+
colors,
120+
showTotalSize,
121+
showEstimatedTransferSize,
122+
budgetFailures,
123+
);
124+
125+
return generateTableText(bundleInfo, colors);
126+
}
127+
128+
function generateBuildStatsData(
129+
data: BundleStats[],
130+
colors: boolean,
131+
showTotalSize: boolean,
132+
showEstimatedTransferSize: boolean,
133+
budgetFailures?: BudgetCalculatorResult[],
134+
): (string | number)[][] {
135+
if (data.length === 0) {
136+
return [];
137+
}
138+
139+
const g = (x: string) => (colors ? ansiColors.green(x) : x);
140+
const c = (x: string) => (colors ? ansiColors.cyan(x) : x);
87141
const r = (x: string) => (colors ? ansiColors.redBright(x) : x);
88142
const y = (x: string) => (colors ? ansiColors.yellowBright(x) : x);
89143
const bold = (x: string) => (colors ? ansiColors.bold(x) : x);
144+
const dim = (x: string) => (colors ? ansiColors.dim(x) : x);
90145

91146
const getSizeColor = (name: string, file?: string, defaultColor = c) => {
92147
const severity = budgets.get(name) || (file && budgets.get(file));
@@ -138,7 +193,7 @@ export function generateBuildStatsTable(
138193
if (showEstimatedTransferSize) {
139194
data = [
140195
g(files),
141-
names,
196+
dim(names),
142197
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
143198
c(
144199
typeof estimatedTransferSize === 'number'
@@ -149,7 +204,7 @@ export function generateBuildStatsTable(
149204
} else {
150205
data = [
151206
g(files),
152-
names,
207+
dim(names),
153208
getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
154209
'',
155210
];
@@ -172,25 +227,25 @@ export function generateBuildStatsTable(
172227
}
173228

174229
const bundleInfo: (string | number)[][] = [];
175-
const baseTitles = ['Names', 'Raw Size'];
230+
const baseTitles = ['Names', 'Raw size'];
176231
const tableAlign: ('l' | 'r')[] = ['l', 'l', 'r'];
177232

178233
if (showEstimatedTransferSize) {
179-
baseTitles.push('Estimated Transfer Size');
234+
baseTitles.push('Estimated transfer size');
180235
tableAlign.push('r');
181236
}
182237

183238
// Entry chunks
184239
if (changedEntryChunksStats.length) {
185-
bundleInfo.push(['Initial Chunk Files', ...baseTitles].map(bold), ...changedEntryChunksStats);
240+
bundleInfo.push(['Initial chunk files', ...baseTitles].map(bold), ...changedEntryChunksStats);
186241

187242
if (showTotalSize) {
188243
bundleInfo.push([]);
189244

190245
const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x);
191246
const totalSizeElements = [
192247
' ',
193-
'Initial Total',
248+
'Initial total',
194249
initialSizeTotalColor(formatSize(initialTotalRawSize)),
195250
];
196251
if (showEstimatedTransferSize) {
@@ -211,10 +266,10 @@ export function generateBuildStatsTable(
211266

212267
// Lazy chunks
213268
if (changedLazyChunksStats.length) {
214-
bundleInfo.push(['Lazy Chunk Files', ...baseTitles].map(bold), ...changedLazyChunksStats);
269+
bundleInfo.push(['Lazy chunk files', ...baseTitles].map(bold), ...changedLazyChunksStats);
215270
}
216271

217-
return generateTableText(bundleInfo, colors);
272+
return bundleInfo;
218273
}
219274

220275
function generateTableText(bundleInfo: (string | number)[][], colors: boolean): string {
@@ -255,12 +310,6 @@ function generateTableText(bundleInfo: (string | number)[][], colors: boolean):
255310
return outputTable.join('\n');
256311
}
257312

258-
function generateBuildStats(hash: string, time: number, colors: boolean): string {
259-
const w = (x: string) => (colors ? ansiColors.bold.white(x) : x);
260-
261-
return `Build at: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`;
262-
}
263-
264313
// We use this cache because we can have multiple builders running in the same process,
265314
// where each builder has different output path.
266315

@@ -279,6 +328,7 @@ function statsToString(
279328

280329
const colors = statsConfig.colors;
281330
const rs = (x: string) => (colors ? ansiColors.reset(x) : x);
331+
const w = (x: string) => (colors ? ansiColors.bold.white(x) : x);
282332

283333
const changedChunksStats: BundleStats[] = [];
284334
let unchangedChunkNumber = 0;
@@ -330,30 +380,13 @@ function statsToString(
330380
// In some cases we do things outside of webpack context
331381
// Such us index generation, service worker augmentation etc...
332382
// This will correct the time and include these.
333-
334383
const time = getBuildDuration(json);
335384

336-
if (unchangedChunkNumber > 0) {
337-
return (
338-
'\n' +
339-
rs(tags.stripIndents`
340-
${statsTable}
341-
342-
${unchangedChunkNumber} unchanged chunks
343-
344-
${generateBuildStats(json.hash || '', time, colors)}
345-
`)
346-
);
347-
} else {
348-
return (
349-
'\n' +
350-
rs(tags.stripIndents`
351-
${statsTable}
352-
353-
${generateBuildStats(json.hash || '', time, colors)}
354-
`)
355-
);
356-
}
385+
return rs(
386+
`\n${statsTable}\n\n` +
387+
(unchangedChunkNumber > 0 ? `${unchangedChunkNumber} unchanged chunks\n\n` : '') +
388+
`Build at: ${w(new Date().toISOString())} - Hash: ${w(json.hash || '')} - Time: ${w('' + time)}ms`,
389+
);
357390
}
358391

359392
export function statsWarningsToString(

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ export default async function () {
77
const { stdout: stdout1 } = await ng('build', '--configuration=development');
88
await expectFileToMatch('dist/test-project/browser/index.html', 'main.js');
99

10-
if (stdout1.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${stdout1}`,
12+
`Expected stdout not to contain 'Estimated transfer size' but it did.\n${stdout1}`,
1313
);
1414
}
1515

@@ -22,9 +22,9 @@ export default async function () {
2222
await expectFileToMatch('dist/test-project/browser/index.html', /main\.[a-zA-Z0-9]{16}\.js/);
2323
}
2424

25-
if (!stdout2.includes('Estimated Transfer Size')) {
25+
if (!stdout2.includes('Estimated transfer size')) {
2626
throw new Error(
27-
`Expected stdout to contain 'Estimated Transfer Size' but it did not.\n${stdout2}`,
27+
`Expected stdout to contain 'Estimated transfer size' but it did not.\n${stdout2}`,
2828
);
2929
}
3030
}

tests/legacy-cli/e2e/tests/basic/styles-array.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export default async function () {
4242
);
4343

4444
// Non injected styles should be listed under lazy chunk files
45-
if (!/Lazy Chunk Files[\s\S]+renamed-lazy-style\.css/m.test(stdout)) {
45+
if (!/Lazy chunk files[\s\S]+renamed-lazy-style\.css/m.test(stdout)) {
4646
console.log(stdout);
47-
throw new Error(`Expected "renamed-lazy-style.css" to be listed under "Lazy Chunk Files".`);
47+
throw new Error(`Expected "renamed-lazy-style.css" to be listed under "Lazy chunk files".`);
4848
}
4949
}

0 commit comments

Comments
 (0)