ModuleConcatenationPlugin.js 26 KB

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