提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
11 KiB

  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Dependency = require("./Dependency");
  7. const { UsageState } = require("./ExportsInfo");
  8. const ModuleGraphConnection = require("./ModuleGraphConnection");
  9. const { STAGE_DEFAULT } = require("./OptimizationStages");
  10. const ArrayQueue = require("./util/ArrayQueue");
  11. const TupleQueue = require("./util/TupleQueue");
  12. const { getEntryRuntime, mergeRuntimeOwned } = require("./util/runtime");
  13. /** @typedef {import("./Compiler")} Compiler */
  14. /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
  15. /** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */
  16. /** @typedef {import("./Dependency").ReferencedExports} ReferencedExports */
  17. /** @typedef {import("./ExportsInfo")} ExportsInfo */
  18. /** @typedef {import("./Module")} Module */
  19. /** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
  20. const { NO_EXPORTS_REFERENCED, EXPORTS_OBJECT_REFERENCED } = Dependency;
  21. const PLUGIN_NAME = "FlagDependencyUsagePlugin";
  22. const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
  23. class FlagDependencyUsagePlugin {
  24. /**
  25. * @param {boolean} global do a global analysis instead of per runtime
  26. */
  27. constructor(global) {
  28. this.global = global;
  29. }
  30. /**
  31. * Apply the plugin
  32. * @param {Compiler} compiler the compiler instance
  33. * @returns {void}
  34. */
  35. apply(compiler) {
  36. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  37. const moduleGraph = compilation.moduleGraph;
  38. compilation.hooks.optimizeDependencies.tap(
  39. { name: PLUGIN_NAME, stage: STAGE_DEFAULT },
  40. (modules) => {
  41. if (compilation.moduleMemCaches) {
  42. throw new Error(
  43. "optimization.usedExports can't be used with cacheUnaffected as export usage is a global effect"
  44. );
  45. }
  46. const logger = compilation.getLogger(PLUGIN_LOGGER_NAME);
  47. /** @type {Map<ExportsInfo, Module>} */
  48. const exportInfoToModuleMap = new Map();
  49. /** @type {TupleQueue<Module, RuntimeSpec>} */
  50. const queue = new TupleQueue();
  51. /**
  52. * @param {Module} module module to process
  53. * @param {ReferencedExports} usedExports list of used exports
  54. * @param {RuntimeSpec} runtime part of which runtime
  55. * @param {boolean} forceSideEffects always apply side effects
  56. * @returns {void}
  57. */
  58. const processReferencedModule = (
  59. module,
  60. usedExports,
  61. runtime,
  62. forceSideEffects
  63. ) => {
  64. const exportsInfo = moduleGraph.getExportsInfo(module);
  65. if (usedExports.length > 0) {
  66. if (!module.buildMeta || !module.buildMeta.exportsType) {
  67. if (exportsInfo.setUsedWithoutInfo(runtime)) {
  68. queue.enqueue(module, runtime);
  69. }
  70. return;
  71. }
  72. for (const usedExportInfo of usedExports) {
  73. let usedExport;
  74. let canMangle = true;
  75. if (Array.isArray(usedExportInfo)) {
  76. usedExport = usedExportInfo;
  77. } else {
  78. usedExport = usedExportInfo.name;
  79. canMangle = usedExportInfo.canMangle !== false;
  80. }
  81. if (usedExport.length === 0) {
  82. if (exportsInfo.setUsedInUnknownWay(runtime)) {
  83. queue.enqueue(module, runtime);
  84. }
  85. } else {
  86. let currentExportsInfo = exportsInfo;
  87. for (let i = 0; i < usedExport.length; i++) {
  88. const exportInfo = currentExportsInfo.getExportInfo(
  89. usedExport[i]
  90. );
  91. if (canMangle === false) {
  92. exportInfo.canMangleUse = false;
  93. }
  94. const lastOne = i === usedExport.length - 1;
  95. if (!lastOne) {
  96. const nestedInfo = exportInfo.getNestedExportsInfo();
  97. if (nestedInfo) {
  98. if (
  99. exportInfo.setUsedConditionally(
  100. (used) => used === UsageState.Unused,
  101. UsageState.OnlyPropertiesUsed,
  102. runtime
  103. )
  104. ) {
  105. const currentModule =
  106. currentExportsInfo === exportsInfo
  107. ? module
  108. : exportInfoToModuleMap.get(currentExportsInfo);
  109. if (currentModule) {
  110. queue.enqueue(currentModule, runtime);
  111. }
  112. }
  113. currentExportsInfo = nestedInfo;
  114. continue;
  115. }
  116. }
  117. if (
  118. exportInfo.setUsedConditionally(
  119. (v) => v !== UsageState.Used,
  120. UsageState.Used,
  121. runtime
  122. )
  123. ) {
  124. const currentModule =
  125. currentExportsInfo === exportsInfo
  126. ? module
  127. : exportInfoToModuleMap.get(currentExportsInfo);
  128. if (currentModule) {
  129. queue.enqueue(currentModule, runtime);
  130. }
  131. }
  132. break;
  133. }
  134. }
  135. }
  136. } else {
  137. // for a module without side effects we stop tracking usage here when no export is used
  138. // This module won't be evaluated in this case
  139. // TODO webpack 6 remove this check
  140. if (
  141. !forceSideEffects &&
  142. module.factoryMeta !== undefined &&
  143. module.factoryMeta.sideEffectFree
  144. ) {
  145. return;
  146. }
  147. if (exportsInfo.setUsedForSideEffectsOnly(runtime)) {
  148. queue.enqueue(module, runtime);
  149. }
  150. }
  151. };
  152. /**
  153. * @param {DependenciesBlock} module the module
  154. * @param {RuntimeSpec} runtime part of which runtime
  155. * @param {boolean} forceSideEffects always apply side effects
  156. * @returns {void}
  157. */
  158. const processModule = (module, runtime, forceSideEffects) => {
  159. /** @type {Map<Module, ReferencedExports | Map<string, string[] | ReferencedExport>>} */
  160. const map = new Map();
  161. /** @type {ArrayQueue<DependenciesBlock>} */
  162. const queue = new ArrayQueue();
  163. queue.enqueue(module);
  164. for (;;) {
  165. const block = queue.dequeue();
  166. if (block === undefined) break;
  167. for (const b of block.blocks) {
  168. if (
  169. !this.global &&
  170. b.groupOptions &&
  171. b.groupOptions.entryOptions
  172. ) {
  173. processModule(
  174. b,
  175. b.groupOptions.entryOptions.runtime || undefined,
  176. true
  177. );
  178. } else {
  179. queue.enqueue(b);
  180. }
  181. }
  182. for (const dep of block.dependencies) {
  183. const connection = moduleGraph.getConnection(dep);
  184. if (!connection || !connection.module) {
  185. continue;
  186. }
  187. const activeState = connection.getActiveState(runtime);
  188. if (activeState === false) continue;
  189. const { module } = connection;
  190. if (activeState === ModuleGraphConnection.TRANSITIVE_ONLY) {
  191. processModule(module, runtime, false);
  192. continue;
  193. }
  194. const oldReferencedExports = map.get(module);
  195. if (oldReferencedExports === EXPORTS_OBJECT_REFERENCED) {
  196. continue;
  197. }
  198. const referencedExports =
  199. compilation.getDependencyReferencedExports(dep, runtime);
  200. if (
  201. oldReferencedExports === undefined ||
  202. oldReferencedExports === NO_EXPORTS_REFERENCED ||
  203. referencedExports === EXPORTS_OBJECT_REFERENCED
  204. ) {
  205. map.set(module, referencedExports);
  206. } else if (
  207. oldReferencedExports !== undefined &&
  208. referencedExports === NO_EXPORTS_REFERENCED
  209. ) {
  210. continue;
  211. } else {
  212. let exportsMap;
  213. if (Array.isArray(oldReferencedExports)) {
  214. exportsMap = new Map();
  215. for (const item of oldReferencedExports) {
  216. if (Array.isArray(item)) {
  217. exportsMap.set(item.join("\n"), item);
  218. } else {
  219. exportsMap.set(item.name.join("\n"), item);
  220. }
  221. }
  222. map.set(module, exportsMap);
  223. } else {
  224. exportsMap = oldReferencedExports;
  225. }
  226. for (const item of referencedExports) {
  227. if (Array.isArray(item)) {
  228. const key = item.join("\n");
  229. const oldItem = exportsMap.get(key);
  230. if (oldItem === undefined) {
  231. exportsMap.set(key, item);
  232. }
  233. // if oldItem is already an array we have to do nothing
  234. // if oldItem is an ReferencedExport object, we don't have to do anything
  235. // as canMangle defaults to true for arrays
  236. } else {
  237. const key = item.name.join("\n");
  238. const oldItem = exportsMap.get(key);
  239. if (oldItem === undefined || Array.isArray(oldItem)) {
  240. exportsMap.set(key, item);
  241. } else {
  242. exportsMap.set(key, {
  243. name: item.name,
  244. canMangle: item.canMangle && oldItem.canMangle
  245. });
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. for (const [module, referencedExports] of map) {
  253. if (Array.isArray(referencedExports)) {
  254. processReferencedModule(
  255. module,
  256. referencedExports,
  257. runtime,
  258. forceSideEffects
  259. );
  260. } else {
  261. processReferencedModule(
  262. module,
  263. [...referencedExports.values()],
  264. runtime,
  265. forceSideEffects
  266. );
  267. }
  268. }
  269. };
  270. logger.time("initialize exports usage");
  271. for (const module of modules) {
  272. const exportsInfo = moduleGraph.getExportsInfo(module);
  273. exportInfoToModuleMap.set(exportsInfo, module);
  274. exportsInfo.setHasUseInfo();
  275. }
  276. logger.timeEnd("initialize exports usage");
  277. logger.time("trace exports usage in graph");
  278. /**
  279. * @param {Dependency} dep dependency
  280. * @param {RuntimeSpec} runtime runtime
  281. */
  282. const processEntryDependency = (dep, runtime) => {
  283. const module = moduleGraph.getModule(dep);
  284. if (module) {
  285. processReferencedModule(
  286. module,
  287. NO_EXPORTS_REFERENCED,
  288. runtime,
  289. true
  290. );
  291. }
  292. };
  293. /** @type {RuntimeSpec} */
  294. let globalRuntime;
  295. for (const [
  296. entryName,
  297. { dependencies: deps, includeDependencies: includeDeps, options }
  298. ] of compilation.entries) {
  299. const runtime = this.global
  300. ? undefined
  301. : getEntryRuntime(compilation, entryName, options);
  302. for (const dep of deps) {
  303. processEntryDependency(dep, runtime);
  304. }
  305. for (const dep of includeDeps) {
  306. processEntryDependency(dep, runtime);
  307. }
  308. globalRuntime = mergeRuntimeOwned(globalRuntime, runtime);
  309. }
  310. for (const dep of compilation.globalEntry.dependencies) {
  311. processEntryDependency(dep, globalRuntime);
  312. }
  313. for (const dep of compilation.globalEntry.includeDependencies) {
  314. processEntryDependency(dep, globalRuntime);
  315. }
  316. while (queue.length) {
  317. const [module, runtime] = /** @type {[Module, RuntimeSpec]} */ (
  318. queue.dequeue()
  319. );
  320. processModule(module, runtime, false);
  321. }
  322. logger.timeEnd("trace exports usage in graph");
  323. }
  324. );
  325. });
  326. }
  327. }
  328. module.exports = FlagDependencyUsagePlugin;