Skip to content

Commit a1e514f

Browse files
refactor(performance): group the hint options before any of them ship
None of the checks has been released, so the surface is still free to change. Twenty-seven options where there were thirty-three: - 'unusedConfig' replaces 'unusedAliases', 'unusedDefines', 'unusedExternals' and 'unusedRules'. Four switches for one finding — configuration nothing used — where the fix is the same each time. Each check keeps its own message. - 'sourceMaps' replaces 'embeddedSourceMaps' and 'missingSourceMaps'. - 'unusedModules' absorbs 'unusedReexports': the same module-nothing-uses finding, told apart only by what kept it there, so each names its own cause and neither reports what the other will. - 'duplicateModules' absorbs 'entrypointOverlap', which was a strict subset of it once the bug below was fixed. 'duplicateModules' counted chunks per module, so a module concatenated into two entrypoints looked like it was in none and went unreported; 'entrypointOverlap' saw it only because it expanded concatenations. It now expands them too, and names the entrypoints paying for each copy. 'unusedModules' reported every 'import "./polyfill"', which is deliberate and was already ruled out for 'unusedReexports'. It now asks that some importer bound a name, reusing the bare-import class, and skips modules webpack left out — with no chunk there is no runtime, which is the only place 'hasOnlyUnusedExports' reads usage.
1 parent a4464a3 commit a1e514f

117 files changed

Lines changed: 348 additions & 624 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/017-performance-diagnostic-gaps.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"webpack": minor
33
---
44

5-
Add performance hints for loaders that drop the source map they were given, for asset files emitted for an import nothing reads, and for modules kept in the bundle only by a side-effect statement, and keep a bare `import` of an asset emitting its file when asset modules are side-effect-free.
5+
Add performance hints for source maps that cost more than they give, for asset files emitted for an import nothing reads, and for modules kept in the bundle only by a side effect; group the checks for configuration that did nothing under one `unusedConfig` option; report duplicated modules the chunk graph alone could not see; and keep a bare `import` of an asset emitting its file when asset modules are side-effect-free.

