Visitar URL original
fix: use buildInfo.diagnostics and persist parse-time bailouts by hai-x · Pull Request #22055 · webpack/webpack · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/021-cache-optimization-bailouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack": patch
---

Move diagnostics to `buildInfo.diagnostics`, persisting parse-time bailouts.
11 changes: 9 additions & 2 deletions lib/Compilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const LazySet = require("./util/LazySet");
const { getOrInsert } = require("./util/MapHelpers");
const WeakTupleMap = require("./util/WeakTupleMap");
const asyncLib = require("./util/async");
const { replayOptimizationBailouts } = require("./util/buildDiagnostics");
const { cachedCleverMerge } = require("./util/cleverMerge");
const {
compareErrors,
Expand Down Expand Up @@ -1826,6 +1827,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
if (currentProfile !== undefined) {
currentProfile.markBuildingEnd();
}
replayOptimizationBailouts(this.moduleGraph, module);
this.hooks.stillValidModule.call(module);
return callback();
}
Expand All @@ -1846,6 +1848,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
this.hooks.failedModule.call(module, err);
return callback(err);
}
replayOptimizationBailouts(this.moduleGraph, module);
if (currentProfile !== undefined) {
currentProfile.markStoringStart();
}
Expand Down Expand Up @@ -6378,8 +6381,12 @@ This prevents using hashes of each other and should be avoided.`);
const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
if (buildInfo.cacheable === false) {
cacheable = false;
if (buildInfo.notCacheableReasons) {
for (const reason of buildInfo.notCacheableReasons) {
const moduleDiagnostics = buildInfo.diagnostics;
if (
moduleDiagnostics !== undefined &&
moduleDiagnostics.notCacheableReasons
) {
for (const reason of moduleDiagnostics.notCacheableReasons) {
if (!notCacheableReasons.includes(reason)) {
notCacheableReasons.push(reason);
}
Expand Down
5 changes: 4 additions & 1 deletion lib/DefinePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
evaluateToString,
toConstantDependency
} = require("./javascript/JavascriptParserHelpers");
const { getBuildDiagnostics } = require("./util/buildDiagnostics");
const createHash = require("./util/createHash");
const createHooksRegistry = require("./util/createHooksRegistry");

Expand Down Expand Up @@ -92,8 +93,10 @@ class RuntimeValue {
const buildInfo = /** @type {BuildInfo} */ (parser.state.module.buildInfo);
if (this.options === true) {
buildInfo.cacheable = false;
const diagnostics = getBuildDiagnostics(buildInfo);
const reasons =
buildInfo.notCacheableReasons || (buildInfo.notCacheableReasons = []);
diagnostics.notCacheableReasons ||
(diagnostics.notCacheableReasons = []);
const reason = `DefinePlugin runtime value ${key}`;
if (!reasons.includes(reason)) reasons.push(reason);
} else {
Expand Down
8 changes: 5 additions & 3 deletions lib/JavascriptMetaInfoPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
JAVASCRIPT_MODULE_TYPE_ESM
} = require("./ModuleTypeConstants");
const { getInnerGraphUtils } = require("./optimize/InnerGraph");
const { recordOptimizationBailout } = require("./util/buildDiagnostics");

/** @import Compiler from "./Compiler" */
/** @import { BuildInfo } from "./Module" */
Expand Down Expand Up @@ -73,9 +74,10 @@ class JavascriptMetaInfoPlugin {
const wasEnabled = innerGraph.isEnabled(parser.state);
innerGraph.bailout(parser.state);
if (wasEnabled) {
compilation.moduleGraph
.getOptimizationBailout(parser.state.module)
.push("Inner graph bailout: eval()");
recordOptimizationBailout(
buildInfo,
"Inner graph bailout: eval()"
);
}
}
});
Expand Down
17 changes: 12 additions & 5 deletions lib/Module.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,31 @@ const makeSerializable = require("./util/makeSerializable");
* @property {Map<string, string>=} exportsBindingSourceByRuntime export definitions `ModuleLibraryPlugin` took over, re-emitted only where the module is wrapped
*/

/**
* What a build recorded for stats and the performance hints to report, kept on the build info so a module restored from the cache still has it.
* @typedef {object} BuildDiagnostics
* @property {string[]=} optimizationBailouts what parser plugins reported as optimization bailouts, replayed into the module graph of every compilation that reuses the module
* @property {string=} sideEffectStatement the statement whose side effect keeps the module in the bundle, as `type at location`
* @property {number=} ineffectivePureAnnotations how many `#__PURE__` annotations sit where the parser does not read them
* @property {number=} topLevelThis how many times the module reads `this` at its top level
* @property {boolean=} usesEval true when the module calls `eval` directly
* @property {string[]=} notCacheableReasons reasons why the module is not cacheable (e.g. paths of loaders that marked it)
*/

