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