Skip to content

Commit d853956

Browse files
committed
add optimization bailout info
track optimization bailout in concat plugin
1 parent b4d2520 commit d853956

7 files changed

Lines changed: 169 additions & 48 deletions

File tree

bin/webpack.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ yargs.options({
124124
group: DISPLAY_GROUP,
125125
describe: "Display information about exports provided from modules"
126126
},
127+
"display-optimization-bailout": {
128+
type: "boolean",
129+
group: DISPLAY_GROUP,
130+
describe: "Display information about why optimization bailed out for modules"
131+
},
127132
"display-error-details": {
128133
type: "boolean",
129134
group: DISPLAY_GROUP,
@@ -144,6 +149,7 @@ if(argv.verbose) {
144149
argv["display-entrypoints"] = true;
145150
argv["display-used-exports"] = true;
146151
argv["display-provided-exports"] = true;
152+
argv["display-optimization-bailout"] = true;
147153
argv["display-error-details"] = true;
148154
argv["display-modules"] = true;
149155
argv["display-cached"] = true;
@@ -243,6 +249,10 @@ function processOptions(options) {
243249
outputOptions.providedExports = bool;
244250
});
245251

252+
ifArg("display-optimization-bailout", function(bool) {
253+
outputOptions.optimizationBailout = bool;
254+
});
255+
246256
ifArg("display-error-details", function(bool) {
247257
outputOptions.errorDetails = bool;
248258
});

lib/FunctionModuleTemplatePlugin.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,23 @@ class FunctionModuleTemplatePlugin {
2525
if(this.outputOptions.pathinfo) {
2626
const source = new ConcatSource();
2727
const req = module.readableIdentifier(this.requestShortener);
28+
source.add("/*!****" + req.replace(/./g, "*") + "****!*\\\n");
29+
source.add(" !*** " + req.replace(/\*\//g, "*_/") + " ***!\n");
30+
source.add(" \\****" + req.replace(/./g, "*") + "****/\n");
2831
if(Array.isArray(module.providedExports))
29-
source.add("/* exports provided: " + module.providedExports.join(", ") + " */\n");
32+
source.add("/*! exports provided: " + module.providedExports.join(", ") + " */\n");
3033
else if(module.providedExports)
31-
source.add("/* unknown exports provided */\n");
34+
source.add("/*! unknown exports provided */\n");
3235
if(Array.isArray(module.usedExports))
33-
source.add("/* exports used: " + module.usedExports.join(", ") + " */\n");
36+
source.add("/*! exports used: " + module.usedExports.join(", ") + " */\n");
3437
else if(module.usedExports)
35-
source.add("/* all exports used */\n");
36-
source.add("/*!****" + req.replace(/./g, "*") + "****!*\\\n");
37-
source.add(" !*** " + req.replace(/\*\//g, "*_/") + " ***!\n");
38-
source.add(" \\****" + req.replace(/./g, "*") + "****/\n");
38+
source.add("/*! all exports used */\n");
39+
if(module.optimizationBailout) {
40+
module.optimizationBailout.forEach(text => {
41+
if(typeof text === "function") text = text(this.requestShortener);
42+
source.add(`/*! ${text} */\n`);
43+
});
44+
}
3945
source.add(moduleSource);
4046
return source;
4147
}

lib/Module.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Module extends DependenciesBlock {
5151
this.dependenciesErrors = [];
5252
this.strict = false;
5353
this.meta = {};
54+
this.optimizationBailout = [];
5455
}
5556

5657
disconnect() {

lib/Stats.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class Stats {
9393
const showReasons = optionOrFallback(options.reasons, !forToString);
9494
const showUsedExports = optionOrFallback(options.usedExports, !forToString);
9595
const showProvidedExports = optionOrFallback(options.providedExports, !forToString);
96+
const showOptimizationBailout = optionOrFallback(options.optimizationBailout, !forToString);
9697
const showChildren = optionOrFallback(options.children, true);
9798
const showSource = optionOrFallback(options.source, !forToString);
9899
const showModuleTrace = optionOrFallback(options.moduleTrace, true);
@@ -311,6 +312,12 @@ class Stats {
311312
if(showProvidedExports) {
312313
obj.providedExports = Array.isArray(module.providedExports) ? module.providedExports : null;
313314
}
315+
if(showOptimizationBailout) {
316+
obj.optimizationBailout = module.optimizationBailout.map(item => {
317+
if(typeof item === "function") return item(requestShortener);
318+
return item;
319+
});
320+
}
314321
if(showDepth) {
315322
obj.depth = module.depth;
316323
}
@@ -636,6 +643,13 @@ class Stats {
636643
newline();
637644
}
638645
}
646+
if(Array.isArray(module.optimizationBailout)) {
647+
module.optimizationBailout.forEach(item => {
648+
colors.normal(prefix);
649+
colors.yellow(item);
650+
newline();
651+
});
652+
}
639653
if(module.reasons) {
640654
module.reasons.forEach(reason => {
641655
colors.normal(prefix);
@@ -867,6 +881,7 @@ class Stats {
867881
depth: pn === "verbose",
868882
usedExports: pn === "verbose",
869883
providedExports: pn === "verbose",
884+
optimizationBailout: pn === "verbose",
870885
colors: true,
871886
performance: true
872887
};

lib/optimize/ConcatenatedModule.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class ConcatenatedModule extends Module {
5858
this.modules = modules;
5959
this.usedExports = rootModule.usedExports;
6060
this.providedExports = rootModule.providedExports;
61+
this.optimizationBailout = rootModule.optimizationBailout;
6162
this.used = rootModule.used;
6263
this._chunks = new Set(rootModule._chunks);
6364
this.index = rootModule.index;

lib/optimize/ModuleConcatenationPlugin.js

Lines changed: 117 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,47 +21,107 @@ class ModuleConcatenationPlugin {
2121
parser.state.module.meta.hasEval = true;
2222
});
2323
});
24+
const bailoutReasonMap = new Map();
25+
function setBailoutReason(module, reason) {
26+
bailoutReasonMap.set(module, reason);
27+
module.optimizationBailout.push(reason);
28+
}
29+
function getBailoutReason(module, requestShortener) {
30+
const reason = bailoutReasonMap.get(module);
31+
if(typeof reason === "function") return reason(requestShortener);
32+
return reason;
33+
}
2434
compilation.plugin("optimize-chunk-modules", (chunks, modules) => {
2535
chunks.forEach(chunk => {
26-
const relevantModules = chunk.mapModules(m => m).filter(module => {
36+
const relevantModules = [];
37+
const possibleInners = new Set();
38+
for(const module of chunk.modulesIterable) {
39+
// Only harmony modules are valid for optimization
40+
if(!module.meta || !module.meta.harmonyModule) {
41+
continue;
42+
}
43+
2744
// Module must not be in other chunks
2845
// TODO add an option to allow module to be in other entry points
29-
if(module.getNumberOfChunks() !== 1)
30-
return false;
46+
if(module.getNumberOfChunks() !== 1) {
47+
setBailoutReason(module, "ModuleConcatenation: module is in multiple chunks");
48+
continue;
49+
}
3150

3251
// Because of variable renaming we can't use modules with eval
33-
if(module.meta && module.meta.hasEval)
34-
return false;
52+
if(module.meta && module.meta.hasEval) {
53+
setBailoutReason(module, "ModuleConcatenation: eval is used in the module");
54+
continue;
55+
}
56+
57+
relevantModules.push(module);
3558

36-
return true;
37-
});
38-
const possibleInners = new Set(relevantModules.filter(module => {
3959
// Module must not be the entry points
40-
if(chunk.entryModule === module)
41-
return false;
60+
if(chunk.entryModule === module) {
61+
setBailoutReason(module, "ModuleConcatenation (inner): module is an entrypoint");
62+
continue;
63+
}
4264

4365
// Exports must be known (and not dynamic)
44-
if(!Array.isArray(module.providedExports))
45-
return false;
66+
if(!Array.isArray(module.providedExports)) {
67+
setBailoutReason(module, "ModuleConcatenation (inner): exports are not known");
68+
continue;
69+
}
4670

4771
// Using dependency variables is not possible as this wraps the code in a function
48-
if(module.variables.length > 0)
49-
return false;
72+
if(module.variables.length > 0) {
73+
setBailoutReason(module, "ModuleConcatenation (inner): dependency variables are used (i. e. ProvidePlugin)");
74+
continue;
75+
}
5076

5177
// Module must only be used by Harmony Imports
52-
if(!module.reasons.every(reason => reason.dependency instanceof HarmonyImportDependency))
53-
return false;
78+
const nonHarmonyReasons = module.reasons.filter(reason => !(reason.dependency instanceof HarmonyImportDependency));
79+
if(nonHarmonyReasons.length > 0) {
80+
const importingModules = new Set(nonHarmonyReasons.map(r => r.module));
81+
setBailoutReason(module, (requestShortener) => {
82+
const names = Array.from(importingModules).map(m => m.readableIdentifier(requestShortener));
83+
return `ModuleConcatenation (inner): module is used with non-harmony imports from ${names.join(", ")}`;
84+
});
85+
continue;
86+
}
5487

55-
return true;
56-
}));
88+
possibleInners.add(module);
89+
}
90+
// sort by depth
91+
// modules with lower depth are more likly suited as roots
92+
// this improves performance, because modules already selected as inner are skipped
93+
relevantModules.sort((a, b) => {
94+
return a.depth - b.depth;
95+
});
5796
const concatConfigurations = [];
97+
const usedAsInner = new Set();
5898
for(const currentRoot of relevantModules) {
99+
// when used by another configuration as inner:
100+
// the other configuration is better and we can skip this one
101+
if(usedAsInner.has(currentRoot))
102+
continue;
103+
104+
// create a configuration with the root
59105
const currentConfiguration = new ConcatConfiguration(currentRoot);
106+
107+
// cache failures to add modules
108+
const failureCache = new Map();
109+
110+
// try to add all imports
60111
for(const imp of this.getImports(currentRoot)) {
61-
this.tryToAdd(currentConfiguration, imp, possibleInners);
112+
const problem = this.tryToAdd(currentConfiguration, imp, possibleInners, failureCache);
113+
if(problem) {
114+
failureCache.set(imp, problem);
115+
currentConfiguration.addWarning(imp, problem);
116+
}
62117
}
63-
if(!currentConfiguration.isEmpty())
118+
if(!currentConfiguration.isEmpty()) {
64119
concatConfigurations.push(currentConfiguration);
120+
for(const module of currentConfiguration.modules) {
121+
if(module !== currentConfiguration.rootModule)
122+
usedAsInner.add(module);
123+
}
124+
}
65125
}
66126
// HACK: Sort configurations by length and start with the longest one
67127
// to get the biggers groups possible. Used modules are marked with usedModules
@@ -77,6 +137,16 @@ class ModuleConcatenationPlugin {
77137
const orderedModules = new Set();
78138
this.addInOrder(concatConfiguration.rootModule, concatConfiguration.modules, orderedModules);
79139
const newModule = new ConcatenatedModule(concatConfiguration.rootModule, Array.from(orderedModules));
140+
for(const warning of concatConfiguration.warnings) {
141+
newModule.optimizationBailout.push((requestShortener) => {
142+
const reason = getBailoutReason(warning[0], requestShortener);
143+
const reasonPrefix = reason ? `: ${reason}` : "";
144+
if(warning[0] === warning[1])
145+
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)}${reasonPrefix}`;
146+
else
147+
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)} because of ${warning[1].readableIdentifier(requestShortener)}${reasonPrefix}`;
148+
});
149+
}
80150
for(const m of orderedModules) {
81151
usedModules.add(m);
82152
chunk.removeModule(m);
@@ -85,10 +155,7 @@ class ModuleConcatenationPlugin {
85155
compilation.modules.push(newModule);
86156
if(chunk.entryModule === concatConfiguration.rootModule)
87157
chunk.entryModule = newModule;
88-
concatConfiguration.rootModule.reasons.forEach(reason => {
89-
if(!concatConfiguration.modules.has(reason.module))
90-
reason.dependency.module = newModule;
91-
});
158+
newModule.reasons.forEach(reason => reason.dependency.module = newModule);
92159
}
93160
compilation.modules = compilation.modules.filter(m => !usedModules.has(m));
94161
});
@@ -117,19 +184,20 @@ class ModuleConcatenationPlugin {
117184
));
118185
}
119186

120-
tryToAdd(config, module, possibleModules) {
121-
// console.log("tryToAdd", module.debugId, module.resource);
187+
tryToAdd(config, module, possibleModules, failureCache) {
188+
const cacheEntry = failureCache.get(module);
189+
if(cacheEntry) {
190+
return cacheEntry;
191+
}
122192

123193
// Already added?
124194
if(config.has(module)) {
125-
// console.log("already added");
126-
return true;
195+
return null;
127196
}
128197

129198
// Not possible to add?
130199
if(!possibleModules.has(module)) {
131-
// console.log("not possible");
132-
return false;
200+
return module;
133201
}
134202

135203
// Clone config to make experimental changes
@@ -139,25 +207,25 @@ class ModuleConcatenationPlugin {
139207
testConfig.add(module);
140208

141209
// Every module which depends on the added module must be in the configuration too.
142-
// console.log("reasons start");
143210
for(const reason of module.reasons) {
144-
if(!this.tryToAdd(testConfig, reason.module, possibleModules)) {
145-
// console.log("reason failed");
146-
return false;
211+
const problem = this.tryToAdd(testConfig, reason.module, possibleModules, failureCache);
212+
if(problem) {
213+
failureCache.set(module, problem); // cache failures for performance
214+
return problem;
147215
}
148216
}
149-
// console.log("reasons end");
150217

151218
// Eagerly try to add imports too if possible
152-
// console.log("imports start");
153-
for(const imp of this.getImports(module))
154-
this.tryToAdd(testConfig, imp, possibleModules);
155-
// console.log("imports end");
219+
for(const imp of this.getImports(module)) {
220+
const problem = this.tryToAdd(testConfig, imp, possibleModules, failureCache);
221+
if(problem) {
222+
config.addWarning(module, problem);
223+
}
224+
}
156225

157-
// console.log("commit");
158226
// Commit experimental changes
159227
config.set(testConfig);
160-
return true;
228+
return null;
161229
}
162230

163231
addInOrder(module, unorderedSet, orderedSet) {
@@ -175,6 +243,7 @@ class ConcatConfiguration {
175243
constructor(rootModule) {
176244
this.rootModule = rootModule;
177245
this.modules = new Set([rootModule]);
246+
this.warnings = new Map();
178247
}
179248

180249
add(module) {
@@ -189,16 +258,23 @@ class ConcatConfiguration {
189258
return this.modules.size === 1;
190259
}
191260

261+
addWarning(module, problem) {
262+
this.warnings.set(module, problem);
263+
}
264+
192265
clone() {
193266
const clone = new ConcatConfiguration(this.rootModule);
194267
for(const module of this.modules)
195268
clone.add(module);
269+
for(const pair of this.warnings)
270+
clone.addWarning(pair[0], pair[1]);
196271
return clone;
197272
}
198273

199274
set(config) {
200275
this.rootModule = config.rootModule;
201276
this.modules = new Set(config.modules);
277+
this.warnings = new Map(config.warnings);
202278
}
203279
}
204280

schemas/webpackOptionsSchema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,18 @@
10111011
"assetsSort": {
10121012
"type": "string",
10131013
"description": "sort the assets by that field"
1014+
},
1015+
"providedExports": {
1016+
"type": "boolean",
1017+
"description": "show exports provided by modules"
1018+
},
1019+
"usedExports": {
1020+
"type": "boolean",
1021+
"description": "show exports used by modules"
1022+
},
1023+
"optimizationBailout": {
1024+
"type": "boolean",
1025+
"description": "show reasons why optimization bailed out for modules"
10141026
}
10151027
}
10161028
},

0 commit comments

Comments
 (0)