Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): lazy modules bundle budgets
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alan-agius4 authored and dgp1130 committed Nov 29, 2021
1 parent 1abd173 commit 4c288b8
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { logging } from '@angular-devkit/core';
import { lazyModuleFiles, lazyModuleFnImport } from '../../../../testing/test-utils';
import { buildWebpackBrowser } from '../../index';
import { Type } from '../../schema';
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
Expand Down Expand Up @@ -67,6 +68,26 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
);
});

it(`should warn when lazy bundle is above 'maximumWarning' threshold`, async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
optimization: true,
budgets: [{ type: Type.Bundle, name: 'lazy-lazy-module', maximumWarning: '100b' }],
});

await harness.writeFiles(lazyModuleFiles);
await harness.writeFiles(lazyModuleFnImport);

const { result, logs } = await harness.executeOnce();
expect(result?.success).toBe(true);
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
level: 'warn',
message: jasmine.stringMatching('lazy-lazy-module exceeded maximum budget'),
}),
);
});

CSS_EXTENSIONS.forEach((ext) => {
it(`shows warnings for large component ${ext} when using 'anyComponentStyle' when AOT`, async () => {
const cssContent = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
JsonStatsPlugin,
ScriptsWebpackPlugin,
} from '../plugins';
import { NamedChunksPlugin } from '../plugins/named-chunks-plugin';
import { ProgressPlugin } from '../plugins/progress-plugin';
import { TransferSizePlugin } from '../plugins/transfer-size-plugin';
import { createIvyPlugin } from '../plugins/typescript';
Expand Down Expand Up @@ -448,7 +449,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
},
},
},
plugins: [new DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
plugins: [new NamedChunksPlugin(), new DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
node: false,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { AsyncDependenciesBlock, Chunk, Compiler, Template, dependencies } from 'webpack';

// `ImportDependency` is not part of Webpack's depenencies typings.
const ImportDependency: typeof dependencies.ModuleDependency = require('webpack/lib/dependencies/ImportDependency');

const PLUGIN_NAME = 'named-chunks-plugin';

/**
* Webpack will not populate the chunk `name` property unless `webpackChunkName` magic comment is used.
* This however will also effect the filename which is not desired when using `deterministic` chunkIds.
* This plugin will populate the chunk `name` which is mainly used so that users can set bundle budgets on lazy chunks.
*/
export class NamedChunksPlugin {
apply(compiler: Compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk) => {
if (chunk.name) {
return;
}

const name = this.generateName(chunk);
if (name) {
chunk.name = name;
}
});
});
}

private generateName(chunk: Chunk): string | undefined {
for (const group of chunk.groupsIterable) {
const [block] = group.getBlocks();
if (!(block instanceof AsyncDependenciesBlock)) {
continue;
}

for (const dependency of block.dependencies) {
if (dependency instanceof ImportDependency) {
return Template.toPath(dependency.request);
}
}
}

return undefined;
}
}

0 comments on commit 4c288b8

Please sign in to comment.