ModuleConcatenationPlugin.js 27 KB

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