UNPKG

26.6 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 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 };
73
74 const setInnerBailoutReason = (module, reason) => {
75 bailoutReasonMap.set(module, reason);
76 };
77
78 const getInnerBailoutReason = (module, requestShortener) => {
79 const reason = bailoutReasonMap.get(module);
80 if (typeof reason === "function") return reason(requestShortener);
81 return reason;
82 };
83
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 };
110
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 }
137
138 // Must not be an async module
139 if (moduleGraph.isAsync(module)) {
140 setBailoutReason(module, `Module is async`);
141 continue;
142 }
143
144 // Must be in strict mode
145 if (!module.buildInfo.strict) {
146 setBailoutReason(module, `Module is not in strict mode`);
147 continue;
148 }
149
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 }
155
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)
162 );
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 }
177
178 // Root modules must have a static list of exports
179 const unknownProvidedExports = relevantExports.filter(
180 exportInfo => {
181 return exportInfo.provided !== true;
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 }
197
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);
249 }
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
265 );
266
267 // cache failures to add modules
268 const failureCache = new Map();
269
270 // potential optional import candidates
271 /** @type {Set<Module>} */
272 const candidates = new Set();
273
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 }
282
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);
301 } else {
302 for (const c of impCandidates) {
303 candidates.add(c);
304 }
305 }
306 }
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);
315 }
316 }
317 } else {
318 statsEmptyConfigurations++;
319 const optimizationBailouts =
320 moduleGraph.getOptimizationBailout(currentRoot);
321 for (const warning of currentConfiguration.getWarningsSorted()) {
322 optimizationBailouts.push(
323 formatBailoutWarning(warning[0], warning[1])
324 );
325 }
326 }
327 }
328 logger.timeEnd("find modules to concatenate");
329 logger.debug(
330 `${
331 concatConfigurations.length
332 } successful concat configurations (avg size: ${
333 statsSizeSum / concatConfigurations.length
334 }), ${statsEmptyConfigurations} bailed out completely`
335 );
336 logger.debug(
337 `${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)`
338 );
339 // HACK: Sort configurations by length and start with the longest one
340 // to get the biggest groups possible. Used modules are marked with usedModules
341 // TODO: Allow to reuse existing configuration while trying to add dependencies.
342 // This would improve performance. O(n^2) -> O(n)
343 logger.time(`sort concat configurations`);
344 concatConfigurations.sort((a, b) => {
345 return b.modules.size - a.modules.size;
346 });
347 logger.timeEnd(`sort concat configurations`);
348 const usedModules = new Set();
349
350 logger.time("create concatenated modules");
351 asyncLib.each(
352 concatConfigurations,
353 (concatConfiguration, callback) => {
354 const rootModule = concatConfiguration.rootModule;
355
356 // Avoid overlapping configurations
357 // TODO: remove this when todo above is fixed
358 if (usedModules.has(rootModule)) return callback();
359 const modules = concatConfiguration.getModules();
360 for (const m of modules) {
361 usedModules.add(m);
362 }
363
364 // Create a new ConcatenatedModule
365 let newModule = ConcatenatedModule.create(
366 rootModule,
367 modules,
368 concatConfiguration.runtime,
369 compiler.root,
370 compilation.outputOptions.hashFunction
371 );
372
373 const build = () => {
374 newModule.build(
375 compiler.options,
376 compilation,
377 null,
378 null,
379 err => {
380 if (err) {
381 if (!err.module) {
382 err.module = newModule;
383 }
384 return callback(err);
385 }
386 integrate();
387 }
388 );
389 };
390
391 const integrate = () => {
392 ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
393 ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
394
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);
405 }
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 );
419 }
420 );
421 // remove module from chunk
422 for (const chunk of chunkGraph.getModuleChunksIterable(
423 rootModule
424 )) {
425 chunkGraph.disconnectChunkAndModule(chunk, m);
426 }
427 }
428 }
429 compilation.modules.delete(rootModule);
430 ChunkGraph.clearChunkGraphForModule(rootModule);
431 ModuleGraph.clearModuleGraphForModule(rootModule);
432
433 // remove module from chunk
434 chunkGraph.replaceModule(rootModule, newModule);
435 // replace module references with the concatenated module
436 moduleGraph.moveModuleConnections(rootModule, newModule, c => {
437 const otherModule =
438 c.module === rootModule ? c.originModule : c.module;
439 const innerConnection =
440 c.dependency instanceof HarmonyImportDependency &&
441 modules.has(otherModule);
442 return !innerConnection;
443 });
444 // add concatenated module to the compilation
445 compilation.modules.add(newModule);
446
447 callback();
448 };
449
450 build();
451 },
452 err => {
453 logger.timeEnd("create concatenated modules");
454 process.nextTick(callback.bind(null, err));
455 }
456 );
457 }
458 );
459 });
460 }
461
462 /**
463 * @param {Compilation} compilation the compilation
464 * @param {Module} module the module to be added
465 * @param {RuntimeSpec} runtime the runtime scope
466 * @returns {Set<Module>} the imported modules
467 */
468 _getImports(compilation, module, runtime) {
469 const moduleGraph = compilation.moduleGraph;
470 const set = new Set();
471 for (const dep of module.dependencies) {
472 // Get reference info only for harmony Dependencies
473 if (!(dep instanceof HarmonyImportDependency)) continue;
474
475 const connection = moduleGraph.getConnection(dep);
476 // Reference is valid and has a module
477 if (
478 !connection ||
479 !connection.module ||
480 !connection.isTargetActive(runtime)
481 ) {
482 continue;
483 }
484
485 const importedNames = compilation.getDependencyReferencedExports(
486 dep,
487 undefined
488 );
489
490 if (
491 importedNames.every(i =>
492 Array.isArray(i) ? i.length > 0 : i.name.length > 0
493 ) ||
494 Array.isArray(moduleGraph.getProvidedExports(module))
495 ) {
496 set.add(connection.module);
497 }
498 }
499 return set;
500 }
501
502 /**
503 * @param {Compilation} compilation webpack compilation
504 * @param {ConcatConfiguration} config concat configuration (will be modified when added)
505 * @param {Module} module the module to be added
506 * @param {RuntimeSpec} runtime the runtime scope of the generated code
507 * @param {RuntimeSpec} activeRuntime the runtime scope of the root module
508 * @param {Set<Module>} possibleModules modules that are candidates
509 * @param {Set<Module>} candidates list of potential candidates (will be added to)
510 * @param {Map<Module, Module | function(RequestShortener): string>} failureCache cache for problematic modules to be more performant
511 * @param {ChunkGraph} chunkGraph the chunk graph
512 * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails
513 * @param {Statistics} statistics gathering metrics
514 * @returns {Module | function(RequestShortener): string} the problematic module
515 */
516 _tryToAdd(
517 compilation,
518 config,
519 module,
520 runtime,
521 activeRuntime,
522 possibleModules,
523 candidates,
524 failureCache,
525 chunkGraph,
526 avoidMutateOnFailure,
527 statistics
528 ) {
529 const cacheEntry = failureCache.get(module);
530 if (cacheEntry) {
531 statistics.cached++;
532 return cacheEntry;
533 }
534
535 // Already added?
536 if (config.has(module)) {
537 statistics.alreadyInConfig++;
538 return null;
539 }
540
541 // Not possible to add?
542 if (!possibleModules.has(module)) {
543 statistics.invalidModule++;
544 failureCache.set(module, module); // cache failures for performance
545 return module;
546 }
547
548 // Module must be in the correct chunks
549 const missingChunks = Array.from(
550 chunkGraph.getModuleChunksIterable(config.rootModule)
551 ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk));
552 if (missingChunks.length > 0) {
553 const problem = requestShortener => {
554 const missingChunksList = Array.from(
555 new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)"))
556 ).sort();
557 const chunks = Array.from(
558 new Set(
559 Array.from(chunkGraph.getModuleChunksIterable(module)).map(
560 chunk => chunk.name || "unnamed chunk(s)"
561 )
562 )
563 ).sort();
564 return `Module ${module.readableIdentifier(
565 requestShortener
566 )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
567 ", "
568 )}, module is in chunk(s) ${chunks.join(", ")})`;
569 };
570 statistics.incorrectChunks++;
571 failureCache.set(module, problem); // cache failures for performance
572 return problem;
573 }
574
575 const moduleGraph = compilation.moduleGraph;
576
577 const incomingConnections =
578 moduleGraph.getIncomingConnectionsByOriginModule(module);
579
580 const incomingConnectionsFromNonModules =
581 incomingConnections.get(null) || incomingConnections.get(undefined);
582 if (incomingConnectionsFromNonModules) {
583 const activeNonModulesConnections =
584 incomingConnectionsFromNonModules.filter(connection => {
585 // We are not interested in inactive connections
586 // or connections without dependency
587 return connection.isActive(runtime) || connection.dependency;
588 });
589 if (activeNonModulesConnections.length > 0) {
590 const problem = requestShortener => {
591 const importingExplanations = new Set(
592 activeNonModulesConnections.map(c => c.explanation).filter(Boolean)
593 );
594 const explanations = Array.from(importingExplanations).sort();
595 return `Module ${module.readableIdentifier(
596 requestShortener
597 )} is referenced ${
598 explanations.length > 0
599 ? `by: ${explanations.join(", ")}`
600 : "in an unsupported way"
601 }`;
602 };
603 statistics.incorrectDependency++;
604 failureCache.set(module, problem); // cache failures for performance
605 return problem;
606 }
607 }
608
609 /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
610 const incomingConnectionsFromModules = new Map();
611 for (const [originModule, connections] of incomingConnections) {
612 if (originModule) {
613 // Ignore connection from orphan modules
614 if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;
615
616 // We don't care for connections from other runtimes
617 let originRuntime = undefined;
618 for (const r of chunkGraph.getModuleRuntimes(originModule)) {
619 originRuntime = mergeRuntimeOwned(originRuntime, r);
620 }
621
622 if (!intersectRuntime(runtime, originRuntime)) continue;
623
624 // We are not interested in inactive connections
625 const activeConnections = connections.filter(connection =>
626 connection.isActive(runtime)
627 );
628 if (activeConnections.length > 0)
629 incomingConnectionsFromModules.set(originModule, activeConnections);
630 }
631 }
632
633 const incomingModules = Array.from(incomingConnectionsFromModules.keys());
634
635 // Module must be in the same chunks like the referencing module
636 const otherChunkModules = incomingModules.filter(originModule => {
637 for (const chunk of chunkGraph.getModuleChunksIterable(
638 config.rootModule
639 )) {
640 if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
641 return true;
642 }
643 }
644 return false;
645 });
646 if (otherChunkModules.length > 0) {
647 const problem = requestShortener => {
648 const names = otherChunkModules
649 .map(m => m.readableIdentifier(requestShortener))
650 .sort();
651 return `Module ${module.readableIdentifier(
652 requestShortener
653 )} is referenced from different chunks by these modules: ${names.join(
654 ", "
655 )}`;
656 };
657 statistics.incorrectChunksOfImporter++;
658 failureCache.set(module, problem); // cache failures for performance
659 return problem;
660 }
661
662 /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
663 const nonHarmonyConnections = new Map();
664 for (const [originModule, connections] of incomingConnectionsFromModules) {
665 const selected = connections.filter(
666 connection =>
667 !connection.dependency ||
668 !(connection.dependency instanceof HarmonyImportDependency)
669 );
670 if (selected.length > 0)
671 nonHarmonyConnections.set(originModule, connections);
672 }
673 if (nonHarmonyConnections.size > 0) {
674 const problem = requestShortener => {
675 const names = Array.from(nonHarmonyConnections)
676 .map(([originModule, connections]) => {
677 return `${originModule.readableIdentifier(
678 requestShortener
679 )} (referenced with ${Array.from(
680 new Set(
681 connections
682 .map(c => c.dependency && c.dependency.type)
683 .filter(Boolean)
684 )
685 )
686 .sort()
687 .join(", ")})`;
688 })
689 .sort();
690 return `Module ${module.readableIdentifier(
691 requestShortener
692 )} is referenced from these modules with unsupported syntax: ${names.join(
693 ", "
694 )}`;
695 };
696 statistics.incorrectModuleDependency++;
697 failureCache.set(module, problem); // cache failures for performance
698 return problem;
699 }
700
701 if (runtime !== undefined && typeof runtime !== "string") {
702 // Module must be consistently referenced in the same runtimes
703 /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
704 const otherRuntimeConnections = [];
705 outer: for (const [
706 originModule,
707 connections
708 ] of incomingConnectionsFromModules) {
709 /** @type {false | RuntimeSpec} */
710 let currentRuntimeCondition = false;
711 for (const connection of connections) {
712 const runtimeCondition = filterRuntime(runtime, runtime => {
713 return connection.isTargetActive(runtime);
714 });
715 if (runtimeCondition === false) continue;
716 if (runtimeCondition === true) continue outer;
717 if (currentRuntimeCondition !== false) {
718 currentRuntimeCondition = mergeRuntime(
719 currentRuntimeCondition,
720 runtimeCondition
721 );
722 } else {
723 currentRuntimeCondition = runtimeCondition;
724 }
725 }
726 if (currentRuntimeCondition !== false) {
727 otherRuntimeConnections.push({
728 originModule,
729 runtimeCondition: currentRuntimeCondition
730 });
731 }
732 }
733 if (otherRuntimeConnections.length > 0) {
734 const problem = requestShortener => {
735 return `Module ${module.readableIdentifier(
736 requestShortener
737 )} is runtime-dependent referenced by these modules: ${Array.from(
738 otherRuntimeConnections,
739 ({ originModule, runtimeCondition }) =>
740 `${originModule.readableIdentifier(
741 requestShortener
742 )} (expected runtime ${runtimeToString(
743 runtime
744 )}, module is only referenced in ${runtimeToString(
745 /** @type {RuntimeSpec} */ (runtimeCondition)
746 )})`
747 ).join(", ")}`;
748 };
749 statistics.incorrectRuntimeCondition++;
750 failureCache.set(module, problem); // cache failures for performance
751 return problem;
752 }
753 }
754
755 let backup;
756 if (avoidMutateOnFailure) {
757 backup = config.snapshot();
758 }
759
760 // Add the module
761 config.add(module);
762
763 incomingModules.sort(compareModulesByIdentifier);
764
765 // Every module which depends on the added module must be in the configuration too.
766 for (const originModule of incomingModules) {
767 const problem = this._tryToAdd(
768 compilation,
769 config,
770 originModule,
771 runtime,
772 activeRuntime,
773 possibleModules,
774 candidates,
775 failureCache,
776 chunkGraph,
777 false,
778 statistics
779 );
780 if (problem) {
781 if (backup !== undefined) config.rollback(backup);
782 statistics.importerFailed++;
783 failureCache.set(module, problem); // cache failures for performance
784 return problem;
785 }
786 }
787
788 // Add imports to possible candidates list
789 for (const imp of this._getImports(compilation, module, runtime)) {
790 candidates.add(imp);
791 }
792 statistics.added++;
793 return null;
794 }
795}
796
797class ConcatConfiguration {
798 /**
799 * @param {Module} rootModule the root module
800 * @param {RuntimeSpec} runtime the runtime
801 */
802 constructor(rootModule, runtime) {
803 this.rootModule = rootModule;
804 this.runtime = runtime;
805 /** @type {Set<Module>} */
806 this.modules = new Set();
807 this.modules.add(rootModule);
808 /** @type {Map<Module, Module | function(RequestShortener): string>} */
809 this.warnings = new Map();
810 }
811
812 add(module) {
813 this.modules.add(module);
814 }
815
816 has(module) {
817 return this.modules.has(module);
818 }
819
820 isEmpty() {
821 return this.modules.size === 1;
822 }
823
824 addWarning(module, problem) {
825 this.warnings.set(module, problem);
826 }
827
828 getWarningsSorted() {
829 return new Map(
830 Array.from(this.warnings).sort((a, b) => {
831 const ai = a[0].identifier();
832 const bi = b[0].identifier();
833 if (ai < bi) return -1;
834 if (ai > bi) return 1;
835 return 0;
836 })
837 );
838 }
839
840 /**
841 * @returns {Set<Module>} modules as set
842 */
843 getModules() {
844 return this.modules;
845 }
846
847 snapshot() {
848 return this.modules.size;
849 }
850
851 rollback(snapshot) {
852 const modules = this.modules;
853 for (const m of modules) {
854 if (snapshot === 0) {
855 modules.delete(m);
856 } else {
857 snapshot--;
858 }
859 }
860 }
861}
862
863module.exports = ModuleConcatenationPlugin;
864
\No newline at end of file