Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/angular/build/src/builders/application/chunk-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,21 @@ export async function optimizeChunks(
}
original.metafile = newMetafile;

// Update the isolated browser metafile to reflect the optimized output.
// Server outputs are excluded since chunk optimization only affects browser bundles.
const serverOutputPaths = new Set(Object.keys(original.serverMetafile?.outputs ?? {}));
const browserOutputs: Metafile['outputs'] = {};
const browserInputs: Metafile['inputs'] = {};
for (const [path, output] of Object.entries(newMetafile.outputs)) {
if (!serverOutputPaths.has(path)) {
browserOutputs[path] = output;
for (const inputPath of Object.keys(output.inputs)) {
browserInputs[inputPath] = newMetafile.inputs[inputPath];
}
}
}
original.browserMetafile = { inputs: browserInputs, outputs: browserOutputs };

// Remove used chunks and associated sourcemaps from the original result
original.outputFiles = original.outputFiles.filter(
(file) =>
Expand Down
55 changes: 51 additions & 4 deletions packages/angular/build/src/builders/application/execute-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
*/

import { BuilderContext } from '@angular-devkit/architect';
import type { Metafile } from 'esbuild';
import { createAngularCompilation } from '../../tools/angular/compilation';
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
import { generateBudgetStats } from '../../tools/esbuild/budget-stats';
import {
BuildOutputFileType,
BundleContextResult,
BundlerContext,
InitialFileRecord,
} from '../../tools/esbuild/bundler-context';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker';
Expand All @@ -37,6 +39,32 @@ import { inlineI18n, loadActiveTranslations } from './i18n';
import { NormalizedApplicationBuildOptions } from './options';
import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling';

function filterMetafileByInitialFiles(
metafile: Metafile,
initialFiles: Map<string, InitialFileRecord>,
): Metafile {
const filteredOutputs: Metafile['outputs'] = {};
const referencedInputs = new Set<string>();

for (const [path, output] of Object.entries(metafile.outputs)) {
if (initialFiles.has(path)) {
filteredOutputs[path] = output;
for (const inputPath of Object.keys(output.inputs)) {
referencedInputs.add(inputPath);
}
}
}

const filteredInputs: Metafile['inputs'] = {};
for (const [inputPath, input] of Object.entries(metafile.inputs)) {
if (referencedInputs.has(inputPath)) {
filteredInputs[inputPath] = input;
}
}

return { inputs: filteredInputs, outputs: filteredOutputs };
}

// eslint-disable-next-line max-lines-per-function
export async function executeBuild(
options: NormalizedApplicationBuildOptions,
Expand Down Expand Up @@ -209,7 +237,7 @@ export async function executeBuild(
executionResult.setExternalMetadata(implicitBrowser, implicitServer, [...explicitExternal]);
}

const { metafile, initialFiles, outputFiles } = bundlingResult;
const { metafile, browserMetafile, serverMetafile, initialFiles, outputFiles } = bundlingResult;

executionResult.outputFiles.push(...outputFiles);

Expand Down Expand Up @@ -301,13 +329,32 @@ export async function executeBuild(
BuildOutputFileType.Root,
);

const ssrOutputEnabled: boolean = !!ssrOptions;

// Write metafile if stats option is enabled
if (options.stats) {
executionResult.addOutputFile(
'stats.json',
JSON.stringify(metafile, null, 2),
'browser-stats.json',
JSON.stringify(browserMetafile, null, 2),
BuildOutputFileType.Root,
);
executionResult.addOutputFile(
'browser-initial-stats.json',
JSON.stringify(filterMetafileByInitialFiles(browserMetafile, initialFiles), null, 2),
BuildOutputFileType.Root,
);
if (ssrOutputEnabled && serverMetafile) {
executionResult.addOutputFile(
'server-stats.json',
JSON.stringify(serverMetafile, null, 2),
BuildOutputFileType.Root,
);
executionResult.addOutputFile(
'server-initial-stats.json',
JSON.stringify(filterMetafileByInitialFiles(serverMetafile, initialFiles), null, 2),
BuildOutputFileType.Root,
);
}
}

if (!jsonLogs) {
Expand All @@ -322,7 +369,7 @@ export async function executeBuild(
colors,
changedFiles,
estimatedTransferSizes,
!!ssrOptions,
ssrOutputEnabled,
verbose,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export class ComponentStylesheetBundler {
contents,
outputFiles,
metafile,
browserMetafile: metafile,
referencedFiles,
externalImports: result.externalImports,
initialFiles: new Map(),
Expand Down
19 changes: 19 additions & 0 deletions packages/angular/build/src/tools/esbuild/bundler-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export type BundleContextResult =
errors: undefined;
warnings: Message[];
metafile: Metafile;
browserMetafile: Metafile;
serverMetafile?: Metafile;
outputFiles: BuildOutputFile[];
initialFiles: Map<string, InitialFileRecord>;
externalImports: {
Expand Down Expand Up @@ -128,6 +130,8 @@ export class BundlerContext {
let errors: Message[] | undefined;
const warnings: Message[] = [];
const metafile: Metafile = { inputs: {}, outputs: {} };
const browserMetafile: Metafile = { inputs: {}, outputs: {} };
let serverMetafile: Metafile | undefined;
const initialFiles = new Map<string, InitialFileRecord>();
const externalImportsBrowser = new Set<string>();
const externalImportsServer = new Set<string>();
Expand All @@ -148,6 +152,17 @@ export class BundlerContext {
Object.assign(metafile.outputs, result.metafile.outputs);
}

// Keep browser and server metafiles isolated for separate stats output
if (result.browserMetafile) {
Object.assign(browserMetafile.inputs, result.browserMetafile.inputs);
Object.assign(browserMetafile.outputs, result.browserMetafile.outputs);
}
if (result.serverMetafile) {
serverMetafile ??= { inputs: {}, outputs: {} };
Object.assign(serverMetafile.inputs, result.serverMetafile.inputs);
Object.assign(serverMetafile.outputs, result.serverMetafile.outputs);
}

result.initialFiles.forEach((value, key) => initialFiles.set(key, value));

outputFiles.push(...result.outputFiles);
Expand All @@ -170,6 +185,8 @@ export class BundlerContext {
errors,
warnings,
metafile,
browserMetafile,
serverMetafile,
initialFiles,
outputFiles,
externalImports: {
Expand Down Expand Up @@ -415,6 +432,8 @@ export class BundlerContext {
},
externalConfiguration,
errors: undefined,
browserMetafile: isPlatformServer ? { inputs: {}, outputs: {} } : result.metafile,
serverMetafile: isPlatformServer ? result.metafile : undefined,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ describe('Browser Builder stats json', () => {

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

it('works with profile flag', async () => {
const { files } = await browserBuild(architect, host, target, { statsJson: true });
expect('stats.json' in files).toBe(true);
const stats = JSON.parse(await files['stats.json']);
expect('browser-stats.json' in files).toBe(true);
const stats = JSON.parse(await files['browser-stats.json']);
expect(stats.chunks[0].modules[0].profile.building).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {

expect(result?.success).toBe(true);

if (harness.expectFile('dist/stats.json').toExist()) {
const content = harness.readFile('dist/stats.json');
if (harness.expectFile('dist/browser-stats.json').toExist()) {
const content = harness.readFile('dist/browser-stats.json');
expect(() => JSON.parse(content))
.withContext('Expected Webpack Stats file to be valid JSON.')
.not.toThrow();
}
if (harness.expectFile('dist/browser-initial-stats.json').toExist()) {
const initialContent = harness.readFile('dist/browser-initial-stats.json');
expect(() => JSON.parse(initialContent))
.withContext('Expected Webpack Stats file to be valid JSON.')
.not.toThrow();
}
});

// TODO: Investigate why this profiling object is no longer present in Webpack 5.90.3+ and if this should even be tested
Expand All @@ -45,8 +51,8 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {

expect(result?.success).toBe(true);

if (harness.expectFile('dist/stats.json').toExist()) {
const stats = JSON.parse(harness.readFile('dist/stats.json'));
if (harness.expectFile('dist/browser-stats.json').toExist()) {
const stats = JSON.parse(harness.readFile('dist/browser-stats.json'));
expect(stats?.chunks?.[0]?.modules?.[0]?.profile?.building).toBeDefined();
}
});
Expand All @@ -61,7 +67,8 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {

expect(result?.success).toBe(true);

harness.expectFile('dist/stats.json').toNotExist();
harness.expectFile('dist/browser-stats.json').toNotExist();
harness.expectFile('dist/browser-initial-stats.json').toNotExist();
});

it('does not generate a Webpack Stats file in output when not present', async () => {
Expand All @@ -73,7 +80,8 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {

expect(result?.success).toBe(true);

harness.expectFile('dist/stats.json').toNotExist();
harness.expectFile('dist/browser-stats.json').toNotExist();
harness.expectFile('dist/browser-initial-stats.json').toNotExist();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
// Once TypeScript provides support for keeping the dynamic import this workaround can be
// changed to a direct dynamic import.
const { VERSION: NG_VERSION } = await import('@angular/compiler-cli');
const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await import(
'@angular/compiler-cli/private/tooling'
);
const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } =
await import('@angular/compiler-cli/private/tooling');

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

if (buildOptions.statsJson) {
extraPlugins.push(
new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'stats.json')),
new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'browser-stats.json')),
new JsonStatsPlugin(
path.resolve(root, buildOptions.outputPath, 'browser-initial-stats.json'),
),
);
}

Expand Down
Loading