ModuleConcatenationPlugin.js 28 KB

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