source: frontend/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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