webpack 5.23.0 → 5.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webpack might be problematic. Click here for more details.

@@ -8,11 +8,8 @@
8
8
  const asyncLib = require("neo-async");
9
9
  const ChunkGraph = require("../ChunkGraph");
10
10
  const ModuleGraph = require("../ModuleGraph");
11
- const ModuleRestoreError = require("../ModuleRestoreError");
12
- const ModuleStoreError = require("../ModuleStoreError");
13
11
  const { STAGE_DEFAULT } = require("../OptimizationStages");
14
12
  const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
15
- const StackedMap = require("../util/StackedMap");
16
13
  const { compareModulesByIdentifier } = require("../util/comparators");
17
14
  const {
18
15
  intersectRuntime,
@@ -29,6 +26,20 @@ const ConcatenatedModule = require("./ConcatenatedModule");
29
26
  /** @typedef {import("../RequestShortener")} RequestShortener */
30
27
  /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
31
28
 
29
+ /**
30
+ * @typedef {Object} Statistics
31
+ * @property {number} cached
32
+ * @property {number} alreadyInConfig
33
+ * @property {number} invalidModule
34
+ * @property {number} incorrectChunks
35
+ * @property {number} incorrectDependency
36
+ * @property {number} incorrectModuleDependency
37
+ * @property {number} incorrectChunksOfImporter
38
+ * @property {number} incorrectRuntimeCondition
39
+ * @property {number} importerFailed
40
+ * @property {number} added
41
+ */
42
+
32
43
  const formatBailoutReason = msg => {
33
44
  return "ModuleConcatenation bailout: " + msg;
34
45
  };
@@ -45,414 +56,404 @@ class ModuleConcatenationPlugin {
45
56
  * @returns {void}
46
57
  */
47
58
  apply(compiler) {
48
- compiler.hooks.compilation.tap(
49
- "ModuleConcatenationPlugin",
50
- (compilation, { normalModuleFactory }) => {
51
- const moduleGraph = compilation.moduleGraph;
52
- const bailoutReasonMap = new Map();
53
- const cache = compilation.getCache("ModuleConcatenationPlugin");
54
-
55
- const setBailoutReason = (module, reason) => {
56
- setInnerBailoutReason(module, reason);
57
- moduleGraph
58
- .getOptimizationBailout(module)
59
- .push(
60
- typeof reason === "function"
61
- ? rs => formatBailoutReason(reason(rs))
62
- : formatBailoutReason(reason)
63
- );
64
- };
65
-
66
- const setInnerBailoutReason = (module, reason) => {
67
- bailoutReasonMap.set(module, reason);
68
- };
59
+ compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => {
60
+ const moduleGraph = compilation.moduleGraph;
61
+ const bailoutReasonMap = new Map();
62
+
63
+ const setBailoutReason = (module, reason) => {
64
+ setInnerBailoutReason(module, reason);
65
+ moduleGraph
66
+ .getOptimizationBailout(module)
67
+ .push(
68
+ typeof reason === "function"
69
+ ? rs => formatBailoutReason(reason(rs))
70
+ : formatBailoutReason(reason)
71
+ );
72
+ };
69
73
 
70
- const getInnerBailoutReason = (module, requestShortener) => {
71
- const reason = bailoutReasonMap.get(module);
72
- if (typeof reason === "function") return reason(requestShortener);
73
- return reason;
74
- };
74
+ const setInnerBailoutReason = (module, reason) => {
75
+ bailoutReasonMap.set(module, reason);
76
+ };
75
77
 
76
- const formatBailoutWarning = (module, problem) => requestShortener => {
77
- if (typeof problem === "function") {
78
- return formatBailoutReason(
79
- `Cannot concat with ${module.readableIdentifier(
80
- requestShortener
81
- )}: ${problem(requestShortener)}`
82
- );
83
- }
84
- const reason = getInnerBailoutReason(module, requestShortener);
85
- const reasonWithPrefix = reason ? `: ${reason}` : "";
86
- if (module === problem) {
87
- return formatBailoutReason(
88
- `Cannot concat with ${module.readableIdentifier(
89
- requestShortener
90
- )}${reasonWithPrefix}`
91
- );
92
- } else {
93
- return formatBailoutReason(
94
- `Cannot concat with ${module.readableIdentifier(
95
- requestShortener
96
- )} because of ${problem.readableIdentifier(
97
- requestShortener
98
- )}${reasonWithPrefix}`
99
- );
100
- }
101
- };
78
+ const getInnerBailoutReason = (module, requestShortener) => {
79
+ const reason = bailoutReasonMap.get(module);
80
+ if (typeof reason === "function") return reason(requestShortener);
81
+ return reason;
82
+ };
102
83
 
103
- compilation.hooks.optimizeChunkModules.tapAsync(
104
- {
105
- name: "ModuleConcatenationPlugin",
106
- stage: STAGE_DEFAULT
107
- },
108
- (allChunks, modules, callback) => {
109
- const logger = compilation.getLogger("ModuleConcatenationPlugin");
110
- const { chunkGraph, moduleGraph } = compilation;
111
- const relevantModules = [];
112
- const possibleInners = new Set();
113
- const context = {
114
- chunkGraph,
115
- moduleGraph
116
- };
117
- logger.time("select relevant modules");
118
- for (const module of modules) {
119
- let canBeRoot = true;
120
- let canBeInner = true;
121
-
122
- const bailoutReason = module.getConcatenationBailoutReason(
123
- context
124
- );
125
- if (bailoutReason) {
126
- setBailoutReason(module, bailoutReason);
127
- continue;
128
- }
84
+ const formatBailoutWarning = (module, problem) => requestShortener => {
85
+ if (typeof problem === "function") {
86
+ return formatBailoutReason(
87
+ `Cannot concat with ${module.readableIdentifier(
88
+ requestShortener
89
+ )}: ${problem(requestShortener)}`
90
+ );
91
+ }
92
+ const reason = getInnerBailoutReason(module, requestShortener);
93
+ const reasonWithPrefix = reason ? `: ${reason}` : "";
94
+ if (module === problem) {
95
+ return formatBailoutReason(
96
+ `Cannot concat with ${module.readableIdentifier(
97
+ requestShortener
98
+ )}${reasonWithPrefix}`
99
+ );
100
+ } else {
101
+ return formatBailoutReason(
102
+ `Cannot concat with ${module.readableIdentifier(
103
+ requestShortener
104
+ )} because of ${problem.readableIdentifier(
105
+ requestShortener
106
+ )}${reasonWithPrefix}`
107
+ );
108
+ }
109
+ };
129
110
 
130
- // Must not be an async module
131
- if (moduleGraph.isAsync(module)) {
132
- setBailoutReason(module, `Module is async`);
133
- continue;
134
- }
111
+ compilation.hooks.optimizeChunkModules.tapAsync(
112
+ {
113
+ name: "ModuleConcatenationPlugin",
114
+ stage: STAGE_DEFAULT
115
+ },
116
+ (allChunks, modules, callback) => {
117
+ const logger = compilation.getLogger(
118
+ "webpack.ModuleConcatenationPlugin"
119
+ );
120
+ const { chunkGraph, moduleGraph } = compilation;
121
+ const relevantModules = [];
122
+ const possibleInners = new Set();
123
+ const context = {
124
+ chunkGraph,
125
+ moduleGraph
126
+ };
127
+ logger.time("select relevant modules");
128
+ for (const module of modules) {
129
+ let canBeRoot = true;
130
+ let canBeInner = true;
131
+
132
+ const bailoutReason = module.getConcatenationBailoutReason(context);
133
+ if (bailoutReason) {
134
+ setBailoutReason(module, bailoutReason);
135
+ continue;
136
+ }
135
137
 
136
- // Must be in strict mode
137
- if (!module.buildInfo.strict) {
138
- setBailoutReason(module, `Module is not in strict mode`);
139
- continue;
140
- }
138
+ // Must not be an async module
139
+ if (moduleGraph.isAsync(module)) {
140
+ setBailoutReason(module, `Module is async`);
141
+ continue;
142
+ }
141
143
 
142
- // Module must be in any chunk (we don't want to do useless work)
143
- if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
144
- setBailoutReason(module, "Module is not in any chunk");
145
- continue;
146
- }
144
+ // Must be in strict mode
145
+ if (!module.buildInfo.strict) {
146
+ setBailoutReason(module, `Module is not in strict mode`);
147
+ continue;
148
+ }
147
149
 
148
- // Exports must be known (and not dynamic)
149
- const exportsInfo = moduleGraph.getExportsInfo(module);
150
- const relevantExports = exportsInfo.getRelevantExports(undefined);
151
- const unknownReexports = relevantExports.filter(exportInfo => {
152
- return (
153
- exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
154
- );
155
- });
156
- if (unknownReexports.length > 0) {
157
- setBailoutReason(
158
- module,
159
- `Reexports in this module do not have a static target (${Array.from(
160
- unknownReexports,
161
- exportInfo =>
162
- `${
163
- exportInfo.name || "other exports"
164
- }: ${exportInfo.getUsedInfo()}`
165
- ).join(", ")})`
166
- );
167
- continue;
168
- }
150
+ // Module must be in any chunk (we don't want to do useless work)
151
+ if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
152
+ setBailoutReason(module, "Module is not in any chunk");
153
+ continue;
154
+ }
169
155
 
170
- // Root modules must have a static list of exports
171
- const unknownProvidedExports = relevantExports.filter(
172
- exportInfo => {
173
- return exportInfo.provided !== true;
174
- }
156
+ // Exports must be known (and not dynamic)
157
+ const exportsInfo = moduleGraph.getExportsInfo(module);
158
+ const relevantExports = exportsInfo.getRelevantExports(undefined);
159
+ const unknownReexports = relevantExports.filter(exportInfo => {
160
+ return (
161
+ exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
175
162
  );
176
- if (unknownProvidedExports.length > 0) {
177
- setBailoutReason(
178
- module,
179
- `List of module exports is dynamic (${Array.from(
180
- unknownProvidedExports,
181
- exportInfo =>
182
- `${
183
- exportInfo.name || "other exports"
184
- }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
185
- ).join(", ")})`
186
- );
187
- canBeRoot = false;
188
- }
163
+ });
164
+ if (unknownReexports.length > 0) {
165
+ setBailoutReason(
166
+ module,
167
+ `Reexports in this module do not have a static target (${Array.from(
168
+ unknownReexports,
169
+ exportInfo =>
170
+ `${
171
+ exportInfo.name || "other exports"
172
+ }: ${exportInfo.getUsedInfo()}`
173
+ ).join(", ")})`
174
+ );
175
+ continue;
176
+ }
189
177
 
190
- // Module must not be an entry point
191
- if (chunkGraph.isEntryModule(module)) {
192
- setInnerBailoutReason(module, "Module is an entry point");
193
- canBeInner = false;
178
+ // Root modules must have a static list of exports
179
+ const unknownProvidedExports = relevantExports.filter(
180
+ exportInfo => {
181
+ return exportInfo.provided !== true;
194
182
  }
183
+ );
184
+ if (unknownProvidedExports.length > 0) {
185
+ setBailoutReason(
186
+ module,
187
+ `List of module exports is dynamic (${Array.from(
188
+ unknownProvidedExports,
189
+ exportInfo =>
190
+ `${
191
+ exportInfo.name || "other exports"
192
+ }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
193
+ ).join(", ")})`
194
+ );
195
+ canBeRoot = false;
196
+ }
195
197
 
196
- if (canBeRoot) relevantModules.push(module);
197
- if (canBeInner) possibleInners.add(module);
198
+ // Module must not be an entry point
199
+ if (chunkGraph.isEntryModule(module)) {
200
+ setInnerBailoutReason(module, "Module is an entry point");
201
+ canBeInner = false;
202
+ }
203
+
204
+ if (canBeRoot) relevantModules.push(module);
205
+ if (canBeInner) possibleInners.add(module);
206
+ }
207
+ logger.timeEnd("select relevant modules");
208
+ logger.debug(
209
+ `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
210
+ );
211
+ // sort by depth
212
+ // modules with lower depth are more likely suited as roots
213
+ // this improves performance, because modules already selected as inner are skipped
214
+ logger.time("sort relevant modules");
215
+ relevantModules.sort((a, b) => {
216
+ return moduleGraph.getDepth(a) - moduleGraph.getDepth(b);
217
+ });
218
+ logger.timeEnd("sort relevant modules");
219
+
220
+ /** @type {Statistics} */
221
+ const stats = {
222
+ cached: 0,
223
+ alreadyInConfig: 0,
224
+ invalidModule: 0,
225
+ incorrectChunks: 0,
226
+ incorrectDependency: 0,
227
+ incorrectModuleDependency: 0,
228
+ incorrectChunksOfImporter: 0,
229
+ incorrectRuntimeCondition: 0,
230
+ importerFailed: 0,
231
+ added: 0
232
+ };
233
+ let statsCandidates = 0;
234
+ let statsSizeSum = 0;
235
+ let statsEmptyConfigurations = 0;
236
+
237
+ logger.time("find modules to concatenate");
238
+ const concatConfigurations = [];
239
+ const usedAsInner = new Set();
240
+ for (const currentRoot of relevantModules) {
241
+ // when used by another configuration as inner:
242
+ // the other configuration is better and we can skip this one
243
+ // TODO reconsider that when it's only used in a different runtime
244
+ if (usedAsInner.has(currentRoot)) continue;
245
+
246
+ let chunkRuntime = undefined;
247
+ for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
248
+ chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
198
249
  }
199
- logger.timeEnd("select relevant modules");
200
- logger.debug(
201
- `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
250
+ const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
251
+ const filteredRuntime = filterRuntime(chunkRuntime, r =>
252
+ exportsInfo.isModuleUsed(r)
253
+ );
254
+ const activeRuntime =
255
+ filteredRuntime === true
256
+ ? chunkRuntime
257
+ : filteredRuntime === false
258
+ ? undefined
259
+ : filteredRuntime;
260
+
261
+ // create a configuration with the root
262
+ const currentConfiguration = new ConcatConfiguration(
263
+ currentRoot,
264
+ activeRuntime
202
265
  );
203
- // sort by depth
204
- // modules with lower depth are more likely suited as roots
205
- // this improves performance, because modules already selected as inner are skipped
206
- logger.time("sort relevant modules");
207
- relevantModules.sort((a, b) => {
208
- return moduleGraph.getDepth(a) - moduleGraph.getDepth(b);
209
- });
210
- logger.timeEnd("sort relevant modules");
211
-
212
- logger.time("find modules to concatenate");
213
- const concatConfigurations = [];
214
- const usedAsInner = new Set();
215
- for (const currentRoot of relevantModules) {
216
- // when used by another configuration as inner:
217
- // the other configuration is better and we can skip this one
218
- if (usedAsInner.has(currentRoot)) continue;
219
-
220
- let chunkRuntime = undefined;
221
- for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
222
- chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
223
- }
224
- const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
225
- const filteredRuntime = filterRuntime(chunkRuntime, r =>
226
- exportsInfo.isModuleUsed(r)
227
- );
228
- const activeRuntime =
229
- filteredRuntime === true
230
- ? chunkRuntime
231
- : filteredRuntime === false
232
- ? undefined
233
- : filteredRuntime;
234
-
235
- // create a configuration with the root
236
- const currentConfiguration = new ConcatConfiguration(
237
- currentRoot,
238
- activeRuntime
239
- );
240
266
 
241
- // cache failures to add modules
242
- const failureCache = new Map();
267
+ // cache failures to add modules
268
+ const failureCache = new Map();
243
269
 
244
- // potential optional import candidates
245
- /** @type {Set<Module>} */
246
- const candidates = new Set();
270
+ // potential optional import candidates
271
+ /** @type {Set<Module>} */
272
+ const candidates = new Set();
247
273
 
248
- // try to add all imports
249
- for (const imp of this._getImports(
250
- compilation,
251
- currentRoot,
252
- activeRuntime
253
- )) {
254
- candidates.add(imp);
255
- }
274
+ // try to add all imports
275
+ for (const imp of this._getImports(
276
+ compilation,
277
+ currentRoot,
278
+ activeRuntime
279
+ )) {
280
+ candidates.add(imp);
281
+ }
256
282
 
257
- for (const imp of candidates) {
258
- // _tryToAdd modifies the config even if it fails
259
- // so make sure to only accept changes when it succeed
260
- const backup = currentConfiguration.snapshot();
261
- const impCandidates = new Set();
262
- const problem = this._tryToAdd(
263
- compilation,
264
- currentConfiguration,
265
- imp,
266
- chunkRuntime,
267
- activeRuntime,
268
- possibleInners,
269
- impCandidates,
270
- failureCache,
271
- chunkGraph
272
- );
273
- if (problem) {
274
- failureCache.set(imp, problem);
275
- currentConfiguration.addWarning(imp, problem);
276
-
277
- // roll back
278
- currentConfiguration.rollback(backup);
279
- } else {
280
- for (const c of impCandidates) {
281
- candidates.add(c);
282
- }
283
- }
284
- }
285
- if (!currentConfiguration.isEmpty()) {
286
- concatConfigurations.push(currentConfiguration);
287
- for (const module of currentConfiguration.getModules()) {
288
- if (module !== currentConfiguration.rootModule) {
289
- usedAsInner.add(module);
290
- }
291
- }
283
+ for (const imp of candidates) {
284
+ const impCandidates = new Set();
285
+ const problem = this._tryToAdd(
286
+ compilation,
287
+ currentConfiguration,
288
+ imp,
289
+ chunkRuntime,
290
+ activeRuntime,
291
+ possibleInners,
292
+ impCandidates,
293
+ failureCache,
294
+ chunkGraph,
295
+ true,
296
+ stats
297
+ );
298
+ if (problem) {
299
+ failureCache.set(imp, problem);
300
+ currentConfiguration.addWarning(imp, problem);
292
301
  } else {
293
- const optimizationBailouts = moduleGraph.getOptimizationBailout(
294
- currentRoot
295
- );
296
- for (const warning of currentConfiguration.getWarningsSorted()) {
297
- optimizationBailouts.push(
298
- formatBailoutWarning(warning[0], warning[1])
299
- );
302
+ for (const c of impCandidates) {
303
+ candidates.add(c);
300
304
  }
301
305
  }
302
306
  }
303
- logger.timeEnd("find modules to concatenate");
304
- logger.debug(
305
- `${concatConfigurations.length} concat configurations`
306
- );
307
- // HACK: Sort configurations by length and start with the longest one
308
- // to get the biggest groups possible. Used modules are marked with usedModules
309
- // TODO: Allow to reuse existing configuration while trying to add dependencies.
310
- // This would improve performance. O(n^2) -> O(n)
311
- logger.time(`sort concat configurations`);
312
- concatConfigurations.sort((a, b) => {
313
- return b.modules.size - a.modules.size;
314
- });
315
- logger.timeEnd(`sort concat configurations`);
316
- const usedModules = new Set();
317
-
318
- logger.time("create concatenated modules");
319
- asyncLib.each(
320
- concatConfigurations,
321
- (concatConfiguration, callback) => {
322
- const rootModule = concatConfiguration.rootModule;
323
-
324
- // Avoid overlapping configurations
325
- // TODO: remove this when todo above is fixed
326
- if (usedModules.has(rootModule)) return callback();
327
- const modules = concatConfiguration.getModules();
328
- for (const m of modules) {
329
- usedModules.add(m);
307
+ statsCandidates += candidates.size;
308
+ if (!currentConfiguration.isEmpty()) {
309
+ const modules = currentConfiguration.getModules();
310
+ statsSizeSum += modules.size;
311
+ concatConfigurations.push(currentConfiguration);
312
+ for (const module of modules) {
313
+ if (module !== currentConfiguration.rootModule) {
314
+ usedAsInner.add(module);
330
315
  }
331
-
332
- // Create a new ConcatenatedModule
333
- let newModule = ConcatenatedModule.create(
334
- rootModule,
335
- modules,
336
- concatConfiguration.runtime,
337
- compiler.root
316
+ }
317
+ } else {
318
+ statsEmptyConfigurations++;
319
+ const optimizationBailouts = moduleGraph.getOptimizationBailout(
320
+ currentRoot
321
+ );
322
+ for (const warning of currentConfiguration.getWarningsSorted()) {
323
+ optimizationBailouts.push(
324
+ formatBailoutWarning(warning[0], warning[1])
338
325
  );
326
+ }
327
+ }
328
+ }
329
+ logger.timeEnd("find modules to concatenate");
330
+ logger.debug(
331
+ `${
332
+ concatConfigurations.length
333
+ } successful concat configurations (avg size: ${
334
+ statsSizeSum / concatConfigurations.length
335
+ }), ${statsEmptyConfigurations} bailed out completely`
336
+ );
337
+ logger.debug(
338
+ `${statsCandidates} candidates were considered for adding (${stats.cached} cached failure, ${stats.alreadyInConfig} already in config, ${stats.invalidModule} invalid module, ${stats.incorrectChunks} incorrect chunks, ${stats.incorrectDependency} incorrect dependency, ${stats.incorrectChunksOfImporter} incorrect chunks of importer, ${stats.incorrectModuleDependency} incorrect module dependency, ${stats.incorrectRuntimeCondition} incorrect runtime condition, ${stats.importerFailed} importer failed, ${stats.added} added)`
339
+ );
340
+ // HACK: Sort configurations by length and start with the longest one
341
+ // to get the biggest groups possible. Used modules are marked with usedModules
342
+ // TODO: Allow to reuse existing configuration while trying to add dependencies.
343
+ // This would improve performance. O(n^2) -> O(n)
344
+ logger.time(`sort concat configurations`);
345
+ concatConfigurations.sort((a, b) => {
346
+ return b.modules.size - a.modules.size;
347
+ });
348
+ logger.timeEnd(`sort concat configurations`);
349
+ const usedModules = new Set();
350
+
351
+ logger.time("create concatenated modules");
352
+ asyncLib.each(
353
+ concatConfigurations,
354
+ (concatConfiguration, callback) => {
355
+ const rootModule = concatConfiguration.rootModule;
356
+
357
+ // Avoid overlapping configurations
358
+ // TODO: remove this when todo above is fixed
359
+ if (usedModules.has(rootModule)) return callback();
360
+ const modules = concatConfiguration.getModules();
361
+ for (const m of modules) {
362
+ usedModules.add(m);
363
+ }
339
364
 
340
- const cacheItem = cache.getItemCache(
341
- newModule.identifier(),
342
- null
343
- );
365
+ // Create a new ConcatenatedModule
366
+ let newModule = ConcatenatedModule.create(
367
+ rootModule,
368
+ modules,
369
+ concatConfiguration.runtime,
370
+ compiler.root
371
+ );
344
372
 
345
- const restore = () => {
346
- cacheItem.get((err, cacheModule) => {
373
+ const build = () => {
374
+ newModule.build(
375
+ compiler.options,
376
+ compilation,
377
+ null,
378
+ null,
379
+ err => {
347
380
  if (err) {
348
- return callback(new ModuleRestoreError(newModule, err));
349
- }
350
-
351
- if (cacheModule) {
352
- cacheModule.updateCacheModule(newModule);
353
- newModule = cacheModule;
354
- }
355
-
356
- build();
357
- });
358
- };
359
-
360
- const build = () => {
361
- newModule.build(
362
- compiler.options,
363
- compilation,
364
- null,
365
- null,
366
- err => {
367
- if (err) {
368
- if (!err.module) {
369
- err.module = newModule;
370
- }
371
- return callback(err);
381
+ if (!err.module) {
382
+ err.module = newModule;
372
383
  }
373
- integrateAndStore();
384
+ return callback(err);
374
385
  }
375
- );
376
- };
386
+ integrate();
387
+ }
388
+ );
389
+ };
377
390
 
378
- const integrateAndStore = () => {
379
- ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
380
- ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
391
+ const integrate = () => {
392
+ ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
393
+ ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
381
394
 
382
- for (const warning of concatConfiguration.getWarningsSorted()) {
383
- moduleGraph
384
- .getOptimizationBailout(newModule)
385
- .push(formatBailoutWarning(warning[0], warning[1]));
395
+ for (const warning of concatConfiguration.getWarningsSorted()) {
396
+ moduleGraph
397
+ .getOptimizationBailout(newModule)
398
+ .push(formatBailoutWarning(warning[0], warning[1]));
399
+ }
400
+ moduleGraph.cloneModuleAttributes(rootModule, newModule);
401
+ for (const m of modules) {
402
+ // add to builtModules when one of the included modules was built
403
+ if (compilation.builtModules.has(m)) {
404
+ compilation.builtModules.add(newModule);
386
405
  }
387
- moduleGraph.cloneModuleAttributes(rootModule, newModule);
388
- for (const m of modules) {
389
- // add to builtModules when one of the included modules was built
390
- if (compilation.builtModules.has(m)) {
391
- compilation.builtModules.add(newModule);
392
- }
393
- if (m !== rootModule) {
394
- // attach external references to the concatenated module too
395
- moduleGraph.copyOutgoingModuleConnections(
396
- m,
397
- newModule,
398
- c => {
399
- return (
400
- c.originModule === m &&
401
- !(
402
- c.dependency instanceof HarmonyImportDependency &&
403
- modules.has(c.module)
404
- )
405
- );
406
- }
407
- );
408
- // remove module from chunk
409
- for (const chunk of chunkGraph.getModuleChunksIterable(
410
- rootModule
411
- )) {
412
- chunkGraph.disconnectChunkAndModule(chunk, m);
406
+ if (m !== rootModule) {
407
+ // attach external references to the concatenated module too
408
+ moduleGraph.copyOutgoingModuleConnections(
409
+ m,
410
+ newModule,
411
+ c => {
412
+ return (
413
+ c.originModule === m &&
414
+ !(
415
+ c.dependency instanceof HarmonyImportDependency &&
416
+ modules.has(c.module)
417
+ )
418
+ );
413
419
  }
420
+ );
421
+ // remove module from chunk
422
+ for (const chunk of chunkGraph.getModuleChunksIterable(
423
+ rootModule
424
+ )) {
425
+ chunkGraph.disconnectChunkAndModule(chunk, m);
414
426
  }
415
427
  }
416
- compilation.modules.delete(rootModule);
417
- // remove module from chunk
418
- chunkGraph.replaceModule(rootModule, newModule);
419
- // replace module references with the concatenated module
420
- moduleGraph.moveModuleConnections(
421
- rootModule,
422
- newModule,
423
- c => {
424
- const otherModule =
425
- c.module === rootModule ? c.originModule : c.module;
426
- const innerConnection =
427
- c.dependency instanceof HarmonyImportDependency &&
428
- modules.has(otherModule);
429
- return !innerConnection;
430
- }
431
- );
432
- // add concatenated module to the compilation
433
- compilation.modules.add(newModule);
434
-
435
- // TODO check if module needs build to avoid caching it without change
436
- cacheItem.store(newModule, err => {
437
- if (err) {
438
- return callback(new ModuleStoreError(newModule, err));
439
- }
440
-
441
- callback();
442
- });
443
- };
444
-
445
- restore();
446
- },
447
- err => {
448
- logger.timeEnd("create concatenated modules");
449
- process.nextTick(() => callback(err));
450
- }
451
- );
452
- }
453
- );
454
- }
455
- );
428
+ }
429
+ compilation.modules.delete(rootModule);
430
+ // remove module from chunk
431
+ chunkGraph.replaceModule(rootModule, newModule);
432
+ // replace module references with the concatenated module
433
+ moduleGraph.moveModuleConnections(rootModule, newModule, c => {
434
+ const otherModule =
435
+ c.module === rootModule ? c.originModule : c.module;
436
+ const innerConnection =
437
+ c.dependency instanceof HarmonyImportDependency &&
438
+ modules.has(otherModule);
439
+ return !innerConnection;
440
+ });
441
+ // add concatenated module to the compilation
442
+ compilation.modules.add(newModule);
443
+
444
+ callback();
445
+ };
446
+
447
+ build();
448
+ },
449
+ err => {
450
+ logger.timeEnd("create concatenated modules");
451
+ process.nextTick(() => callback(err));
452
+ }
453
+ );
454
+ }
455
+ );
456
+ });
456
457
  }
457
458
 
458
459
  /**
@@ -505,6 +506,8 @@ class ModuleConcatenationPlugin {
505
506
  * @param {Set<Module>} candidates list of potential candidates (will be added to)
506
507
  * @param {Map<Module, Module | function(RequestShortener): string>} failureCache cache for problematic modules to be more performant
507
508
  * @param {ChunkGraph} chunkGraph the chunk graph
509
+ * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails
510
+ * @param {Statistics} statistics gathering metrics
508
511
  * @returns {Module | function(RequestShortener): string} the problematic module
509
512
  */
510
513
  _tryToAdd(
@@ -516,20 +519,25 @@ class ModuleConcatenationPlugin {
516
519
  possibleModules,
517
520
  candidates,
518
521
  failureCache,
519
- chunkGraph
522
+ chunkGraph,
523
+ avoidMutateOnFailure,
524
+ statistics
520
525
  ) {
521
526
  const cacheEntry = failureCache.get(module);
522
527
  if (cacheEntry) {
528
+ statistics.cached++;
523
529
  return cacheEntry;
524
530
  }
525
531
 
526
532
  // Already added?
527
533
  if (config.has(module)) {
534
+ statistics.alreadyInConfig++;
528
535
  return null;
529
536
  }
530
537
 
531
538
  // Not possible to add?
532
539
  if (!possibleModules.has(module)) {
540
+ statistics.invalidModule++;
533
541
  failureCache.set(module, module); // cache failures for performance
534
542
  return module;
535
543
  }
@@ -537,137 +545,106 @@ class ModuleConcatenationPlugin {
537
545
  // Module must be in the correct chunks
538
546
  const missingChunks = Array.from(
539
547
  chunkGraph.getModuleChunksIterable(config.rootModule)
540
- )
541
- .filter(chunk => !chunkGraph.isModuleInChunk(module, chunk))
542
- .map(chunk => chunk.name || "unnamed chunk(s)");
548
+ ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk));
543
549
  if (missingChunks.length > 0) {
544
- const missingChunksList = Array.from(new Set(missingChunks)).sort();
545
- const chunks = Array.from(
546
- new Set(
547
- Array.from(chunkGraph.getModuleChunksIterable(module)).map(
548
- chunk => chunk.name || "unnamed chunk(s)"
550
+ const problem = requestShortener => {
551
+ const missingChunksList = Array.from(
552
+ new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)"))
553
+ ).sort();
554
+ const chunks = Array.from(
555
+ new Set(
556
+ Array.from(chunkGraph.getModuleChunksIterable(module)).map(
557
+ chunk => chunk.name || "unnamed chunk(s)"
558
+ )
549
559
  )
550
- )
551
- ).sort();
552
- const problem = requestShortener =>
553
- `Module ${module.readableIdentifier(
560
+ ).sort();
561
+ return `Module ${module.readableIdentifier(
554
562
  requestShortener
555
563
  )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
556
564
  ", "
557
565
  )}, module is in chunk(s) ${chunks.join(", ")})`;
566
+ };
567
+ statistics.incorrectChunks++;
558
568
  failureCache.set(module, problem); // cache failures for performance
559
569
  return problem;
560
570
  }
561
571
 
562
- // Add the module
563
- config.add(module);
564
-
565
572
  const moduleGraph = compilation.moduleGraph;
566
573
 
567
- const incomingConnections = Array.from(
568
- moduleGraph.getIncomingConnections(module)
569
- ).filter(connection => {
570
- // We are not interested in inactive connections
571
- if (!connection.isActive(runtime)) return false;
572
-
573
- // Include, but do not analyse further, connections from non-modules
574
- if (!connection.originModule) return true;
575
-
576
- // Ignore connection from orphan modules
577
- if (chunkGraph.getNumberOfModuleChunks(connection.originModule) === 0)
578
- return false;
574
+ const incomingConnections = moduleGraph.getIncomingConnectionsByOriginModule(
575
+ module
576
+ );
579
577
 
580
- // We don't care for connections from other runtimes
581
- let originRuntime = undefined;
582
- for (const r of chunkGraph.getModuleRuntimes(connection.originModule)) {
583
- originRuntime = mergeRuntimeOwned(originRuntime, r);
578
+ const incomingConnectionsFromNonModules =
579
+ incomingConnections.get(null) || incomingConnections.get(undefined);
580
+ if (incomingConnectionsFromNonModules) {
581
+ const activeNonModulesConnections = incomingConnectionsFromNonModules.filter(
582
+ connection => {
583
+ // We are not interested in inactive connections
584
+ // or connections without dependency
585
+ return connection.isActive(runtime) || connection.dependency;
586
+ }
587
+ );
588
+ if (activeNonModulesConnections.length > 0) {
589
+ const problem = requestShortener => {
590
+ const importingExplanations = new Set(
591
+ activeNonModulesConnections.map(c => c.explanation).filter(Boolean)
592
+ );
593
+ const explanations = Array.from(importingExplanations).sort();
594
+ return `Module ${module.readableIdentifier(
595
+ requestShortener
596
+ )} is referenced ${
597
+ explanations.length > 0
598
+ ? `by: ${explanations.join(", ")}`
599
+ : "in an unsupported way"
600
+ }`;
601
+ };
602
+ statistics.incorrectDependency++;
603
+ failureCache.set(module, problem); // cache failures for performance
604
+ return problem;
584
605
  }
606
+ }
585
607
 
586
- return intersectRuntime(runtime, originRuntime);
587
- });
608
+ /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
609
+ const incomingConnectionsFromModules = new Map();
610
+ for (const [originModule, connections] of incomingConnections) {
611
+ if (originModule) {
612
+ // Ignore connection from orphan modules
613
+ if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;
614
+
615
+ // We don't care for connections from other runtimes
616
+ let originRuntime = undefined;
617
+ for (const r of chunkGraph.getModuleRuntimes(originModule)) {
618
+ originRuntime = mergeRuntimeOwned(originRuntime, r);
619
+ }
588
620
 
589
- const nonHarmonyConnections = incomingConnections.filter(
590
- connection =>
591
- !connection.originModule ||
592
- !connection.dependency ||
593
- !(connection.dependency instanceof HarmonyImportDependency)
594
- );
595
- if (nonHarmonyConnections.length > 0) {
596
- const problem = requestShortener => {
597
- const importingModules = new Set(
598
- nonHarmonyConnections.map(c => c.originModule).filter(Boolean)
599
- );
600
- const importingExplanations = new Set(
601
- nonHarmonyConnections.map(c => c.explanation).filter(Boolean)
602
- );
603
- const importingModuleTypes = new Map(
604
- Array.from(importingModules).map(
605
- m =>
606
- /** @type {[Module, Set<string>]} */ ([
607
- m,
608
- new Set(
609
- nonHarmonyConnections
610
- .filter(c => c.originModule === m)
611
- .map(c => c.dependency.type)
612
- .sort()
613
- )
614
- ])
615
- )
621
+ if (!intersectRuntime(runtime, originRuntime)) continue;
622
+
623
+ // We are not interested in inactive connections
624
+ const activeConnections = connections.filter(connection =>
625
+ connection.isActive(runtime)
616
626
  );
617
- const names = Array.from(importingModules)
618
- .map(
619
- m =>
620
- `${m.readableIdentifier(
621
- requestShortener
622
- )} (referenced with ${Array.from(
623
- importingModuleTypes.get(m)
624
- ).join(", ")})`
625
- )
626
- .sort();
627
- const explanations = Array.from(importingExplanations).sort();
628
- if (names.length > 0 && explanations.length === 0) {
629
- return `Module ${module.readableIdentifier(
630
- requestShortener
631
- )} is referenced from these modules with unsupported syntax: ${names.join(
632
- ", "
633
- )}`;
634
- } else if (names.length === 0 && explanations.length > 0) {
635
- return `Module ${module.readableIdentifier(
636
- requestShortener
637
- )} is referenced by: ${explanations.join(", ")}`;
638
- } else if (names.length > 0 && explanations.length > 0) {
639
- return `Module ${module.readableIdentifier(
640
- requestShortener
641
- )} is referenced from these modules with unsupported syntax: ${names.join(
642
- ", "
643
- )} and by: ${explanations.join(", ")}`;
644
- } else {
645
- return `Module ${module.readableIdentifier(
646
- requestShortener
647
- )} is referenced in a unsupported way`;
648
- }
649
- };
650
- failureCache.set(module, problem); // cache failures for performance
651
- return problem;
627
+ if (activeConnections.length > 0)
628
+ incomingConnectionsFromModules.set(originModule, activeConnections);
629
+ }
652
630
  }
653
631
 
632
+ const incomingModules = Array.from(incomingConnectionsFromModules.keys());
633
+
654
634
  // Module must be in the same chunks like the referencing module
655
- const otherChunkConnections = incomingConnections.filter(connection => {
635
+ const otherChunkModules = incomingModules.filter(originModule => {
656
636
  for (const chunk of chunkGraph.getModuleChunksIterable(
657
637
  config.rootModule
658
638
  )) {
659
- if (!chunkGraph.isModuleInChunk(connection.originModule, chunk)) {
639
+ if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
660
640
  return true;
661
641
  }
662
642
  }
663
643
  return false;
664
644
  });
665
- if (otherChunkConnections.length > 0) {
645
+ if (otherChunkModules.length > 0) {
666
646
  const problem = requestShortener => {
667
- const importingModules = new Set(
668
- otherChunkConnections.map(c => c.originModule)
669
- );
670
- const names = Array.from(importingModules)
647
+ const names = otherChunkModules
671
648
  .map(m => m.readableIdentifier(requestShortener))
672
649
  .sort();
673
650
  return `Module ${module.readableIdentifier(
@@ -676,41 +653,90 @@ class ModuleConcatenationPlugin {
676
653
  ", "
677
654
  )}`;
678
655
  };
656
+ statistics.incorrectChunksOfImporter++;
657
+ failureCache.set(module, problem); // cache failures for performance
658
+ return problem;
659
+ }
660
+
661
+ /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
662
+ const nonHarmonyConnections = new Map();
663
+ for (const [originModule, connections] of incomingConnectionsFromModules) {
664
+ const selected = connections.filter(
665
+ connection =>
666
+ !connection.dependency ||
667
+ !(connection.dependency instanceof HarmonyImportDependency)
668
+ );
669
+ if (selected.length > 0)
670
+ nonHarmonyConnections.set(originModule, connections);
671
+ }
672
+ if (nonHarmonyConnections.size > 0) {
673
+ const problem = requestShortener => {
674
+ const names = Array.from(nonHarmonyConnections)
675
+ .map(([originModule, connections]) => {
676
+ return `${originModule.readableIdentifier(
677
+ requestShortener
678
+ )} (referenced with ${Array.from(
679
+ new Set(
680
+ connections
681
+ .map(c => c.dependency && c.dependency.type)
682
+ .filter(Boolean)
683
+ )
684
+ )
685
+ .sort()
686
+ .join(", ")})`;
687
+ })
688
+ .sort();
689
+ return `Module ${module.readableIdentifier(
690
+ requestShortener
691
+ )} is referenced from these modules with unsupported syntax: ${names.join(
692
+ ", "
693
+ )}`;
694
+ };
695
+ statistics.incorrectModuleDependency++;
679
696
  failureCache.set(module, problem); // cache failures for performance
680
697
  return problem;
681
698
  }
682
699
 
683
700
  if (runtime !== undefined && typeof runtime !== "string") {
684
701
  // Module must be consistently referenced in the same runtimes
685
- /** @type {Map<Module, boolean | RuntimeSpec>} */
686
- const runtimeConditionMap = new Map();
687
- for (const connection of incomingConnections) {
688
- const runtimeCondition = filterRuntime(runtime, runtime => {
689
- return connection.isTargetActive(runtime);
690
- });
691
- if (runtimeCondition === false) continue;
692
- const old = runtimeConditionMap.get(connection.originModule) || false;
693
- if (old === true) continue;
694
- if (old !== false && runtimeCondition !== true) {
695
- runtimeConditionMap.set(
696
- connection.originModule,
697
- mergeRuntime(old, runtimeCondition)
698
- );
699
- } else {
700
- runtimeConditionMap.set(connection.originModule, runtimeCondition);
702
+ /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
703
+ const otherRuntimeConnections = [];
704
+ outer: for (const [
705
+ originModule,
706
+ connections
707
+ ] of incomingConnectionsFromModules) {
708
+ /** @type {false | RuntimeSpec} */
709
+ let currentRuntimeCondition = false;
710
+ for (const connection of connections) {
711
+ const runtimeCondition = filterRuntime(runtime, runtime => {
712
+ return connection.isTargetActive(runtime);
713
+ });
714
+ if (runtimeCondition === false) continue;
715
+ if (runtimeCondition === true) continue outer;
716
+ if (currentRuntimeCondition !== false) {
717
+ currentRuntimeCondition = mergeRuntime(
718
+ currentRuntimeCondition,
719
+ runtimeCondition
720
+ );
721
+ } else {
722
+ currentRuntimeCondition = runtimeCondition;
723
+ }
724
+ }
725
+ if (currentRuntimeCondition !== false) {
726
+ otherRuntimeConnections.push({
727
+ originModule,
728
+ runtimeCondition: currentRuntimeCondition
729
+ });
701
730
  }
702
731
  }
703
- const otherRuntimeConnections = Array.from(runtimeConditionMap).filter(
704
- ([, runtimeCondition]) => typeof runtimeCondition !== "boolean"
705
- );
706
732
  if (otherRuntimeConnections.length > 0) {
707
733
  const problem = requestShortener => {
708
734
  return `Module ${module.readableIdentifier(
709
735
  requestShortener
710
736
  )} is runtime-dependent referenced by these modules: ${Array.from(
711
737
  otherRuntimeConnections,
712
- ([module, runtimeCondition]) =>
713
- `${module.readableIdentifier(
738
+ ({ originModule, runtimeCondition }) =>
739
+ `${originModule.readableIdentifier(
714
740
  requestShortener
715
741
  )} (expected runtime ${runtimeToString(
716
742
  runtime
@@ -719,14 +745,21 @@ class ModuleConcatenationPlugin {
719
745
  )})`
720
746
  ).join(", ")}`;
721
747
  };
748
+ statistics.incorrectRuntimeCondition++;
722
749
  failureCache.set(module, problem); // cache failures for performance
723
750
  return problem;
724
751
  }
725
752
  }
726
753
 
727
- const incomingModules = Array.from(
728
- new Set(incomingConnections.map(c => c.originModule))
729
- ).sort(compareModulesByIdentifier);
754
+ let backup;
755
+ if (avoidMutateOnFailure) {
756
+ backup = config.snapshot();
757
+ }
758
+
759
+ // Add the module
760
+ config.add(module);
761
+
762
+ incomingModules.sort(compareModulesByIdentifier);
730
763
 
731
764
  // Every module which depends on the added module must be in the configuration too.
732
765
  for (const originModule of incomingModules) {
@@ -739,9 +772,13 @@ class ModuleConcatenationPlugin {
739
772
  possibleModules,
740
773
  candidates,
741
774
  failureCache,
742
- chunkGraph
775
+ chunkGraph,
776
+ false,
777
+ statistics
743
778
  );
744
779
  if (problem) {
780
+ if (backup !== undefined) config.rollback(backup);
781
+ statistics.importerFailed++;
745
782
  failureCache.set(module, problem); // cache failures for performance
746
783
  return problem;
747
784
  }
@@ -751,6 +788,7 @@ class ModuleConcatenationPlugin {
751
788
  for (const imp of this._getImports(compilation, module, runtime)) {
752
789
  candidates.add(imp);
753
790
  }
791
+ statistics.added++;
754
792
  return null;
755
793
  }
756
794
  }
@@ -763,15 +801,15 @@ class ConcatConfiguration {
763
801
  constructor(rootModule, runtime) {
764
802
  this.rootModule = rootModule;
765
803
  this.runtime = runtime;
766
- /** @type {StackedMap<Module, true>} */
767
- this.modules = new StackedMap();
768
- this.modules.set(rootModule, true);
769
- /** @type {StackedMap<Module, Module | function(RequestShortener): string>} */
770
- this.warnings = new StackedMap();
804
+ /** @type {Set<Module>} */
805
+ this.modules = new Set();
806
+ this.modules.add(rootModule);
807
+ /** @type {Map<Module, Module | function(RequestShortener): string>} */
808
+ this.warnings = new Map();
771
809
  }
772
810
 
773
811
  add(module) {
774
- this.modules.set(module, true);
812
+ this.modules.add(module);
775
813
  }
776
814
 
777
815
  has(module) {
@@ -788,7 +826,7 @@ class ConcatConfiguration {
788
826
 
789
827
  getWarningsSorted() {
790
828
  return new Map(
791
- this.warnings.asPairArray().sort((a, b) => {
829
+ Array.from(this.warnings).sort((a, b) => {
792
830
  const ai = a[0].identifier();
793
831
  const bi = b[0].identifier();
794
832
  if (ai < bi) return -1;
@@ -802,17 +840,22 @@ class ConcatConfiguration {
802
840
  * @returns {Set<Module>} modules as set
803
841
  */
804
842
  getModules() {
805
- return this.modules.asSet();
843
+ return this.modules;
806
844
  }
807
845
 
808
846
  snapshot() {
809
- const base = this.modules;
810
- this.modules = this.modules.createChild();
811
- return base;
847
+ return this.modules.size;
812
848
  }
813
849
 
814
850
  rollback(snapshot) {
815
- this.modules = snapshot;
851
+ const modules = this.modules;
852
+ for (const m of modules) {
853
+ if (snapshot === 0) {
854
+ modules.delete(m);
855
+ } else {
856
+ snapshot--;
857
+ }
858
+ }
816
859
  }
817
860
  }
818
861