Skip to content

Commit 1769fa2

Browse files
authored
Merge pull request webpack#5064 from webpack/feature/scope-hoisting-multi-entry
Allow scope hoisting to process modules in multiple chunks
2 parents a73646a + 28f826a commit 1769fa2

19 files changed

+271
-133
lines changed

lib/Compilation.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,15 @@ class Compilation extends Tapable {
906906
chunks
907907
}];
908908

909+
const filterFn = dep => {
910+
if(chunks.has(dep.chunk)) return false;
911+
for(const chunk of chunks) {
912+
if(chunk.containsModule(dep.module))
913+
return false;
914+
}
915+
return true;
916+
};
917+
909918
while(queue2.length) {
910919
const queueItem = queue2.pop();
911920
chunk = queueItem.chunk;
@@ -914,14 +923,7 @@ class Compilation extends Tapable {
914923
const deps = chunkDependencies.get(chunk);
915924
if(!deps) continue;
916925

917-
const depsFiltered = deps.filter(dep => {
918-
if(chunks.has(dep.chunk)) return false;
919-
for(const chunk of chunks) {
920-
if(chunk.containsModule(dep.module))
921-
return false;
922-
}
923-
return true;
924-
});
926+
const depsFiltered = deps.filter(filterFn);
925927

926928
for(let i = 0; i < depsFiltered.length; i++) {
927929
const dep = depsFiltered[i];

lib/FlagDependencyExportsPlugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class FlagDependencyExportsPlugin {
1919
module = queue[i];
2020

2121
if(module.providedExports !== true) {
22-
moduleWithExports = false;
22+
moduleWithExports = module.meta && module.meta.harmonyModule;
2323
moduleProvidedExports = Array.isArray(module.providedExports) ? new Set(module.providedExports) : new Set();
2424
processDependenciesBlock(module);
2525
if(!moduleWithExports) {

lib/Module.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,28 @@ class Module extends DependenciesBlock {
120120
return Array.from(this._chunks, fn);
121121
}
122122

123+
getChunks() {
124+
return Array.from(this._chunks);
125+
}
126+
123127
getNumberOfChunks() {
124128
return this._chunks.size;
125129
}
126130

131+
hasEqualsChunks(otherModule) {
132+
if(this._chunks.size !== otherModule._chunks.size) return false;
133+
this._ensureChunksSortedByDebugId();
134+
otherModule._ensureChunksSortedByDebugId();
135+
const a = this._chunks[Symbol.iterator]();
136+
const b = otherModule._chunks[Symbol.iterator]();
137+
while(true) { // eslint-disable-line
138+
const aItem = a.next();
139+
const bItem = b.next();
140+
if(aItem.done) return true;
141+
if(aItem.value !== bItem.value) return false;
142+
}
143+
}
144+
127145
_ensureChunksSorted() {
128146
if(this._chunksIsSorted) return;
129147
this._chunks = new Set(Array.from(this._chunks).sort(byId));

lib/Stats.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,10 @@ class Stats {
641641
const processModuleContent = (module, prefix) => {
642642
if(Array.isArray(module.providedExports)) {
643643
colors.normal(prefix);
644-
colors.cyan(`[exports: ${module.providedExports.join(", ")}]`);
644+
if(module.providedExports.length === 0)
645+
colors.cyan("[no exports]");
646+
else
647+
colors.cyan(`[exports: ${module.providedExports.join(", ")}]`);
645648
newline();
646649
}
647650
if(module.usedExports !== undefined) {

lib/optimize/ModuleConcatenationPlugin.js

Lines changed: 135 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class ModuleConcatenationPlugin {
2323
});
2424
const bailoutReasonMap = new Map();
2525

26-
function setBailoutReason(module, reason) {
26+
function setBailoutReason(module, prefix, reason) {
2727
bailoutReasonMap.set(module, reason);
28-
module.optimizationBailout.push(reason);
28+
module.optimizationBailout.push(typeof reason === "function" ? (rs) => `${prefix}: ${reason(rs)}` : `${prefix}: ${reason}`);
2929
}
3030

3131
function getBailoutReason(module, requestShortener) {
@@ -35,141 +35,136 @@ class ModuleConcatenationPlugin {
3535
}
3636

3737
compilation.plugin("optimize-chunk-modules", (chunks, modules) => {
38-
chunks.forEach(chunk => {
39-
const relevantModules = [];
40-
const possibleInners = new Set();
41-
for(const module of chunk.modulesIterable) {
42-
// Only harmony modules are valid for optimization
43-
if(!module.meta || !module.meta.harmonyModule) {
44-
continue;
45-
}
38+
const relevantModules = [];
39+
const possibleInners = new Set();
40+
for(const module of modules) {
41+
// Only harmony modules are valid for optimization
42+
if(!module.meta || !module.meta.harmonyModule) {
43+
continue;
44+
}
4645

47-
// Module must not be in other chunks
48-
// TODO add an option to allow module to be in other entry points
49-
if(module.getNumberOfChunks() !== 1) {
50-
setBailoutReason(module, "ModuleConcatenation: module is in multiple chunks");
51-
continue;
52-
}
46+
// Because of variable renaming we can't use modules with eval
47+
if(module.meta && module.meta.hasEval) {
48+
setBailoutReason(module, "ModuleConcatenation", "eval is used in the module");
49+
continue;
50+
}
5351

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

60-
relevantModules.push(module);
54+
// Module must not be the entry points
55+
if(module.getChunks().some(chunk => chunk.entryModule === module)) {
56+
setBailoutReason(module, "ModuleConcatenation (inner)", "module is an entrypoint");
57+
continue;
58+
}
6159

62-
// Module must not be the entry points
63-
if(chunk.entryModule === module) {
64-
setBailoutReason(module, "ModuleConcatenation (inner): module is an entrypoint");
65-
continue;
66-
}
60+
// Exports must be known (and not dynamic)
61+
if(!Array.isArray(module.providedExports)) {
62+
setBailoutReason(module, "ModuleConcatenation (inner)", "exports are not known");
63+
continue;
64+
}
6765

68-
// Exports must be known (and not dynamic)
69-
if(!Array.isArray(module.providedExports)) {
70-
setBailoutReason(module, "ModuleConcatenation (inner): exports are not known");
71-
continue;
72-
}
66+
// Using dependency variables is not possible as this wraps the code in a function
67+
if(module.variables.length > 0) {
68+
setBailoutReason(module, "ModuleConcatenation (inner)", "dependency variables are used (i. e. ProvidePlugin)");
69+
continue;
70+
}
7371

74-
// Using dependency variables is not possible as this wraps the code in a function
75-
if(module.variables.length > 0) {
76-
setBailoutReason(module, "ModuleConcatenation (inner): dependency variables are used (i. e. ProvidePlugin)");
77-
continue;
78-
}
72+
// Module must only be used by Harmony Imports
73+
const nonHarmonyReasons = module.reasons.filter(reason => !(reason.dependency instanceof HarmonyImportDependency));
74+
if(nonHarmonyReasons.length > 0) {
75+
const importingModules = new Set(nonHarmonyReasons.map(r => r.module));
76+
setBailoutReason(module, "ModuleConcatenation (inner)", (requestShortener) => {
77+
const names = Array.from(importingModules).map(m => m.readableIdentifier(requestShortener)).sort();
78+
return `module is used with non-harmony imports from ${names.join(", ")}`;
79+
});
80+
continue;
81+
}
7982

80-
// Module must only be used by Harmony Imports
81-
const nonHarmonyReasons = module.reasons.filter(reason => !(reason.dependency instanceof HarmonyImportDependency));
82-
if(nonHarmonyReasons.length > 0) {
83-
const importingModules = new Set(nonHarmonyReasons.map(r => r.module));
84-
setBailoutReason(module, (requestShortener) => {
85-
const names = Array.from(importingModules).map(m => m.readableIdentifier(requestShortener));
86-
return `ModuleConcatenation (inner): module is used with non-harmony imports from ${names.join(", ")}`;
87-
});
88-
continue;
83+
possibleInners.add(module);
84+
}
85+
// sort by depth
86+
// modules with lower depth are more likly suited as roots
87+
// this improves performance, because modules already selected as inner are skipped
88+
relevantModules.sort((a, b) => {
89+
return a.depth - b.depth;
90+
});
91+
const concatConfigurations = [];
92+
const usedAsInner = new Set();
93+
for(const currentRoot of relevantModules) {
94+
// when used by another configuration as inner:
95+
// the other configuration is better and we can skip this one
96+
if(usedAsInner.has(currentRoot))
97+
continue;
98+
99+
// create a configuration with the root
100+
const currentConfiguration = new ConcatConfiguration(currentRoot);
101+
102+
// cache failures to add modules
103+
const failureCache = new Map();
104+
105+
// try to add all imports
106+
for(const imp of this.getImports(currentRoot)) {
107+
const problem = this.tryToAdd(currentConfiguration, imp, possibleInners, failureCache);
108+
if(problem) {
109+
failureCache.set(imp, problem);
110+
currentConfiguration.addWarning(imp, problem);
89111
}
90-
91-
possibleInners.add(module);
92112
}
93-
// sort by depth
94-
// modules with lower depth are more likly suited as roots
95-
// this improves performance, because modules already selected as inner are skipped
96-
relevantModules.sort((a, b) => {
97-
return a.depth - b.depth;
98-
});
99-
const concatConfigurations = [];
100-
const usedAsInner = new Set();
101-
for(const currentRoot of relevantModules) {
102-
// when used by another configuration as inner:
103-
// the other configuration is better and we can skip this one
104-
if(usedAsInner.has(currentRoot))
105-
continue;
106-
107-
// create a configuration with the root
108-
const currentConfiguration = new ConcatConfiguration(currentRoot);
109-
110-
// cache failures to add modules
111-
const failureCache = new Map();
112-
113-
// try to add all imports
114-
for(const imp of this.getImports(currentRoot)) {
115-
const problem = this.tryToAdd(currentConfiguration, imp, possibleInners, failureCache);
116-
if(problem) {
117-
failureCache.set(imp, problem);
118-
currentConfiguration.addWarning(imp, problem);
119-
}
120-
}
121-
if(!currentConfiguration.isEmpty()) {
122-
concatConfigurations.push(currentConfiguration);
123-
for(const module of currentConfiguration.modules) {
124-
if(module !== currentConfiguration.rootModule)
125-
usedAsInner.add(module);
126-
}
113+
if(!currentConfiguration.isEmpty()) {
114+
concatConfigurations.push(currentConfiguration);
115+
for(const module of currentConfiguration.modules) {
116+
if(module !== currentConfiguration.rootModule)
117+
usedAsInner.add(module);
127118
}
128119
}
129-
// HACK: Sort configurations by length and start with the longest one
130-
// to get the biggers groups possible. Used modules are marked with usedModules
131-
// TODO: Allow to reuse existing configuration while trying to add dependencies.
132-
// This would improve performance. O(n^2) -> O(n)
133-
concatConfigurations.sort((a, b) => {
134-
return b.modules.size - a.modules.size;
135-
});
136-
const usedModules = new Set();
137-
for(const concatConfiguration of concatConfigurations) {
138-
if(usedModules.has(concatConfiguration.rootModule))
139-
continue;
140-
const orderedModules = new Set();
141-
this.addInOrder(concatConfiguration.rootModule, concatConfiguration.modules, orderedModules);
142-
const newModule = new ConcatenatedModule(concatConfiguration.rootModule, Array.from(orderedModules));
143-
for(const warning of concatConfiguration.warnings) {
144-
newModule.optimizationBailout.push((requestShortener) => {
145-
const reason = getBailoutReason(warning[0], requestShortener);
146-
const reasonPrefix = reason ? `: ${reason}` : "";
147-
if(warning[0] === warning[1])
148-
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)}${reasonPrefix}`;
149-
else
150-
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)} because of ${warning[1].readableIdentifier(requestShortener)}${reasonPrefix}`;
151-
});
152-
}
153-
for(const m of orderedModules) {
154-
usedModules.add(m);
155-
chunk.removeModule(m);
156-
}
120+
}
121+
// HACK: Sort configurations by length and start with the longest one
122+
// to get the biggers groups possible. Used modules are marked with usedModules
123+
// TODO: Allow to reuse existing configuration while trying to add dependencies.
124+
// This would improve performance. O(n^2) -> O(n)
125+
concatConfigurations.sort((a, b) => {
126+
return b.modules.size - a.modules.size;
127+
});
128+
const usedModules = new Set();
129+
for(const concatConfiguration of concatConfigurations) {
130+
if(usedModules.has(concatConfiguration.rootModule))
131+
continue;
132+
const orderedModules = new Set();
133+
this.addInOrder(concatConfiguration.rootModule, concatConfiguration.modules, orderedModules);
134+
const newModule = new ConcatenatedModule(concatConfiguration.rootModule, Array.from(orderedModules));
135+
concatConfiguration.sortWarnings();
136+
for(const warning of concatConfiguration.warnings) {
137+
newModule.optimizationBailout.push((requestShortener) => {
138+
const reason = getBailoutReason(warning[0], requestShortener);
139+
const reasonPrefix = reason ? `: ${reason}` : "";
140+
if(warning[0] === warning[1])
141+
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)}${reasonPrefix}`;
142+
else
143+
return `ModuleConcatenation: Cannot concat with ${warning[0].readableIdentifier(requestShortener)} because of ${warning[1].readableIdentifier(requestShortener)}${reasonPrefix}`;
144+
});
145+
}
146+
const chunks = concatConfiguration.rootModule.getChunks();
147+
for(const m of orderedModules) {
148+
usedModules.add(m);
149+
chunks.forEach(chunk => chunk.removeModule(m));
150+
}
151+
chunks.forEach(chunk => {
157152
chunk.addModule(newModule);
158-
compilation.modules.push(newModule);
159153
if(chunk.entryModule === concatConfiguration.rootModule)
160154
chunk.entryModule = newModule;
161-
newModule.reasons.forEach(reason => reason.dependency.module = newModule);
162-
newModule.dependencies.forEach(dep => {
163-
if(dep.module) {
164-
dep.module.reasons.forEach(reason => {
165-
if(reason.dependency === dep)
166-
reason.module = newModule;
167-
});
168-
}
169-
});
170-
}
171-
compilation.modules = compilation.modules.filter(m => !usedModules.has(m));
172-
});
155+
});
156+
compilation.modules.push(newModule);
157+
newModule.reasons.forEach(reason => reason.dependency.module = newModule);
158+
newModule.dependencies.forEach(dep => {
159+
if(dep.module) {
160+
dep.module.reasons.forEach(reason => {
161+
if(reason.dependency === dep)
162+
reason.module = newModule;
163+
});
164+
}
165+
});
166+
}
167+
compilation.modules = compilation.modules.filter(m => !usedModules.has(m));
173168
});
174169
});
175170
}
@@ -208,6 +203,13 @@ class ModuleConcatenationPlugin {
208203

209204
// Not possible to add?
210205
if(!possibleModules.has(module)) {
206+
failureCache.set(module, module); // cache failures for performance
207+
return module;
208+
}
209+
210+
// module must be in the same chunks
211+
if(!config.rootModule.hasEqualsChunks(module)) {
212+
failureCache.set(module, module); // cache failures for performance
211213
return module;
212214
}
213215

@@ -273,6 +275,16 @@ class ConcatConfiguration {
273275
this.warnings.set(module, problem);
274276
}
275277

278+
sortWarnings() {
279+
this.warnings = new Map(Array.from(this.warnings).sort((a, b) => {
280+
const ai = a[0].identifier();
281+
const bi = b[0].identifier();
282+
if(ai < bi) return -1;
283+
if(ai > bi) return 1;
284+
return 0;
285+
}));
286+
}
287+
276288
clone() {
277289
const clone = new ConcatConfiguration(this.rootModule);
278290
for(const module of this.modules)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./common2";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "common";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "common";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "common";

0 commit comments

Comments
 (0)