ModuleConcatenationPlugin.js 29 KB

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