/**
* Defines the build info properties common to all module types.
* Module type specific properties live in the `Known*BuildInfo` typedef of the dedicated module class.
* @typedef {object} KnownBuildInfo
* @property {boolean=} cacheable
* @property {string[]=} notCacheableReasons reasons why the module is not cacheable (e.g. paths of loaders that marked it)
* @property {boolean=} strict
* @property {string=} moduleArgument
* @property {string=} exportsArgument
* @property {Record<string, Source>=} assets assets added by loaders or plugins
* @property {Map<string, AssetInfo | undefined>=} assetsInfo
* @property {Set<string>=} topLevelDeclarations top level declaration names
* @property {boolean=} isCircular true when the module is part of a circular dependency chain
* @property {boolean=} usesEval true when the module calls `eval` directly
* @property {number=} ineffectivePureAnnotations how many `#__PURE__` annotations sit where the parser does not read them
* @property {number=} topLevelThis how many times the module reads `this` at its top level
* @property {string=} sideEffectStatement the statement whose side effect keeps the module in the bundle, as `type at location`
* @property {boolean=} usesTopLevelAwaitForOf module uses top-level `for await…of` or `await using`, which can't be lowered to a generator
* @property {BuildDiagnostics=} diagnostics what the build recorded for stats and the performance hints to report
*/

/** @typedef {string | Set<string>} ValueCacheVersion */
Expand Down
6 changes: 4 additions & 2 deletions lib/NormalModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const {
const LazySet = require("./util/LazySet");
const { isSubset } = require("./util/SetHelpers");
const { getScheme } = require("./util/URLAbsoluteSpecifier");
const { getBuildDiagnostics } = require("./util/buildDiagnostics");
const {
concatComparators,
keepOriginalOrder,
Expand Down Expand Up @@ -1524,7 +1525,8 @@ class NormalModule extends Module {
}
buildInfo.cacheable = buildInfo.cacheable && result.cacheable;
if (result.notCacheableReasons.length > 0) {
buildInfo.notCacheableReasons = result.notCacheableReasons;
getBuildDiagnostics(buildInfo).notCacheableReasons =
result.notCacheableReasons;
}
processResult(err, result.result);
}
Expand Down Expand Up @@ -1632,7 +1634,6 @@ class NormalModule extends Module {
};
this.buildInfo = {
cacheable: false,
notCacheableReasons: undefined,
parsed: true,
fileDependencies: undefined,
contextDependencies: undefined,
Expand All @@ -1651,6 +1652,7 @@ class NormalModule extends Module {
inlineExports: undefined,
moduleConcatenationBailout: undefined,
moduleConcatenationRootBailout: undefined,
diagnostics: undefined,
needCreateRequire: undefined,
usesTopLevelUsingDeclaration: undefined
};
Expand Down
7 changes: 1 addition & 6 deletions lib/WebpackOptionsApply.js
Original file line number Diff line number Diff line change
Expand Up @@ -693,12 +693,7 @@ class WebpackOptionsApply extends OptionsApply {
const SideEffectsFlagPlugin = require("./optimize/SideEffectsFlagPlugin");

new SideEffectsFlagPlugin(
options.optimization.sideEffects === true,
Boolean(
options.performance &&
options.performance.hints &&
options.performance.unusedModules
)
options.optimization.sideEffects === true
).apply(compiler);
}
if (options.optimization.providedExports) {
Expand Down
9 changes: 6 additions & 3 deletions lib/dependencies/AMDDefineDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const { recordOptimizationBailout } = require("../util/buildDiagnostics");
const AMDDefineDependency = require("./AMDDefineDependency");
const AMDRequireArrayDependency = require("./AMDRequireArrayDependency");
const AMDRequireContextDependency = require("./AMDRequireContextDependency");
Expand Down Expand Up @@ -35,6 +36,7 @@ const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers");
* } from "../../declarations/WebpackOptions"
*/
/** @import Dependency from "../Dependency" */
/** @import { BuildInfo } from "../Module" */
/** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
/**
* @import JavascriptParser, {
Expand Down Expand Up @@ -353,9 +355,10 @@ class AMDDefineDependencyParserPlugin {
return;
}
DynamicExports.bailout(parser.state);
parser.state.compilation.moduleGraph
.getOptimizationBailout(parser.state.module)
.push("AMD bailout: define() prevents static exports analysis");
recordOptimizationBailout(
/** @type {BuildInfo} */ (parser.state.module.buildInfo),
"AMD bailout: define() prevents static exports analysis"
);
/** @type {Identifier[] | null} */
let fnParams = null;
let fnParamsOffset = 0;
Expand Down
19 changes: 6 additions & 13 deletions lib/dependencies/CommonJsExportsParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

const RuntimeGlobals = require("../RuntimeGlobals");
const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
const { recordOptimizationBailout } = require("../util/buildDiagnostics");
const formatLocation = require("../util/formatLocation");
const { propertyAccess } = require("../util/property");
const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
Expand Down Expand Up @@ -38,7 +39,7 @@ const FUNCTION_PROPERTY_DROP_HEAD =
* } from "estree"
*/
/** @import { ExportInfoName } from "../Dependency" */
/** @import ModuleGraph from "../ModuleGraph" */
/** @import { BuildInfo } from "../Module" */
/** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
/**
* @import JavascriptParser, {
Expand Down Expand Up @@ -357,15 +358,6 @@ const getObjectExportKind = (property) => {
const PLUGIN_NAME = "CommonJsExportsParserPlugin";

class CommonJsExportsParserPlugin {
/**
* Creates an instance of CommonJsExportsParserPlugin.
* @param {ModuleGraph} moduleGraph module graph
*/
constructor(moduleGraph) {
/** @type {ModuleGraph} */
this.moduleGraph = moduleGraph;
}

