FlagDependencyExportsPlugin.js 13 KB

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