FlagDependencyExportsPlugin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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 Queue = require("./util/Queue");
  8. /** @typedef {import("./Compiler")} Compiler */
  9. /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
  10. /** @typedef {import("./Dependency")} Dependency */
  11. /** @typedef {import("./Dependency").ExportSpec} ExportSpec */
  12. /** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */
  13. /** @typedef {import("./ExportsInfo")} ExportsInfo */
  14. /** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */
  15. /** @typedef {import("./ExportsInfo").RestoreProvidedData} RestoreProvidedData */
  16. /** @typedef {import("./Module")} Module */
  17. /** @typedef {import("./Module").BuildInfo} BuildInfo */
  18. const PLUGIN_NAME = "FlagDependencyExportsPlugin";
  19. const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
  20. class FlagDependencyExportsPlugin {
  21. /**
  22. * Apply the plugin
  23. * @param {Compiler} compiler the compiler instance
  24. * @returns {void}
  25. */
  26. apply(compiler) {
  27. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  28. const moduleGraph = compilation.moduleGraph;
  29. const cache = compilation.getCache(PLUGIN_NAME);
  30. compilation.hooks.finishModules.tapAsync(
  31. PLUGIN_NAME,
  32. (modules, callback) => {
  33. const logger = compilation.getLogger(PLUGIN_LOGGER_NAME);
  34. let statRestoredFromMemCache = 0;
  35. let statRestoredFromCache = 0;
  36. let statNoExports = 0;
  37. let statFlaggedUncached = 0;
  38. let statNotCached = 0;
  39. let statQueueItemsProcessed = 0;
  40. const { moduleMemCaches } = compilation;
  41. /** @type {Queue<Module>} */
  42. const queue = new Queue();
  43. // Step 1: Try to restore cached provided export info from cache
  44. logger.time("restore cached provided exports");
  45. asyncLib.each(
  46. /** @type {import("neo-async").IterableCollection<Module>} */ (
  47. /** @type {unknown} */ (modules)
  48. ),
  49. (module, callback) => {
  50. const exportsInfo = moduleGraph.getExportsInfo(module);
  51. // If the module doesn't have an exportsType, it's a module
  52. // without declared exports.
  53. if (
  54. (!module.buildMeta || !module.buildMeta.exportsType) &&
  55. exportsInfo.otherExportsInfo.provided !== null
  56. ) {
  57. // It's a module without declared exports
  58. statNoExports++;
  59. exportsInfo.setHasProvideInfo();
  60. exportsInfo.setUnknownExportsProvided();
  61. return callback();
  62. }
  63. // If the module has no hash, it's uncacheable
  64. if (
  65. typeof (/** @type {BuildInfo} */ (module.buildInfo).hash) !==
  66. "string"
  67. ) {
  68. statFlaggedUncached++;
  69. // Enqueue uncacheable module for determining the exports
  70. queue.enqueue(module);
  71. exportsInfo.setHasProvideInfo();
  72. return callback();
  73. }
  74. const memCache = moduleMemCaches && moduleMemCaches.get(module);
  75. const memCacheValue = memCache && memCache.get(this);
  76. if (memCacheValue !== undefined) {
  77. statRestoredFromMemCache++;
  78. exportsInfo.restoreProvided(memCacheValue);
  79. return callback();
  80. }
  81. cache.get(
  82. module.identifier(),
  83. /** @type {BuildInfo} */
  84. (module.buildInfo).hash,
  85. (err, result) => {
  86. if (err) return callback(err);
  87. if (result !== undefined) {
  88. statRestoredFromCache++;
  89. exportsInfo.restoreProvided(result);
  90. } else {
  91. statNotCached++;
  92. // Without cached info enqueue module for determining the exports
  93. queue.enqueue(module);
  94. exportsInfo.setHasProvideInfo();
  95. }
  96. callback();
  97. }
  98. );
  99. },
  100. (err) => {
  101. logger.timeEnd("restore cached provided exports");
  102. if (err) return callback(err);
  103. /** @type {Set<Module>} */
  104. const modulesToStore = new Set();
  105. /** @type {Map<Module, Set<Module>>} */
  106. const dependencies = new Map();
  107. /** @type {Module} */
  108. let module;
  109. /** @type {ExportsInfo} */
  110. let exportsInfo;
  111. /** @type {Map<Dependency, ExportsSpec>} */
  112. const exportsSpecsFromDependencies = new Map();
  113. let cacheable = true;
  114. let changed = false;
  115. /**
  116. * @param {DependenciesBlock} depBlock the dependencies block
  117. * @returns {void}
  118. */
  119. const processDependenciesBlock = (depBlock) => {
  120. for (const dep of depBlock.dependencies) {
  121. processDependency(dep);
  122. }
  123. for (const block of depBlock.blocks) {
  124. processDependenciesBlock(block);
  125. }
  126. };
  127. /**
  128. * @param {Dependency} dep the dependency
  129. * @returns {void}
  130. */
  131. const processDependency = (dep) => {
  132. const exportDesc = dep.getExports(moduleGraph);
  133. if (!exportDesc) return;
  134. exportsSpecsFromDependencies.set(dep, exportDesc);
  135. };
  136. /**
  137. * @param {Dependency} dep dependency
  138. * @param {ExportsSpec} exportDesc info
  139. * @returns {void}
  140. */
  141. const processExportsSpec = (dep, exportDesc) => {
  142. const exports = exportDesc.exports;
  143. const globalCanMangle = exportDesc.canMangle;
  144. const globalFrom = exportDesc.from;
  145. const globalPriority = exportDesc.priority;
  146. const globalTerminalBinding =
  147. exportDesc.terminalBinding || false;
  148. const exportDeps = exportDesc.dependencies;
  149. if (exportDesc.hideExports) {
  150. for (const name of exportDesc.hideExports) {
  151. const exportInfo = exportsInfo.getExportInfo(name);
  152. exportInfo.unsetTarget(dep);
  153. }
  154. }
  155. if (exports === true) {
  156. // unknown exports
  157. if (
  158. exportsInfo.setUnknownExportsProvided(
  159. globalCanMangle,
  160. exportDesc.excludeExports,
  161. globalFrom && dep,
  162. globalFrom,
  163. globalPriority
  164. )
  165. ) {
  166. changed = true;
  167. }
  168. } else if (Array.isArray(exports)) {
  169. /**
  170. * merge in new exports
  171. * @param {ExportsInfo} exportsInfo own exports info
  172. * @param {(ExportSpec | string)[]} exports list of exports
  173. */
  174. const mergeExports = (exportsInfo, exports) => {
  175. for (const exportNameOrSpec of exports) {
  176. /** @type {ExportInfoName} */
  177. let name;
  178. let canMangle = globalCanMangle;
  179. let terminalBinding = globalTerminalBinding;
  180. /** @type {ExportSpec["exports"]} */
  181. let exports;
  182. let from = globalFrom;
  183. /** @type {ExportSpec["export"]} */
  184. let fromExport;
  185. let priority = globalPriority;
  186. let hidden = false;
  187. if (typeof exportNameOrSpec === "string") {
  188. name = exportNameOrSpec;
  189. } else {
  190. name = exportNameOrSpec.name;
  191. if (exportNameOrSpec.canMangle !== undefined) {
  192. canMangle = exportNameOrSpec.canMangle;
  193. }
  194. if (exportNameOrSpec.export !== undefined) {
  195. fromExport = exportNameOrSpec.export;
  196. }
  197. if (exportNameOrSpec.exports !== undefined) {
  198. exports = exportNameOrSpec.exports;
  199. }
  200. if (exportNameOrSpec.from !== undefined) {
  201. from = exportNameOrSpec.from;
  202. }
  203. if (exportNameOrSpec.priority !== undefined) {
  204. priority = exportNameOrSpec.priority;
  205. }
  206. if (exportNameOrSpec.terminalBinding !== undefined) {
  207. terminalBinding = exportNameOrSpec.terminalBinding;
  208. }
  209. if (exportNameOrSpec.hidden !== undefined) {
  210. hidden = exportNameOrSpec.hidden;
  211. }
  212. }
  213. const exportInfo = exportsInfo.getExportInfo(name);
  214. if (
  215. exportInfo.provided === false ||
  216. exportInfo.provided === null
  217. ) {
  218. exportInfo.provided = true;
  219. changed = true;
  220. }
  221. if (
  222. exportInfo.canMangleProvide !== false &&
  223. canMangle === false
  224. ) {
  225. exportInfo.canMangleProvide = false;
  226. changed = true;
  227. }
  228. if (terminalBinding && !exportInfo.terminalBinding) {
  229. exportInfo.terminalBinding = true;
  230. changed = true;
  231. }
  232. if (exports) {
  233. const nestedExportsInfo =
  234. exportInfo.createNestedExportsInfo();
  235. mergeExports(
  236. /** @type {ExportsInfo} */ (nestedExportsInfo),
  237. exports
  238. );
  239. }
  240. if (
  241. from &&
  242. (hidden
  243. ? exportInfo.unsetTarget(dep)
  244. : exportInfo.setTarget(
  245. dep,
  246. from,
  247. fromExport === undefined ? [name] : fromExport,
  248. priority
  249. ))
  250. ) {
  251. changed = true;
  252. }
  253. // Recalculate target exportsInfo
  254. const target = exportInfo.getTarget(moduleGraph);
  255. /** @type {undefined | ExportsInfo} */
  256. let targetExportsInfo;
  257. if (target) {
  258. const targetModuleExportsInfo =
  259. moduleGraph.getExportsInfo(target.module);
  260. targetExportsInfo =
  261. targetModuleExportsInfo.getNestedExportsInfo(
  262. target.export
  263. );
  264. // add dependency for this module
  265. const set = dependencies.get(target.module);
  266. if (set === undefined) {
  267. dependencies.set(target.module, new Set([module]));
  268. } else {
  269. set.add(module);
  270. }
  271. }
  272. if (exportInfo.exportsInfoOwned) {
  273. if (
  274. /** @type {ExportsInfo} */
  275. (exportInfo.exportsInfo).setRedirectNamedTo(
  276. targetExportsInfo
  277. )
  278. ) {
  279. changed = true;
  280. }
  281. } else if (exportInfo.exportsInfo !== targetExportsInfo) {
  282. exportInfo.exportsInfo = targetExportsInfo;
  283. changed = true;
  284. }
  285. }
  286. };
  287. mergeExports(exportsInfo, exports);
  288. }
  289. // store dependencies
  290. if (exportDeps) {
  291. cacheable = false;
  292. for (const exportDependency of exportDeps) {
  293. // add dependency for this module
  294. const set = dependencies.get(exportDependency);
  295. if (set === undefined) {
  296. dependencies.set(exportDependency, new Set([module]));
  297. } else {
  298. set.add(module);
  299. }
  300. }
  301. }
  302. };
  303. const notifyDependencies = () => {
  304. const deps = dependencies.get(module);
  305. if (deps !== undefined) {
  306. for (const dep of deps) {
  307. queue.enqueue(dep);
  308. }
  309. }
  310. };
  311. logger.time("figure out provided exports");
  312. while (queue.length > 0) {
  313. module = /** @type {Module} */ (queue.dequeue());
  314. statQueueItemsProcessed++;
  315. exportsInfo = moduleGraph.getExportsInfo(module);
  316. cacheable = true;
  317. changed = false;
  318. exportsSpecsFromDependencies.clear();
  319. moduleGraph.freeze();
  320. processDependenciesBlock(module);
  321. moduleGraph.unfreeze();
  322. for (const [dep, exportsSpec] of exportsSpecsFromDependencies) {
  323. processExportsSpec(dep, exportsSpec);
  324. }
  325. if (cacheable) {
  326. modulesToStore.add(module);
  327. }
  328. if (changed) {
  329. notifyDependencies();
  330. }
  331. }
  332. logger.timeEnd("figure out provided exports");
  333. logger.log(
  334. `${Math.round(
  335. (100 * (statFlaggedUncached + statNotCached)) /
  336. (statRestoredFromMemCache +
  337. statRestoredFromCache +
  338. statNotCached +
  339. statFlaggedUncached +
  340. statNoExports)
  341. )}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${statRestoredFromMemCache} from mem cache, ${
  342. statQueueItemsProcessed - statNotCached - statFlaggedUncached
  343. } additional calculations due to dependencies)`
  344. );
  345. logger.time("store provided exports into cache");
  346. asyncLib.each(
  347. modulesToStore,
  348. (module, callback) => {
  349. if (
  350. typeof (
  351. /** @type {BuildInfo} */
  352. (module.buildInfo).hash
  353. ) !== "string"
  354. ) {
  355. // not cacheable
  356. return callback();
  357. }
  358. const cachedData = moduleGraph
  359. .getExportsInfo(module)
  360. .getRestoreProvidedData();
  361. const memCache =
  362. moduleMemCaches && moduleMemCaches.get(module);
  363. if (memCache) {
  364. memCache.set(this, cachedData);
  365. }
  366. cache.store(
  367. module.identifier(),
  368. /** @type {BuildInfo} */
  369. (module.buildInfo).hash,
  370. cachedData,
  371. callback
  372. );
  373. },
  374. (err) => {
  375. logger.timeEnd("store provided exports into cache");
  376. callback(err);
  377. }
  378. );
  379. }
  380. );
  381. }
  382. );
  383. /** @type {WeakMap<Module, RestoreProvidedData>} */
  384. const providedExportsCache = new WeakMap();
  385. compilation.hooks.rebuildModule.tap(PLUGIN_NAME, (module) => {
  386. providedExportsCache.set(
  387. module,
  388. moduleGraph.getExportsInfo(module).getRestoreProvidedData()
  389. );
  390. });
  391. compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, (module) => {
  392. moduleGraph.getExportsInfo(module).restoreProvided(
  393. /** @type {RestoreProvidedData} */
  394. (providedExportsCache.get(module))
  395. );
  396. });
  397. });
  398. }
  399. }
  400. module.exports = FlagDependencyExportsPlugin;