ModuleConcatenationPlugin.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  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. /**
  39. * @param {string} msg message
  40. * @returns {string} formatted message
  41. */
  42. const formatBailoutReason = msg => {
  43. return "ModuleConcatenation bailout: " + msg;
  44. };
  45. class ModuleConcatenationPlugin {
  46. constructor(options) {
  47. if (typeof options !== "object") options = {};
  48. this.options = options;
  49. }
  50. /**
  51. * Apply the plugin
  52. * @param {Compiler} compiler the compiler instance
  53. * @returns {void}
  54. */
  55. apply(compiler) {
  56. const { _backCompat: backCompat } = compiler;
  57. compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => {
  58. if (compilation.moduleMemCaches) {
  59. throw new Error(
  60. "optimization.concatenateModules can't be used with cacheUnaffected as module concatenation is a global effect"
  61. );
  62. }
  63. const moduleGraph = compilation.moduleGraph;
  64. /** @type {Map<Module, string | ((requestShortener: RequestShortener) => string)>} */
  65. const bailoutReasonMap = new Map();
  66. /**
  67. * @param {Module} module the module
  68. * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
  69. */
  70. const setBailoutReason = (module, reason) => {
  71. setInnerBailoutReason(module, reason);
  72. moduleGraph
  73. .getOptimizationBailout(module)
  74. .push(
  75. typeof reason === "function"
  76. ? rs => formatBailoutReason(reason(rs))
  77. : formatBailoutReason(reason)
  78. );
  79. };
  80. /**
  81. * @param {Module} module the module
  82. * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
  83. */
  84. const setInnerBailoutReason = (module, reason) => {
  85. bailoutReasonMap.set(module, reason);
  86. };
  87. /**
  88. * @param {Module} module the module
  89. * @param {RequestShortener} requestShortener the request shortener
  90. * @returns {string | ((requestShortener: RequestShortener) => string) | undefined} the reason
  91. */
  92. const getInnerBailoutReason = (module, requestShortener) => {
  93. const reason = bailoutReasonMap.get(module);
  94. if (typeof reason === "function") return reason(requestShortener);
  95. return reason;
  96. };
  97. /**
  98. * @param {Module} module the module
  99. * @param {Module | function(RequestShortener): string} problem the problem
  100. * @returns {(requestShortener: RequestShortener) => string} the reason
  101. */
  102. const formatBailoutWarning = (module, problem) => requestShortener => {
  103. if (typeof problem === "function") {
  104. return formatBailoutReason(
  105. `Cannot concat with ${module.readableIdentifier(
  106. requestShortener
  107. )}: ${problem(requestShortener)}`
  108. );
  109. }
  110. const reason = getInnerBailoutReason(module, requestShortener);
  111. const reasonWithPrefix = reason ? `: ${reason}` : "";
  112. if (module === problem) {
  113. return formatBailoutReason(
  114. `Cannot concat with ${module.readableIdentifier(
  115. requestShortener
  116. )}${reasonWithPrefix}`
  117. );
  118. } else {
  119. return formatBailoutReason(
  120. `Cannot concat with ${module.readableIdentifier(
  121. requestShortener
  122. )} because of ${problem.readableIdentifier(
  123. requestShortener
  124. )}${reasonWithPrefix}`
  125. );
  126. }
  127. };
  128. compilation.hooks.optimizeChunkModules.tapAsync(
  129. {
  130. name: "ModuleConcatenationPlugin",
  131. stage: STAGE_DEFAULT
  132. },
  133. (allChunks, modules, callback) => {
  134. const logger = compilation.getLogger(
  135. "webpack.ModuleConcatenationPlugin"
  136. );
  137. const { chunkGraph, moduleGraph } = compilation;
  138. const relevantModules = [];
  139. const possibleInners = new Set();
  140. const context = {
  141. chunkGraph,
  142. moduleGraph
  143. };
  144. logger.time("select relevant modules");
  145. for (const module of modules) {
  146. let canBeRoot = true;
  147. let canBeInner = true;
  148. const bailoutReason = module.getConcatenationBailoutReason(context);
  149. if (bailoutReason) {
  150. setBailoutReason(module, bailoutReason);
  151. continue;
  152. }
  153. // Must not be an async module
  154. if (moduleGraph.isAsync(module)) {
  155. setBailoutReason(module, `Module is async`);
  156. continue;
  157. }
  158. // Must be in strict mode
  159. if (!module.buildInfo.strict) {
  160. setBailoutReason(module, `Module is not in strict mode`);
  161. continue;
  162. }
  163. // Module must be in any chunk (we don't want to do useless work)
  164. if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
  165. setBailoutReason(module, "Module is not in any chunk");
  166. continue;
  167. }
  168. // Exports must be known (and not dynamic)
  169. const exportsInfo = moduleGraph.getExportsInfo(module);
  170. const relevantExports = exportsInfo.getRelevantExports(undefined);
  171. const unknownReexports = relevantExports.filter(exportInfo => {
  172. return (
  173. exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
  174. );
  175. });
  176. if (unknownReexports.length > 0) {
  177. setBailoutReason(
  178. module,
  179. `Reexports in this module do not have a static target (${Array.from(
  180. unknownReexports,
  181. exportInfo =>
  182. `${
  183. exportInfo.name || "other exports"
  184. }: ${exportInfo.getUsedInfo()}`
  185. ).join(", ")})`
  186. );
  187. continue;
  188. }
  189. // Root modules must have a static list of exports
  190. const unknownProvidedExports = relevantExports.filter(
  191. exportInfo => {
  192. return exportInfo.provided !== true;
  193. }
  194. );
  195. if (unknownProvidedExports.length > 0) {
  196. setBailoutReason(
  197. module,
  198. `List of module exports is dynamic (${Array.from(
  199. unknownProvidedExports,
  200. exportInfo =>
  201. `${
  202. exportInfo.name || "other exports"
  203. }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
  204. ).join(", ")})`
  205. );
  206. canBeRoot = false;
  207. }
  208. // Module must not be an entry point
  209. if (chunkGraph.isEntryModule(module)) {
  210. setInnerBailoutReason(module, "Module is an entry point");
  211. canBeInner = false;
  212. }
  213. if (canBeRoot) relevantModules.push(module);
  214. if (canBeInner) possibleInners.add(module);
  215. }
  216. logger.timeEnd("select relevant modules");
  217. logger.debug(
  218. `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
  219. );
  220. // sort by depth
  221. // modules with lower depth are more likely suited as roots
  222. // this improves performance, because modules already selected as inner are skipped
  223. logger.time("sort relevant modules");
  224. relevantModules.sort((a, b) => {
  225. return moduleGraph.getDepth(a) - moduleGraph.getDepth(b);
  226. });
  227. logger.timeEnd("sort relevant modules");
  228. /** @type {Statistics} */
  229. const stats = {
  230. cached: 0,
  231. alreadyInConfig: 0,
  232. invalidModule: 0,
  233. incorrectChunks: 0,
  234. incorrectDependency: 0,
  235. incorrectModuleDependency: 0,
  236. incorrectChunksOfImporter: 0,
  237. incorrectRuntimeCondition: 0,
  238. importerFailed: 0,
  239. added: 0
  240. };
  241. let statsCandidates = 0;
  242. let statsSizeSum = 0;
  243. let statsEmptyConfigurations = 0;
  244. logger.time("find modules to concatenate");
  245. const concatConfigurations = [];
  246. const usedAsInner = new Set();
  247. for (const currentRoot of relevantModules) {
  248. // when used by another configuration as inner:
  249. // the other configuration is better and we can skip this one
  250. // TODO reconsider that when it's only used in a different runtime
  251. if (usedAsInner.has(currentRoot)) continue;
  252. let chunkRuntime = undefined;
  253. for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
  254. chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
  255. }
  256. const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
  257. const filteredRuntime = filterRuntime(chunkRuntime, r =>
  258. exportsInfo.isModuleUsed(r)
  259. );
  260. const activeRuntime =
  261. filteredRuntime === true
  262. ? chunkRuntime
  263. : filteredRuntime === false
  264. ? undefined
  265. : filteredRuntime;
  266. // create a configuration with the root
  267. const currentConfiguration = new ConcatConfiguration(
  268. currentRoot,
  269. activeRuntime
  270. );
  271. // cache failures to add modules
  272. const failureCache = new Map();
  273. // potential optional import candidates
  274. /** @type {Set<Module>} */
  275. const candidates = new Set();
  276. // try to add all imports
  277. for (const imp of this._getImports(
  278. compilation,
  279. currentRoot,
  280. activeRuntime
  281. )) {
  282. candidates.add(imp);
  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. logger.time("create concatenated modules");
  351. asyncLib.each(
  352. concatConfigurations,
  353. (concatConfiguration, callback) => {
  354. const rootModule = concatConfiguration.rootModule;
  355. // Avoid overlapping configurations
  356. // TODO: remove this when todo above is fixed
  357. if (usedModules.has(rootModule)) return callback();
  358. const modules = concatConfiguration.getModules();
  359. for (const m of modules) {
  360. usedModules.add(m);
  361. }
  362. // Create a new ConcatenatedModule
  363. let newModule = ConcatenatedModule.create(
  364. rootModule,
  365. modules,
  366. concatConfiguration.runtime,
  367. compiler.root,
  368. compilation.outputOptions.hashFunction
  369. );
  370. const build = () => {
  371. newModule.build(
  372. compiler.options,
  373. compilation,
  374. null,
  375. null,
  376. err => {
  377. if (err) {
  378. if (!err.module) {
  379. err.module = newModule;
  380. }
  381. return callback(err);
  382. }
  383. integrate();
  384. }
  385. );
  386. };
  387. const integrate = () => {
  388. if (backCompat) {
  389. ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
  390. ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
  391. }
  392. for (const warning of concatConfiguration.getWarningsSorted()) {
  393. moduleGraph
  394. .getOptimizationBailout(newModule)
  395. .push(formatBailoutWarning(warning[0], warning[1]));
  396. }
  397. moduleGraph.cloneModuleAttributes(rootModule, newModule);
  398. for (const m of modules) {
  399. // add to builtModules when one of the included modules was built
  400. if (compilation.builtModules.has(m)) {
  401. compilation.builtModules.add(newModule);
  402. }
  403. if (m !== rootModule) {
  404. // attach external references to the concatenated module too
  405. moduleGraph.copyOutgoingModuleConnections(
  406. m,
  407. newModule,
  408. c => {
  409. return (
  410. c.originModule === m &&
  411. !(
  412. c.dependency instanceof HarmonyImportDependency &&
  413. modules.has(c.module)
  414. )
  415. );
  416. }
  417. );
  418. // remove module from chunk
  419. for (const chunk of chunkGraph.getModuleChunksIterable(
  420. rootModule
  421. )) {
  422. const sourceTypes = chunkGraph.getChunkModuleSourceTypes(
  423. chunk,
  424. m
  425. );
  426. if (sourceTypes.size === 1) {
  427. chunkGraph.disconnectChunkAndModule(chunk, m);
  428. } else {
  429. const newSourceTypes = new Set(sourceTypes);
  430. newSourceTypes.delete("javascript");
  431. chunkGraph.setChunkModuleSourceTypes(
  432. chunk,
  433. m,
  434. newSourceTypes
  435. );
  436. }
  437. }
  438. }
  439. }
  440. compilation.modules.delete(rootModule);
  441. ChunkGraph.clearChunkGraphForModule(rootModule);
  442. ModuleGraph.clearModuleGraphForModule(rootModule);
  443. // remove module from chunk
  444. chunkGraph.replaceModule(rootModule, newModule);
  445. // replace module references with the concatenated module
  446. moduleGraph.moveModuleConnections(rootModule, newModule, c => {
  447. const otherModule =
  448. c.module === rootModule ? c.originModule : c.module;
  449. const innerConnection =
  450. c.dependency instanceof HarmonyImportDependency &&
  451. modules.has(/** @type {Module} */ (otherModule));
  452. return !innerConnection;
  453. });
  454. // add concatenated module to the compilation
  455. compilation.modules.add(newModule);
  456. callback();
  457. };
  458. build();
  459. },
  460. err => {
  461. logger.timeEnd("create concatenated modules");
  462. process.nextTick(callback.bind(null, err));
  463. }
  464. );
  465. }
  466. );
  467. });
  468. }
  469. /**
  470. * @param {Compilation} compilation the compilation
  471. * @param {Module} module the module to be added
  472. * @param {RuntimeSpec} runtime the runtime scope
  473. * @returns {Set<Module>} the imported modules
  474. */
  475. _getImports(compilation, module, runtime) {
  476. const moduleGraph = compilation.moduleGraph;
  477. const set = new Set();
  478. for (const dep of module.dependencies) {
  479. // Get reference info only for harmony Dependencies
  480. if (!(dep instanceof HarmonyImportDependency)) continue;
  481. const connection = moduleGraph.getConnection(dep);
  482. // Reference is valid and has a module
  483. if (
  484. !connection ||
  485. !connection.module ||
  486. !connection.isTargetActive(runtime)
  487. ) {
  488. continue;
  489. }
  490. const importedNames = compilation.getDependencyReferencedExports(
  491. dep,
  492. undefined
  493. );
  494. if (
  495. importedNames.every(i =>
  496. Array.isArray(i) ? i.length > 0 : i.name.length > 0
  497. ) ||
  498. Array.isArray(moduleGraph.getProvidedExports(module))
  499. ) {
  500. set.add(connection.module);
  501. }
  502. }
  503. return set;
  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 {null | 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. // Already added?
  538. if (config.has(module)) {
  539. statistics.alreadyInConfig++;
  540. return null;
  541. }
  542. // Not possible to add?
  543. if (!possibleModules.has(module)) {
  544. statistics.invalidModule++;
  545. failureCache.set(module, module); // cache failures for performance
  546. return module;
  547. }
  548. // Module must be in the correct chunks
  549. const missingChunks = Array.from(
  550. chunkGraph.getModuleChunksIterable(config.rootModule)
  551. ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk));
  552. if (missingChunks.length > 0) {
  553. /**
  554. * @param {RequestShortener} requestShortener request shortener
  555. * @returns {string} problem description
  556. */
  557. const problem = requestShortener => {
  558. const missingChunksList = Array.from(
  559. new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)"))
  560. ).sort();
  561. const chunks = Array.from(
  562. new Set(
  563. Array.from(chunkGraph.getModuleChunksIterable(module)).map(
  564. chunk => chunk.name || "unnamed chunk(s)"
  565. )
  566. )
  567. ).sort();
  568. return `Module ${module.readableIdentifier(
  569. requestShortener
  570. )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
  571. ", "
  572. )}, module is in chunk(s) ${chunks.join(", ")})`;
  573. };
  574. statistics.incorrectChunks++;
  575. failureCache.set(module, problem); // cache failures for performance
  576. return problem;
  577. }
  578. const moduleGraph = compilation.moduleGraph;
  579. const incomingConnections =
  580. moduleGraph.getIncomingConnectionsByOriginModule(module);
  581. const incomingConnectionsFromNonModules =
  582. incomingConnections.get(null) || incomingConnections.get(undefined);
  583. if (incomingConnectionsFromNonModules) {
  584. const activeNonModulesConnections =
  585. incomingConnectionsFromNonModules.filter(connection => {
  586. // We are not interested in inactive connections
  587. // or connections without dependency
  588. return connection.isActive(runtime);
  589. });
  590. if (activeNonModulesConnections.length > 0) {
  591. /**
  592. * @param {RequestShortener} requestShortener request shortener
  593. * @returns {string} problem description
  594. */
  595. const problem = requestShortener => {
  596. const importingExplanations = new Set(
  597. activeNonModulesConnections.map(c => c.explanation).filter(Boolean)
  598. );
  599. const explanations = Array.from(importingExplanations).sort();
  600. return `Module ${module.readableIdentifier(
  601. requestShortener
  602. )} is referenced ${
  603. explanations.length > 0
  604. ? `by: ${explanations.join(", ")}`
  605. : "in an unsupported way"
  606. }`;
  607. };
  608. statistics.incorrectDependency++;
  609. failureCache.set(module, problem); // cache failures for performance
  610. return problem;
  611. }
  612. }
  613. /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
  614. const incomingConnectionsFromModules = new Map();
  615. for (const [originModule, connections] of incomingConnections) {
  616. if (originModule) {
  617. // Ignore connection from orphan modules
  618. if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;
  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. if (!intersectRuntime(runtime, originRuntime)) continue;
  625. // We are not interested in inactive connections
  626. const activeConnections = connections.filter(connection =>
  627. connection.isActive(runtime)
  628. );
  629. if (activeConnections.length > 0)
  630. incomingConnectionsFromModules.set(originModule, activeConnections);
  631. }
  632. }
  633. const incomingModules = Array.from(incomingConnectionsFromModules.keys());
  634. // Module must be in the same chunks like the referencing module
  635. const otherChunkModules = incomingModules.filter(originModule => {
  636. for (const chunk of chunkGraph.getModuleChunksIterable(
  637. config.rootModule
  638. )) {
  639. if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
  640. return true;
  641. }
  642. }
  643. return false;
  644. });
  645. if (otherChunkModules.length > 0) {
  646. /**
  647. * @param {RequestShortener} requestShortener request shortener
  648. * @returns {string} problem description
  649. */
  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. /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
  665. const nonHarmonyConnections = new Map();
  666. for (const [originModule, connections] of incomingConnectionsFromModules) {
  667. const selected = connections.filter(
  668. connection =>
  669. !connection.dependency ||
  670. !(connection.dependency instanceof HarmonyImportDependency)
  671. );
  672. if (selected.length > 0)
  673. nonHarmonyConnections.set(originModule, connections);
  674. }
  675. if (nonHarmonyConnections.size > 0) {
  676. /**
  677. * @param {RequestShortener} requestShortener request shortener
  678. * @returns {string} problem description
  679. */
  680. const problem = requestShortener => {
  681. const names = Array.from(nonHarmonyConnections)
  682. .map(([originModule, connections]) => {
  683. return `${originModule.readableIdentifier(
  684. requestShortener
  685. )} (referenced with ${Array.from(
  686. new Set(
  687. connections
  688. .map(c => c.dependency && c.dependency.type)
  689. .filter(Boolean)
  690. )
  691. )
  692. .sort()
  693. .join(", ")})`;
  694. })
  695. .sort();
  696. return `Module ${module.readableIdentifier(
  697. requestShortener
  698. )} is referenced from these modules with unsupported syntax: ${names.join(
  699. ", "
  700. )}`;
  701. };
  702. statistics.incorrectModuleDependency++;
  703. failureCache.set(module, problem); // cache failures for performance
  704. return problem;
  705. }
  706. if (runtime !== undefined && typeof runtime !== "string") {
  707. // Module must be consistently referenced in the same runtimes
  708. /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
  709. const otherRuntimeConnections = [];
  710. outer: for (const [
  711. originModule,
  712. connections
  713. ] of incomingConnectionsFromModules) {
  714. /** @type {false | RuntimeSpec} */
  715. let currentRuntimeCondition = false;
  716. for (const connection of connections) {
  717. const runtimeCondition = filterRuntime(runtime, runtime => {
  718. return connection.isTargetActive(runtime);
  719. });
  720. if (runtimeCondition === false) continue;
  721. if (runtimeCondition === true) continue outer;
  722. if (currentRuntimeCondition !== false) {
  723. currentRuntimeCondition = mergeRuntime(
  724. currentRuntimeCondition,
  725. runtimeCondition
  726. );
  727. } else {
  728. currentRuntimeCondition = runtimeCondition;
  729. }
  730. }
  731. if (currentRuntimeCondition !== false) {
  732. otherRuntimeConnections.push({
  733. originModule,
  734. runtimeCondition: currentRuntimeCondition
  735. });
  736. }
  737. }
  738. if (otherRuntimeConnections.length > 0) {
  739. /**
  740. * @param {RequestShortener} requestShortener request shortener
  741. * @returns {string} problem description
  742. */
  743. const problem = requestShortener => {
  744. return `Module ${module.readableIdentifier(
  745. requestShortener
  746. )} is runtime-dependent referenced by these modules: ${Array.from(
  747. otherRuntimeConnections,
  748. ({ originModule, runtimeCondition }) =>
  749. `${originModule.readableIdentifier(
  750. requestShortener
  751. )} (expected runtime ${runtimeToString(
  752. runtime
  753. )}, module is only referenced in ${runtimeToString(
  754. /** @type {RuntimeSpec} */ (runtimeCondition)
  755. )})`
  756. ).join(", ")}`;
  757. };
  758. statistics.incorrectRuntimeCondition++;
  759. failureCache.set(module, problem); // cache failures for performance
  760. return problem;
  761. }
  762. }
  763. let backup;
  764. if (avoidMutateOnFailure) {
  765. backup = config.snapshot();
  766. }
  767. // Add the module
  768. config.add(module);
  769. incomingModules.sort(compareModulesByIdentifier);
  770. // Every module which depends on the added module must be in the configuration too.
  771. for (const originModule of incomingModules) {
  772. const problem = this._tryToAdd(
  773. compilation,
  774. config,
  775. originModule,
  776. runtime,
  777. activeRuntime,
  778. possibleModules,
  779. candidates,
  780. failureCache,
  781. chunkGraph,
  782. false,
  783. statistics
  784. );
  785. if (problem) {
  786. if (backup !== undefined) config.rollback(backup);
  787. statistics.importerFailed++;
  788. failureCache.set(module, problem); // cache failures for performance
  789. return problem;
  790. }
  791. }
  792. // Add imports to possible candidates list
  793. for (const imp of this._getImports(compilation, module, runtime)) {
  794. candidates.add(imp);
  795. }
  796. statistics.added++;
  797. return null;
  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. * @param {Module} module the module
  816. */
  817. add(module) {
  818. this.modules.add(module);
  819. }
  820. /**
  821. * @param {Module} module the module
  822. * @returns {boolean} true, when the module is in the module set
  823. */
  824. has(module) {
  825. return this.modules.has(module);
  826. }
  827. isEmpty() {
  828. return this.modules.size === 1;
  829. }
  830. /**
  831. * @param {Module} module the module
  832. * @param {Module | function(RequestShortener): string} problem the problem
  833. */
  834. addWarning(module, problem) {
  835. this.warnings.set(module, problem);
  836. }
  837. /**
  838. * @returns {Map<Module, Module | function(RequestShortener): string>} warnings
  839. */
  840. getWarningsSorted() {
  841. return new Map(
  842. Array.from(this.warnings).sort((a, b) => {
  843. const ai = a[0].identifier();
  844. const bi = b[0].identifier();
  845. if (ai < bi) return -1;
  846. if (ai > bi) return 1;
  847. return 0;
  848. })
  849. );
  850. }
  851. /**
  852. * @returns {Set<Module>} modules as set
  853. */
  854. getModules() {
  855. return this.modules;
  856. }
  857. snapshot() {
  858. return this.modules.size;
  859. }
  860. rollback(snapshot) {
  861. const modules = this.modules;
  862. for (const m of modules) {
  863. if (snapshot === 0) {
  864. modules.delete(m);
  865. } else {
  866. snapshot--;
  867. }
  868. }
  869. }
  870. }
  871. module.exports = ModuleConcatenationPlugin;