1 |
|
2 |
|
3 |
|
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 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
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 |
|
55 |
|
56 |
|
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 |
|
140 | if (moduleGraph.isAsync(module)) {
|
141 | setBailoutReason(module, `Module is async`);
|
142 | continue;
|
143 | }
|
144 |
|
145 |
|
146 | if (!module.buildInfo.strict) {
|
147 | setBailoutReason(module, `Module is not in strict mode`);
|
148 | continue;
|
149 | }
|
150 |
|
151 |
|
152 | if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
|
153 | setBailoutReason(module, "Module is not in any chunk");
|
154 | continue;
|
155 | }
|
156 |
|
157 |
|
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 |
|
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 |
|
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 |
|
213 |
|
214 |
|
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 |
|
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 |
|
243 |
|
244 |
|
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 |
|
263 | const currentConfiguration = new ConcatConfiguration(
|
264 | currentRoot,
|
265 | activeRuntime
|
266 | );
|
267 |
|
268 |
|
269 | const failureCache = new Map();
|
270 |
|
271 |
|
272 |
|
273 | const candidates = new Set();
|
274 |
|
275 |
|
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 |
|
800 | class 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 |
|
866 | module.exports = ModuleConcatenationPlugin;
|
867 |
|
\ | No newline at end of file |