UNPKG

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