Skip to content

Commit 9545676

Browse files
feat(@angular/build): Support splitting browser and server stats json files for easier consumption
This feature supports splitting out the browser and server stats json files so it's easier to inspect the bundle in various analyzers. Today, everything gets dumped into a single file and it's nearly impossible to use without hours of fix -> remove unused browser chunks -> analyze and starting the loop all over again. This feature implements the feature request I made in #28185, along with another developers request to see a stats json file for just the initial page bundle. I've tested this out in my own repository and it's already helped an incredible amount. Fixes #28185 #28671
1 parent b697145 commit 9545676

File tree

8 files changed

+67
-14
lines changed

8 files changed

+67
-14
lines changed

packages/angular/build/src/builders/application/execute-build.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
2020
import { extractLicenses } from '../../tools/esbuild/license-extractor';
2121
import { profileAsync } from '../../tools/esbuild/profiling';
2222
import {
23+
buildMetafileForType,
2324
calculateEstimatedTransferSizes,
2425
logBuildStats,
2526
transformSupportedBrowsersToTargets,
@@ -301,13 +302,22 @@ export async function executeBuild(
301302
BuildOutputFileType.Root,
302303
);
303304

305+
const ssrOutputEnabled: boolean = !!ssrOptions;
306+
304307
// Write metafile if stats option is enabled
305308
if (options.stats) {
306309
executionResult.addOutputFile(
307-
'stats.json',
308-
JSON.stringify(metafile, null, 2),
310+
'browser-stats.json',
311+
JSON.stringify(buildMetafileForType(metafile, 'browser', outputFiles), null, 2),
309312
BuildOutputFileType.Root,
310313
);
314+
if (ssrOutputEnabled) {
315+
executionResult.addOutputFile(
316+
'server-stats.json',
317+
JSON.stringify(buildMetafileForType(metafile, 'server', outputFiles), null, 2),
318+
BuildOutputFileType.Root,
319+
);
320+
}
311321
}
312322

313323
if (!jsonLogs) {
@@ -322,7 +332,7 @@ export async function executeBuild(
322332
colors,
323333
changedFiles,
324334
estimatedTransferSizes,
325-
!!ssrOptions,
335+
ssrOutputEnabled,
326336
verbose,
327337
),
328338
);

packages/angular/build/src/builders/application/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@
535535
},
536536
"statsJson": {
537537
"type": "boolean",
538-
"description": "Generates a 'stats.json' file which can be analyzed with https://esbuild.github.io/analyze/.",
538+
"description": "Generates a 'browser-stats.json' (and 'server-stats.json' when SSR is enabled) file which can be analyzed with https://esbuild.github.io/analyze/.",
539539
"default": false
540540
},
541541
"budgets": {

packages/angular/build/src/tools/esbuild/utils.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,50 @@ import {
2929
PrerenderedRoutesRecord,
3030
} from './bundler-execution-result';
3131

32+
export function buildMetafileForType(
33+
metafile: Metafile,
34+
type: 'browser' | 'server',
35+
outputFiles: BuildOutputFile[],
36+
): Metafile {
37+
const outputPathsForType = new Set(
38+
outputFiles
39+
.filter(({ type: fileType }) => {
40+
const isServerFile =
41+
fileType === BuildOutputFileType.ServerApplication ||
42+
fileType === BuildOutputFileType.ServerRoot;
43+
44+
return type === 'server' ? isServerFile : !isServerFile;
45+
})
46+
.map(({ path }) => path),
47+
);
48+
49+
const filteredOutputs: Metafile['outputs'] = {};
50+
for (const [outputPath, output] of Object.entries(metafile.outputs)) {
51+
if (outputPathsForType.has(outputPath)) {
52+
filteredOutputs[outputPath] = output;
53+
}
54+
}
55+
56+
const referencedInputs = new Set<string>();
57+
for (const output of Object.values(filteredOutputs)) {
58+
for (const inputPath of Object.keys(output.inputs)) {
59+
referencedInputs.add(inputPath);
60+
}
61+
}
62+
63+
const filteredInputs: Metafile['inputs'] = {};
64+
for (const [inputPath, input] of Object.entries(metafile.inputs)) {
65+
if (referencedInputs.has(inputPath)) {
66+
filteredInputs[inputPath] = input;
67+
}
68+
}
69+
70+
return {
71+
inputs: filteredInputs,
72+
outputs: filteredOutputs,
73+
};
74+
}
75+
3276
export function logBuildStats(
3377
metafile: Metafile,
3478
outputFiles: BuildOutputFile[],

packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@
406406
},
407407
"statsJson": {
408408
"type": "boolean",
409-
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
409+
"description": "Generates a 'browser-stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
410410
"default": false
411411
},
412412
"budgets": {

packages/angular_devkit/build_angular/src/builders/browser/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
},
395395
"statsJson": {
396396
"type": "boolean",
397-
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
397+
"description": "Generates a 'browser-stats.json' (and 'server-stats.json' when SSR is enabled) file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
398398
"default": false
399399
},
400400
"budgets": {

packages/angular_devkit/build_angular/src/builders/browser/specs/stats-json_spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ describe('Browser Builder stats json', () => {
2121

2222
it('works', async () => {
2323
const { files } = await browserBuild(architect, host, target, { statsJson: true });
24-
expect('stats.json' in files).toBe(true);
24+
expect('browser-stats.json' in files).toBe(true);
2525
});
2626

2727
it('works with profile flag', async () => {
2828
const { files } = await browserBuild(architect, host, target, { statsJson: true });
29-
expect('stats.json' in files).toBe(true);
30-
const stats = JSON.parse(await files['stats.json']);
29+
expect('browser-stats.json' in files).toBe(true);
30+
const stats = JSON.parse(await files['browser-stats.json']);
3131
expect(stats.chunks[0].modules[0].profile.building).toBeDefined();
3232
});
3333
});

packages/angular_devkit/build_angular/src/builders/server/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@
208208
},
209209
"statsJson": {
210210
"type": "boolean",
211-
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
211+
"description": "Generates a 'browser-stats.json' and 'server-stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
212212
"default": false
213213
},
214214
"watch": {

packages/angular_devkit/build_angular/src/tools/webpack/configs/common.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
8383
// Once TypeScript provides support for keeping the dynamic import this workaround can be
8484
// changed to a direct dynamic import.
8585
const { VERSION: NG_VERSION } = await import('@angular/compiler-cli');
86-
const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await import(
87-
'@angular/compiler-cli/private/tooling'
88-
);
86+
const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } =
87+
await import('@angular/compiler-cli/private/tooling');
8988

9089
// determine hashing format
9190
const hashFormat = getOutputHashFormat(buildOptions.outputHashing);
@@ -245,7 +244,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
245244

246245
if (buildOptions.statsJson) {
247246
extraPlugins.push(
248-
new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'stats.json')),
247+
new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'browser-stats.json')),
249248
);
250249
}
251250

0 commit comments

Comments
 (0)