/**
* Applies the plugin by registering its hooks on the compiler.
* @param {JavascriptParser} parser the parser
Expand Down Expand Up @@ -406,9 +398,10 @@ class CommonJsExportsParserPlugin {
* @param {string} reason reason
*/
const bailoutHint = (reason) => {
this.moduleGraph
.getOptimizationBailout(parser.state.module)
.push(`CommonJS bailout: ${reason}`);
recordOptimizationBailout(
/** @type {BuildInfo} */ (parser.state.module.buildInfo),
`CommonJS bailout: ${reason}`
);
};
/** @type {unknown} */
let keptAllExportsForState;
Expand Down
4 changes: 1 addition & 3 deletions lib/dependencies/CommonJsPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,7 @@ class CommonJsPlugin {
);

new CommonJsImportsParserPlugin(parserOptions).apply(parser);
new CommonJsExportsParserPlugin(compilation.moduleGraph).apply(
parser
);
new CommonJsExportsParserPlugin().apply(parser);
};

normalModuleFactory.hooks.parser
Expand Down
13 changes: 8 additions & 5 deletions lib/optimize/ConcatenatedModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const {
} = require("../runtime/MakeDeferredNamespaceObjectRuntime");
const { equals } = require("../util/ArrayHelpers");
const LazySet = require("../util/LazySet");
const { getBuildDiagnostics } = require("../util/buildDiagnostics");
const {
compareModulesByFullName,
concatComparators
Expand Down Expand Up @@ -1185,15 +1186,17 @@ class ConcatenatedModule extends Module {

for (const m of this._modules) {
// populate cacheable
const { cacheable, notCacheableReasons } = /** @type {BuildInfo} */ (
m.buildInfo
);
const { cacheable, diagnostics } = /** @type {BuildInfo} */ (m.buildInfo);
if (!cacheable) {
this.buildInfo.cacheable = false;
const notCacheableReasons =
diagnostics === undefined
? undefined
: diagnostics.notCacheableReasons;
if (notCacheableReasons) {
const own = getBuildDiagnostics(this.buildInfo);
const reasons =
this.buildInfo.notCacheableReasons ||
(this.buildInfo.notCacheableReasons = []);
own.notCacheableReasons || (own.notCacheableReasons = []);
for (const reason of notCacheableReasons) {
if (!reasons.includes(reason)) reasons.push(reason);
}
Expand Down
35 changes: 15 additions & 20 deletions lib/optimize/SideEffectsFlagPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const HarmonyExportImportedSpecifierDependency = require("../dependencies/Harmon
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
const { ImportPhaseUtils } = require("../dependencies/ImportPhase");
const {
getBuildDiagnostics,
recordOptimizationBailout
} = require("../util/buildDiagnostics");
const formatLocation = require("../util/formatLocation");
const { dirname, join, readJson, relative } = require("../util/fs");
const { getGlobToRegExpSource } = require("../util/globUtils");
Expand Down Expand Up @@ -334,13 +338,10 @@ class SideEffectsFlagPlugin {
/**
* Creates an instance of SideEffectsFlagPlugin.
* @param {boolean} analyseSource analyse source code for side effects
* @param {boolean=} recordStatement keep the statement that made a module have side effects, for `performance.unusedModules` to report
*/
constructor(analyseSource = true, recordStatement = false) {
constructor(analyseSource = true) {
/** @type {boolean} */
this._analyseSource = analyseSource;
/** @type {boolean} */
this._recordStatement = recordStatement;
}

/**
Expand Down Expand Up @@ -423,8 +424,6 @@ class SideEffectsFlagPlugin {
return module;
});
if (this._analyseSource) {
const recordStatement = this._recordStatement;

/**
* Processes the provided parser.
* @param {JavascriptParser} parser the parser
Expand Down Expand Up @@ -553,20 +552,16 @@ class SideEffectsFlagPlugin {
const loc = parser.getLocation(sideEffectsStatement);
const at = formatLocation(loc);

// The bailout lives on the module graph, which the cache does
// not restore. Gated: every module here would carry a string.
if (recordStatement) {
/** @type {BuildInfo} */
(parser.state.module.buildInfo).sideEffectStatement =
`${type} at ${at}`;
}

moduleGraph
.getOptimizationBailout(parser.state.module)
.push(
() =>
`Statement (${type}) with side effects in source code at ${at}`
);
const buildInfo = /** @type {BuildInfo} */ (
parser.state.module.buildInfo
);
recordOptimizationBailout(
buildInfo,
`Statement (${type}) with side effects in source code at ${at}`
);
// `performance.unusedModules` names the statement, so it is kept typed too.
getBuildDiagnostics(buildInfo).sideEffectStatement =
`${type} at ${at}`;
}
});
};
Expand Down
7 changes: 4 additions & 3 deletions lib/performance/CacheEffectivenessPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ class CacheEffectivenessPlugin {

uncacheable++;

const notCacheableReasons =
buildInfo.diagnostics && buildInfo.diagnostics.notCacheableReasons;
const reasons =
buildInfo.notCacheableReasons &&
buildInfo.notCacheableReasons.length > 0
? buildInfo.notCacheableReasons
notCacheableReasons && notCacheableReasons.length > 0
? notCacheableReasons
: [UNSPECIFIED_REASON];

for (const reason of reasons) {
Expand Down
7 changes: 5 additions & 2 deletions lib/performance/EvalUsagePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
JAVASCRIPT_MODULE_TYPE_ESM
} = require("../ModuleTypeConstants");
const EvalUsageWarning = require("../errors/EvalUsageWarning");
const { getBuildDiagnostics } = require("../util/buildDiagnostics");
const { compareStrings } = require("../util/comparators");

/** @import { PerformanceOptions } from "../../declarations/WebpackOptions" */
Expand Down Expand Up @@ -59,7 +60,7 @@ class EvalUsagePlugin {
parser.state.module.buildInfo
);

buildInfo.usesEval = true;
getBuildDiagnostics(buildInfo).usesEval = true;
});
};

Expand All @@ -84,7 +85,9 @@ class EvalUsagePlugin {
// Every built module carries one, so only the field is in question.
const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);

if (!buildInfo.usesEval) continue;
if (!buildInfo.diagnostics || !buildInfo.diagnostics.usesEval) {
continue;
}

callers.push(module.readableIdentifier(requestShortener));
}
Expand Down
Loading
Loading