Skip to content

Commit 4c288b8

Browse files
alan-agius4dgp1130
authored andcommitted
fix(@angular-devkit/build-angular): lazy modules bundle budgets
Since the introduction of Webpack 5 in version 12 bundle budgets for lazy chunks have been broken due to the removal of the `NamedLazyChunksPlugin`. https://github.com/angular/angular-cli/blob/21a49e6492dda1c4a325c0339518c3c110880d02/packages/angular_devkit/build_angular/src/webpack/plugins/named-chunks-plugin.ts#L8 With this change we re-introduce a similar plugin to allow setting bundle budgets on lazy chunks. This issue has also been reported on Slack by a GDE https://angular-team.slack.com/archives/C08M4JKNH/p1637115196222300 Closes: #11019
1 parent 1abd173 commit 4c288b8

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

packages/angular_devkit/build_angular/src/builders/browser/tests/options/bundle-budgets_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { logging } from '@angular-devkit/core';
10+
import { lazyModuleFiles, lazyModuleFnImport } from '../../../../testing/test-utils';
1011
import { buildWebpackBrowser } from '../../index';
1112
import { Type } from '../../schema';
1213
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
@@ -67,6 +68,26 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
6768
);
6869
});
6970

71+
it(`should warn when lazy bundle is above 'maximumWarning' threshold`, async () => {
72+
harness.useTarget('build', {
73+
...BASE_OPTIONS,
74+
optimization: true,
75+
budgets: [{ type: Type.Bundle, name: 'lazy-lazy-module', maximumWarning: '100b' }],
76+
});
77+
78+
await harness.writeFiles(lazyModuleFiles);
79+
await harness.writeFiles(lazyModuleFnImport);
80+
81+
const { result, logs } = await harness.executeOnce();
82+
expect(result?.success).toBe(true);
83+
expect(logs).toContain(
84+
jasmine.objectContaining<logging.LogEntry>({
85+
level: 'warn',
86+
message: jasmine.stringMatching('lazy-lazy-module exceeded maximum budget'),
87+
}),
88+
);
89+
});
90+
7091
CSS_EXTENSIONS.forEach((ext) => {
7192
it(`shows warnings for large component ${ext} when using 'anyComponentStyle' when AOT`, async () => {
7293
const cssContent = `

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
JsonStatsPlugin,
3131
ScriptsWebpackPlugin,
3232
} from '../plugins';
33+
import { NamedChunksPlugin } from '../plugins/named-chunks-plugin';
3334
import { ProgressPlugin } from '../plugins/progress-plugin';
3435
import { TransferSizePlugin } from '../plugins/transfer-size-plugin';
3536
import { createIvyPlugin } from '../plugins/typescript';
@@ -448,7 +449,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
448449
},
449450
},
450451
},
451-
plugins: [new DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
452+
plugins: [new NamedChunksPlugin(), new DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
452453
node: false,
453454
};
454455
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { AsyncDependenciesBlock, Chunk, Compiler, Template, dependencies } from 'webpack';
10+
11+
// `ImportDependency` is not part of Webpack's depenencies typings.
12+
const ImportDependency: typeof dependencies.ModuleDependency = require('webpack/lib/dependencies/ImportDependency');
13+
14+
const PLUGIN_NAME = 'named-chunks-plugin';
15+
16+
/**
17+
* Webpack will not populate the chunk `name` property unless `webpackChunkName` magic comment is used.
18+
* This however will also effect the filename which is not desired when using `deterministic` chunkIds.
19+
* This plugin will populate the chunk `name` which is mainly used so that users can set bundle budgets on lazy chunks.
20+
*/
21+
export class NamedChunksPlugin {
22+
apply(compiler: Compiler) {
23+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
24+
compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk) => {
25+
if (chunk.name) {
26+
return;
27+
}
28+
29+
const name = this.generateName(chunk);
30+
if (name) {
31+
chunk.name = name;
32+
}
33+
});
34+
});
35+
}
36+
37+
private generateName(chunk: Chunk): string | undefined {
38+
for (const group of chunk.groupsIterable) {
39+
const [block] = group.getBlocks();
40+
if (!(block instanceof AsyncDependenciesBlock)) {
41+
continue;
42+
}
43+
44+
for (const dependency of block.dependencies) {
45+
if (dependency instanceof ImportDependency) {
46+
return Template.toPath(dependency.request);
47+
}
48+
}
49+
}
50+
51+
return undefined;
52+
}
53+
}

0 commit comments

Comments
 (0)