‎declarations/WebpackOptions.d.ts‎

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3467,16 +3467,6 @@ export interface PerformanceOptions {
34673467
* @since 5.110.0
34683468
*/
34693469
dynamicExports?: boolean;
3470-
/**
3471-
* Report a production build whose 'devtool' writes the source map into the JavaScript, so everyone loading the page downloads it (requires 'hints' to be enabled).
3472-
* @since 5.110.0
3473-
*/
3474-
embeddedSourceMaps?: boolean;
3475-
/**
3476-
* Report modules shipped by more than one entrypoint, which every page that loads them downloads again (requires 'hints' to be enabled).
3477-
* @since 5.110.0
3478-
*/
3479-
entrypointOverlap?: boolean;
34803470
/**
34813471
* Report modules that call 'eval' directly, which stops minification, scope hoisting and tree shaking (requires 'hints' to be enabled).
34823472
* @since 5.110.0
@@ -3519,11 +3509,6 @@ export interface PerformanceOptions {
35193509
* @since 5.110.0
35203510
*/
35213511
missingSideEffects?: boolean;
3522-
/**
3523-
* Report modules a loader transformed without returning a source map, which leaves the map pointing at the loader's output rather than the file (requires 'hints' to be enabled).
3524-
* @since 5.110.0
3525-
*/
3526-
missingSourceMaps?: boolean;
35273512
/**
35283513
* Report an entry that exports a default beside named exports for a CommonJS library, where a consumer receives the namespace object (requires 'hints' to be enabled).
35293514
* @since 5.110.0
@@ -3549,6 +3534,11 @@ export interface PerformanceOptions {
35493534
* @since 5.110.0
35503535
*/
35513536
scopeHoistingBailouts?: boolean;
3537+
/**
3538+
* Report source maps that cost more than they give: a production 'devtool' that writes the map into the JavaScript, and modules a loader transformed without returning a map, which leaves positions pointing at the loader's output (requires 'hints' to be enabled).
3539+
* @since 5.110.0
3540+
*/
3541+
sourceMaps?: boolean;
35523542
/**
35533543
* Report splits 'optimization.splitChunks' refused because 'maxInitialRequests' or 'maxAsyncRequests' was already reached (requires 'hints' to be enabled).
35543544
* @since 5.110.0
@@ -3569,41 +3559,21 @@ export interface PerformanceOptions {
35693559
* @since 5.110.0
35703560
*/
35713561
unsplitVendors?: boolean;
3572-
/**
3573-
* Report 'resolve.alias' entries that no request matched.
3574-
* @since 5.110.0
3575-
*/
3576-
unusedAliases?: boolean;
35773562
/**
35783563
* Report asset files emitted for an import whose binding nothing reads, so the bytes ship for nothing (requires 'hints' to be enabled).
35793564
* @since 5.110.0
35803565
*/
35813566
unusedAssets?: boolean;
35823567
/**
3583-
* Report keys defined by 'DefinePlugin' that no module ever referenced, which cost a parser hook per module and invalidate the build when their value changes.
3584-
* @since 5.110.0
3585-
*/
3586-
unusedDefines?: boolean;
3587-
/**
3588-
* Report requests listed in 'externals' that no module ever imported, which usually means the request is misspelled and the real one got bundled instead.
3568+
* Report configuration that no build used: 'resolve.alias' entries nothing matched, 'DefinePlugin' keys nothing referenced, 'externals' nothing imported, and 'module.rules' that never matched (requires 'hints' to be enabled).
35893569
* @since 5.110.0
35903570
*/
3591-
unusedExternals?: boolean;
3571+
unusedConfig?: boolean;
35923572
/**
3593-
* Report modules bundled although nothing uses what they export, naming the statement whose side effect kept each one (requires 'hints' to be enabled).
3573+
* Report modules bundled although nothing uses what they export, naming the re-export or the side-effect statement that kept each one (requires 'hints' to be enabled).
35943574
* @since 5.110.0
35953575
*/
35963576
unusedModules?: boolean;
3597-
/**
3598-
* Report modules bundled although nothing uses what they export, pulled in by a re-export (requires 'hints' to be enabled).
3599-
* @since 5.110.0
3600-
*/
3601-
unusedReexports?: boolean;
3602-
/**
3603-
* Report rules in 'module.rules' that never matched a module, which cost condition evaluation on every build. Note that plugins may add rules too, so a reported rule is not necessarily one you wrote.
3604-
* @since 5.110.0
3605-
*/
3606-
unusedRules?: boolean;
36073577
}
36083578
/**
36093579
* Options affecting how file system snapshots are created and validated.

‎lib/DefinePlugin.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ const PLUGIN_NAME = "DefinePlugin";
375375
const VALUE_DEP_PREFIX = `webpack/${PLUGIN_NAME} `;
376376
const VALUE_DEP_MAIN = `webpack/${PLUGIN_NAME}_hash`;
377377

378-
// Keys webpack injects itself are not the user's to remove, so `performance.unusedDefines`
378+
// Keys webpack injects itself are not the user's to remove, so `performance.unusedConfig`
379379
// never sees them.
380380
/** @type {WeakSet<DefinePlugin>} */
381381
const INTERNAL_PLUGINS = new WeakSet();
@@ -721,11 +721,11 @@ class DefinePlugin {
721721
(compilation, { normalModuleFactory }) => {
722722
const definitions = this.definitions;
723723
const { performance } = compilation.options;
724-
// Only `performance.unusedDefines` reads these, so a build without it
724+
// Only `performance.unusedConfig` reads these, so a build without it
725725
// collects nothing.
726726
const collectDeclared =
727727
performance !== false &&
728-
performance.unusedDefines === true &&
728+
performance.unusedConfig === true &&
729729
!INTERNAL_PLUGINS.has(this);
730730
/** @type {Set<string> | undefined} */
731731
let declaredKeys;
@@ -1331,7 +1331,7 @@ class DefinePlugin {
13311331
DefinePlugin.getCompilationHooks = createHooksRegistry(createCompilationHooks);
13321332

13331333
/**
1334-
* Marks an instance as webpack's own, so `performance.unusedDefines` never
1334+
* Marks an instance as webpack's own, so `performance.unusedConfig` never
13351335
* reports the keys it declares against the user.
13361336
* @param {DefinePlugin} plugin the instance webpack constructed itself
13371337
* @returns {DefinePlugin} the same instance

‎lib/WebpackOptionsApply.js‎

Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -974,12 +974,6 @@ class WebpackOptionsApply extends OptionsApply {
974974
new RedundantDynamicImportsPlugin(options.performance).apply(compiler);
975975
}
976976

977-
if (options.performance.entrypointOverlap) {
978-
const EntrypointOverlapPlugin = require("./performance/EntrypointOverlapPlugin");
979-
980-
new EntrypointOverlapPlugin(options.performance).apply(compiler);
981-
}
982-
983977
if (options.performance.largeModules) {
984978
const LargeModulesPlugin = require("./performance/LargeModulesPlugin");
985979

@@ -1052,12 +1046,6 @@ class WebpackOptionsApply extends OptionsApply {
10521046
new DynamicExportsPlugin(options.performance).apply(compiler);
10531047
}
10541048

1055-
if (options.performance.embeddedSourceMaps) {
1056-
const EmbeddedSourceMapsPlugin = require("./performance/EmbeddedSourceMapsPlugin");
1057-
1058-
new EmbeddedSourceMapsPlugin(options.performance).apply(compiler);
1059-
}
1060-
10611049
if (options.performance.inlinedAssets) {
10621050
const InlinedAssetsPlugin = require("./performance/InlinedAssetsPlugin");
10631051

@@ -1070,27 +1058,45 @@ class WebpackOptionsApply extends OptionsApply {
10701058
new MissingSideEffectsPlugin(options.performance).apply(compiler);
10711059
}
10721060

1073-
if (options.performance.missingSourceMaps) {
1061+
if (options.performance.scopeHoistingBailouts) {
1062+
const ScopeHoistingBailoutsPlugin = require("./performance/ScopeHoistingBailoutsPlugin");
1063+
1064+
new ScopeHoistingBailoutsPlugin(options.performance).apply(compiler);
1065+
}
1066+
1067+
// One switch for configuration that did nothing: the four read different
1068+
// parts of the config, but the finding and the fix are the same.
1069+
// One switch for source maps: a devtool that ships the map to everyone,
1070+
// and a loader that loses it, are both about the map being worth its cost.
1071+
if (options.performance.sourceMaps) {
1072+
const EmbeddedSourceMapsPlugin = require("./performance/EmbeddedSourceMapsPlugin");
10741073
const MissingSourceMapsPlugin = require("./performance/MissingSourceMapsPlugin");
10751074

1075+
new EmbeddedSourceMapsPlugin(options.performance).apply(compiler);
10761076
new MissingSourceMapsPlugin(options.performance).apply(compiler);
10771077
}
10781078

1079-
if (options.performance.scopeHoistingBailouts) {
1080-
const ScopeHoistingBailoutsPlugin = require("./performance/ScopeHoistingBailoutsPlugin");
1079+
if (options.performance.unusedConfig) {
1080+
const UnusedAliasesPlugin = require("./performance/UnusedAliasesPlugin");
1081+
const UnusedDefinesPlugin = require("./performance/UnusedDefinesPlugin");
1082+
const UnusedExternalsPlugin = require("./performance/UnusedExternalsPlugin");
1083+
// Reports even where size hints are off: a dead rule is a configuration
1084+
// mistake either way.
1085+
const UnusedRuleWarningPlugin = require("./performance/UnusedRuleWarningPlugin");
10811086

1082-
new ScopeHoistingBailoutsPlugin(options.performance).apply(compiler);
1087+
new UnusedAliasesPlugin(options.performance).apply(compiler);
1088+
new UnusedDefinesPlugin(options.performance).apply(compiler);
1089+
new UnusedExternalsPlugin(options.performance).apply(compiler);
1090+
new UnusedRuleWarningPlugin(options.performance).apply(compiler);
10831091
}
10841092

1093+
// One switch for a module nothing uses: the two differ only in what kept
1094+
// it there, and each names the cause it knows about.
10851095
if (options.performance.unusedModules) {
10861096
const UnusedModulesPlugin = require("./performance/UnusedModulesPlugin");
1087-
1088-
new UnusedModulesPlugin(options.performance).apply(compiler);
1089-
}
1090-
1091-
if (options.performance.unusedReexports) {
10921097
const UnusedReexportsPlugin = require("./performance/UnusedReexportsPlugin");
10931098

1099+
new UnusedModulesPlugin(options.performance).apply(compiler);
10941100
new UnusedReexportsPlugin(options.performance).apply(compiler);
10951101
}
10961102

@@ -1104,12 +1110,6 @@ class WebpackOptionsApply extends OptionsApply {
11041110

11051111
// Deliberately not gated on `hints`: an alias nothing matches is a
11061112
// configuration mistake, not a size.
1107-
if (options.performance.unusedAliases) {
1108-
const UnusedAliasesPlugin = require("./performance/UnusedAliasesPlugin");
1109-
1110-
new UnusedAliasesPlugin(options.performance).apply(compiler);
1111-
}
1112-
11131113
// Deliberately not gated on `hints`: a key nothing reads is a
11141114
// configuration mistake, not a size.
11151115
if (options.performance.unusedAssets) {
@@ -1118,35 +1118,15 @@ class WebpackOptionsApply extends OptionsApply {
11181118
new UnusedAssetsPlugin(options.performance).apply(compiler);
11191119
}
11201120

1121-
if (options.performance.unusedDefines) {
1122-
const UnusedDefinesPlugin = require("./performance/UnusedDefinesPlugin");
1123-
1124-
new UnusedDefinesPlugin(options.performance).apply(compiler);
1125-
}
1126-
11271121
// Deliberately not gated on `hints`: a misspelled external gets bundled
11281122
// instead, which no size limit would explain.
1129-
if (options.performance.unusedExternals) {
1130-
const UnusedExternalsPlugin = require("./performance/UnusedExternalsPlugin");
1131-
1132-
new UnusedExternalsPlugin(options.performance).apply(compiler);
1133-
}
1134-
11351123
// Deliberately not gated on `hints`: asking for both directives on one
11361124
// import is a configuration mistake, not a size.
11371125
if (options.performance.conflictingResourceHints) {
11381126
const ConflictingResourceHintsPlugin = require("./performance/ConflictingResourceHintsPlugin");
11391127

11401128
new ConflictingResourceHintsPlugin(options.performance).apply(compiler);
11411129
}
1142-
1143-
// Deliberately not gated on `hints`: a dead rule is a configuration
1144-
// mistake worth reporting even where size hints are off.
1145-
if (options.performance.unusedRules) {
1146-
const UnusedRuleWarningPlugin = require("./performance/UnusedRuleWarningPlugin");
1147-
1148-
new UnusedRuleWarningPlugin(options.performance).apply(compiler);
1149-
}
11501130
}
11511131

11521132
new TemplatedPathPlugin().apply(compiler);

‎lib/config/defaults.js‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,31 +2403,25 @@ const applyPerformanceDefaults = (performance, { production }) => {
24032403
D(performance, "duplicateModules", all);
24042404
D(performance, "duplicatePackages", all);
24052405
D(performance, "dynamicExports", all);
2406-
D(performance, "entrypointOverlap", all);
2407-
D(performance, "embeddedSourceMaps", all);
24082406
D(performance, "evalUsage", all);
24092407
D(performance, "hotspots", all);
24102408
D(performance, "legacyJavascript", all);
24112409
D(performance, "largeModules", all);
24122410
D(performance, "inlinedAssets", all);
24132411
D(performance, "mixedExports", all);
24142412
D(performance, "missingSideEffects", all);
2415-
D(performance, "missingSourceMaps", all);
24162413
D(performance, "osDependentRules", all);
24172414
D(performance, "pureAnnotations", all);
24182415
D(performance, "redundantDynamicImports", all);
24192416
D(performance, "scopeHoistingBailouts", all);
24202417
D(performance, "splitChunksCapped", all);
2418+
D(performance, "sourceMaps", all);
24212419
D(performance, "tinyChunks", all);
24222420
D(performance, "topLevelThis", all);
24232421
D(performance, "unsplitVendors", all);
2424-
D(performance, "unusedAliases", all);
24252422
D(performance, "unusedAssets", all);
2426-
D(performance, "unusedDefines", all);
2427-
D(performance, "unusedExternals", all);
2423+
D(performance, "unusedConfig", all);
24282424
D(performance, "unusedModules", all);
2429-
D(performance, "unusedReexports", all);
2430-
D(performance, "unusedRules", all);
24312425
F(performance, "hints", () => (production ? "warning" : false));
24322426
};
24332427

‎lib/errors/DuplicateModulesWarning.js‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const WebpackError = require("./WebpackError");
1212
* @typedef {object} DuplicateModuleDetails
1313
* @property {string} name
1414
* @property {number} chunks how many chunks carry a copy
15+
* @property {string[]} entrypoints the entrypoints those chunks belong to, when more than one
1516
* @property {number} wasted bytes the extra copies cost
1617
*/
1718

@@ -23,18 +24,22 @@ class DuplicateModulesWarning extends WebpackError {
2324
*/
2425
constructor(modules, wasted) {
2526
const list = modules
26-
.map(
27-
(module) =>
28-
`\n ${module.name} (in ${module.chunks} chunks, ${formatSize(
29-
module.wasted
30-
)} extra)`
31-
)
27+
.map((module) => {
28+
// Which entrypoints pay for it is the actionable part, so it leads
29+
// where the copies are spread across more than one.
30+
const where =
31+
module.entrypoints.length > 1
32+
? `in ${module.entrypoints.join(", ")}, ${module.chunks} chunks`
33+
: `in ${module.chunks} chunks`;
34+
35+
return `\n ${module.name} (${where}, ${formatSize(module.wasted)} extra)`;
36+
})
3237
.join("");
3338

3439
super(
3540
`duplicate modules: copies of the same module across chunks add ${formatSize(
3641
wasted
37-
)}:${list}\nA module reached from several chunks is emitted into each of them unless a shared chunk holds it. 'optimization.splitChunks' can pull it into one.\nFor more info visit https://webpack.js.org/plugins/split-chunks-plugin/`
42+
)}:${list}\nA module reached from several chunks is emitted into each of them unless a shared chunk holds it. 'optimization.splitChunks' can pull it into one, with 'chunks: "all"' where the copies are in different entrypoints.\nFor more info visit https://webpack.js.org/plugins/split-chunks-plugin/`
3843
);
3944

4045
/** @type {string} */

‎lib/errors/EntrypointOverlapWarning.js‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)