FlagDependencyExportsPlugin.js 13 KB

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