InnerGraph.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const { UsageState } = require("../ExportsInfo");
  7. const JavascriptParser = require("../javascript/JavascriptParser");
  8. /** @import Compilation from "../Compilation" */
  9. /** @import Dependency, { GetConditionFn } from "../Dependency" */
  10. /** @import Module from "../Module" */
  11. /** @import ModuleGraph from "../ModuleGraph" */
  12. /** @import { ParserState } from "../Parser" */
  13. /** @import { RuntimeSpec } from "../util/runtime" */
  14. /** @typedef {boolean | ((compilation: Compilation, module: Module) => boolean)} PureCondition */
  15. /** @typedef {Set<string | TopLevelSymbol>} InnerGraphValueSet */
  16. /** @typedef {InnerGraphValueSet | true} InnerGraphValue */
  17. /** @typedef {TopLevelSymbol | null} InnerGraphKey */
  18. /** @typedef {Map<InnerGraphKey, InnerGraphValue | undefined>} InnerGraph */
  19. /** @typedef {(value: boolean | Set<string> | undefined, module: Module) => void} UsageCallback */
  20. /**
  21. * Defines the state object type used by this module.
  22. * @typedef {object} StateObject
  23. * @property {InnerGraph} innerGraph
  24. * @property {TopLevelSymbol=} currentTopLevelSymbol
  25. * @property {Map<TopLevelSymbol, Set<UsageCallback>>} usageCallbackMap
  26. */
  27. /** @typedef {false | StateObject} State */
  28. /** @typedef {string | TopLevelSymbol | true} Usage */
  29. /** @typedef {Set<string> | boolean} UsedByExports */
  30. /** @typedef {Dependency & { usedByExports: UsedByExports | undefined }} DependencyWithUsedByExports */
  31. class TopLevelSymbol {
  32. /**
  33. * Creates an instance of TopLevelSymbol.
  34. * @param {string} name name of the variable
  35. * @param {PureCondition=} pure pure condition
  36. */
  37. constructor(name, pure = false) {
  38. /** @type {string} */
  39. this.name = name;
  40. /** @type {boolean} */
  41. this.conditional = false;
  42. /** @type {boolean} */
  43. this._pure = false;
  44. /** @type {((compilation: Compilation, module: Module) => boolean) | undefined} */
  45. this.pureFn = undefined;
  46. this.setPure(pure);
  47. }
  48. /**
  49. * Sets the pure condition
  50. * @param {PureCondition} pure pure condition
  51. * @returns {void}
  52. */
  53. setPure(pure) {
  54. this.conditional = typeof pure === "function";
  55. this._pure = pure === true;
  56. this.pureFn = typeof pure === "function" ? pure : undefined;
  57. }
  58. /**
  59. * @param {Compilation} compilation compilation
  60. * @param {Module} module module
  61. * @returns {boolean} if the symbol is pure
  62. */
  63. isPure(compilation, module) {
  64. if (!this.conditional) return this._pure;
  65. const pureFn =
  66. /** @type {(compilation: Compilation, module: Module) => boolean} */ (
  67. this.pureFn
  68. );
  69. return pureFn(compilation, module);
  70. }
  71. }
  72. module.exports.TopLevelSymbol = TopLevelSymbol;
  73. const topLevelSymbolTag = Symbol("top level symbol");
  74. /** @type {WeakMap<Compilation, InnerGraphUtils>} */
  75. const innerGraphByCompilation = new WeakMap();
  76. /**
  77. * @typedef {object} InnerGraphUtils
  78. * @property {(parserState: ParserState) => void} enable
  79. * @property {(parserState: ParserState) => void} bailout
  80. * @property {(parserState: ParserState) => boolean} isEnabled
  81. * @property {(parserState: ParserState, symbol: TopLevelSymbol | null, usage: Usage) => void} addUsage
  82. * @property {(parserState: ParserState, onUsageCallback: UsageCallback) => void} onUsage
  83. * @property {(parserState: ParserState, symbol: TopLevelSymbol | undefined) => void} setTopLevelSymbol
  84. * @property {(parserState: ParserState) => TopLevelSymbol | void} getTopLevelSymbol
  85. * @property {(parser: JavascriptParser, name: string, pure?: PureCondition) => TopLevelSymbol | undefined} tagTopLevelSymbol
  86. * @property {(parser: JavascriptParser, name: string, usage: Usage) => void} addVariableUsage
  87. * @property {(module: Module) => void} inferDependencyUsage
  88. * @property {(module: Module) => void} release
  89. */
  90. /**
  91. * Returns false, when unused. Otherwise true.
  92. * @param {Dependency} dependency the dependency
  93. * @param {UsedByExports | undefined} usedByExports usedByExports info
  94. * @param {ModuleGraph} moduleGraph moduleGraph
  95. * @param {RuntimeSpec} runtime runtime
  96. * @returns {boolean} false, when unused. Otherwise true
  97. */
  98. function isDependencyUsedByExports(
  99. dependency,
  100. usedByExports,
  101. moduleGraph,
  102. runtime
  103. ) {
  104. if (usedByExports === false) return false;
  105. if (usedByExports !== true && usedByExports !== undefined) {
  106. const selfModule =
  107. /** @type {Module} */
  108. (moduleGraph.getParentModule(dependency));
  109. const exportsInfo = moduleGraph.getExportsInfo(selfModule);
  110. for (const exportName of usedByExports) {
  111. if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) {
  112. return true;
  113. }
  114. }
  115. return false;
  116. }
  117. return true;
  118. }
  119. /**
  120. * One condition per module graph rather than per dependency. The dependency the
  121. * condition was built for is always the one on the connection it is called with
  122. * (`ModuleGraph#setResolvedModule` is the only caller of `getCondition`, and it
  123. * pairs the two), so it can be read off the connection instead of captured.
  124. * @type {WeakMap<ModuleGraph, GetConditionFn>}
  125. */
  126. const usedByExportsConditions = new WeakMap();
  127. /**
  128. * Returns dependency condition
  129. * @param {Dependency} dependency the dependency
  130. * @param {ModuleGraph} moduleGraph moduleGraph
  131. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  132. */
  133. module.exports.getDependencyUsedByExportsCondition = (
  134. dependency,
  135. moduleGraph
  136. ) => {
  137. switch (
  138. /** @type {DependencyWithUsedByExports} */ (dependency).usedByExports
  139. ) {
  140. case false:
  141. return false;
  142. case true:
  143. return null;
  144. default: {
  145. const existing = usedByExportsConditions.get(moduleGraph);
  146. if (existing !== undefined) return existing;
  147. /** @type {GetConditionFn} */
  148. const condition = (connection, runtime) => {
  149. const dep = /** @type {Dependency} */ (connection.dependency);
  150. return isDependencyUsedByExports(
  151. dep,
  152. /** @type {DependencyWithUsedByExports} */ (dep).usedByExports,
  153. moduleGraph,
  154. runtime
  155. );
  156. };
  157. usedByExportsConditions.set(moduleGraph, condition);
  158. return condition;
  159. }
  160. }
  161. };
  162. /**
  163. * Returns the InnerGraph utils scoped to a single compilation.
  164. * @param {Compilation} compilation the compilation
  165. * @returns {InnerGraphUtils} utils
  166. */
  167. module.exports.getInnerGraphUtils = (compilation) => {
  168. let utils = innerGraphByCompilation.get(compilation);
  169. if (utils) return utils;
  170. /** @type {WeakMap<Module, State>} */
  171. const states = new WeakMap();
  172. /**
  173. * @param {ParserState} parserState parser state
  174. * @returns {State | undefined} state
  175. */
  176. function getState(parserState) {
  177. return states.get(parserState.module);
  178. }
  179. /**
  180. * @param {ParserState} parserState parser state
  181. * @returns {void}
  182. */
  183. function enable(parserState) {
  184. const state = states.get(parserState.module);
  185. if (state === false) {
  186. return;
  187. }
  188. states.set(parserState.module, {
  189. innerGraph: new Map(),
  190. currentTopLevelSymbol: undefined,
  191. usageCallbackMap: new Map()
  192. });
  193. }
  194. /**
  195. * @param {ParserState} parserState parser state
  196. * @returns {void}
  197. */
  198. function bailout(parserState) {
  199. states.set(parserState.module, false);
  200. }
  201. /**
  202. * @param {ParserState} parserState parser state
  203. * @returns {boolean} true, when enabled
  204. */
  205. function isEnabled(parserState) {
  206. return Boolean(states.get(parserState.module));
  207. }
  208. /**
  209. * @param {ParserState} state parser state
  210. * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols
  211. * @param {Usage} usage usage data
  212. * @returns {void}
  213. */
  214. function addUsage(state, symbol, usage) {
  215. const innerGraphState = getState(state);
  216. if (innerGraphState) {
  217. const { innerGraph } = innerGraphState;
  218. const info = innerGraph.get(symbol);
  219. if (usage === true) {
  220. innerGraph.set(symbol, true);
  221. } else if (info === undefined) {
  222. innerGraph.set(symbol, new Set([usage]));
  223. } else if (info !== true) {
  224. info.add(usage);
  225. }
  226. }
  227. }
  228. /**
  229. * @param {JavascriptParser} parser the parser
  230. * @param {string} name name of variable
  231. * @param {Usage} usage usage data
  232. * @returns {void}
  233. */
  234. function addVariableUsage(parser, name, usage) {
  235. const symbol =
  236. /** @type {TopLevelSymbol} */ (
  237. parser.getTagData(name, topLevelSymbolTag)
  238. ) || tagTopLevelSymbol(parser, name);
  239. if (symbol) {
  240. addUsage(parser.state, symbol, usage);
  241. }
  242. }
  243. /**
  244. * @param {ParserState} state parser state
  245. * @returns {TopLevelSymbol | void} usage data
  246. */
  247. function getTopLevelSymbol(state) {
  248. const innerGraphState = getState(state);
  249. if (innerGraphState) {
  250. return innerGraphState.currentTopLevelSymbol;
  251. }
  252. }
  253. /**
  254. * @param {ParserState} state parser state
  255. * @param {TopLevelSymbol | undefined} symbol the symbol
  256. */
  257. function setTopLevelSymbol(state, symbol) {
  258. const innerGraphState = getState(state);
  259. if (innerGraphState) {
  260. innerGraphState.currentTopLevelSymbol = symbol;
  261. }
  262. }
  263. /**
  264. * @param {ParserState} state parser state
  265. * @param {UsageCallback} onUsageCallback on usage callback
  266. */
  267. function onUsage(state, onUsageCallback) {
  268. const innerGraphState = getState(state);
  269. if (innerGraphState) {
  270. const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState;
  271. if (currentTopLevelSymbol) {
  272. let callbacks = usageCallbackMap.get(currentTopLevelSymbol);
  273. if (callbacks === undefined) {
  274. /** @type {Set<UsageCallback>} */
  275. callbacks = new Set();
  276. usageCallbackMap.set(currentTopLevelSymbol, callbacks);
  277. }
  278. callbacks.add(onUsageCallback);
  279. } else {
  280. onUsageCallback(true, state.module);
  281. }
  282. } else {
  283. onUsageCallback(undefined, state.module);
  284. }
  285. }
  286. /**
  287. * @param {JavascriptParser} parser parser
  288. * @param {string} name name of variable
  289. * @param {PureCondition=} pure pure condition
  290. * @returns {TopLevelSymbol=} symbol
  291. */
  292. function tagTopLevelSymbol(parser, name, pure) {
  293. const innerGraphState = getState(parser.state);
  294. if (!innerGraphState) return;
  295. parser.defineVariable(name);
  296. const existingTag = /** @type {TopLevelSymbol} */ (
  297. parser.getTagData(name, topLevelSymbolTag)
  298. );
  299. if (existingTag) {
  300. if (pure !== undefined) {
  301. existingTag.setPure(pure);
  302. }
  303. return existingTag;
  304. }
  305. const symbol = new TopLevelSymbol(name, pure);
  306. parser.tagVariable(
  307. name,
  308. topLevelSymbolTag,
  309. symbol,
  310. JavascriptParser.VariableInfoFlags.Normal
  311. );
  312. return symbol;
  313. }
  314. /**
  315. * @param {Module} module module
  316. * @returns {void}
  317. */
  318. function inferDependencyUsage(module) {
  319. const innerGraphState = states.get(module);
  320. if (!innerGraphState) {
  321. return;
  322. }
  323. const { innerGraph, usageCallbackMap } = innerGraphState;
  324. /** @type {Map<InnerGraphKey, InnerGraphValueSet | undefined>} */
  325. const processed = new Map();
  326. // flatten graph to terminal nodes (string, undefined or true)
  327. const nonTerminal = new Set(innerGraph.keys());
  328. while (nonTerminal.size > 0) {
  329. for (const key of nonTerminal) {
  330. if (key !== null && !key.isPure(compilation, module)) {
  331. innerGraph.set(key, true);
  332. nonTerminal.delete(key);
  333. continue;
  334. }
  335. /** @type {InnerGraphValue} */
  336. let newSet = new Set();
  337. let isTerminal = true;
  338. const value = innerGraph.get(key);
  339. let alreadyProcessed = processed.get(key);
  340. if (alreadyProcessed === undefined) {
  341. /** @type {InnerGraphValueSet} */
  342. alreadyProcessed = new Set();
  343. processed.set(key, alreadyProcessed);
  344. }
  345. if (value !== true && value !== undefined) {
  346. for (const item of value) {
  347. alreadyProcessed.add(item);
  348. }
  349. for (const item of value) {
  350. if (typeof item === "string") {
  351. newSet.add(item);
  352. } else if (!item.isPure(compilation, module)) {
  353. newSet = true;
  354. break;
  355. } else {
  356. const itemValue = innerGraph.get(item);
  357. if (itemValue === true) {
  358. newSet = true;
  359. break;
  360. }
  361. if (itemValue !== undefined) {
  362. for (const i of itemValue) {
  363. if (i === key) continue;
  364. if (alreadyProcessed.has(i)) continue;
  365. newSet.add(i);
  366. if (typeof i !== "string") {
  367. isTerminal = false;
  368. }
  369. }
  370. }
  371. }
  372. }
  373. if (newSet === true) {
  374. innerGraph.set(key, true);
  375. } else if (newSet.size === 0) {
  376. innerGraph.set(key, undefined);
  377. } else {
  378. innerGraph.set(key, newSet);
  379. }
  380. }
  381. if (isTerminal) {
  382. nonTerminal.delete(key);
  383. // For the global key, merge with all other keys
  384. if (key === null) {
  385. const globalValue = innerGraph.get(null);
  386. if (globalValue) {
  387. for (const [key, value] of innerGraph) {
  388. if (key !== null && value !== true) {
  389. if (globalValue === true) {
  390. innerGraph.set(key, true);
  391. } else {
  392. const newSet = new Set(value);
  393. for (const item of globalValue) {
  394. newSet.add(item);
  395. }
  396. innerGraph.set(key, newSet);
  397. }
  398. }
  399. }
  400. }
  401. }
  402. }
  403. }
  404. }
  405. /** @type {Map<Dependency, true | Set<string>>} */
  406. for (const [symbol, callbacks] of usageCallbackMap) {
  407. const usage = symbol.isPure(compilation, module)
  408. ? /** @type {true | Set<string> | undefined} */ (innerGraph.get(symbol))
  409. : true;
  410. for (const callback of callbacks) {
  411. callback(usage === undefined ? false : usage, module);
  412. }
  413. }
  414. }
  415. /**
  416. * @param {Module} module module
  417. * @returns {void}
  418. */
  419. function release(module) {
  420. states.delete(module);
  421. }
  422. utils = {
  423. enable,
  424. bailout,
  425. isEnabled,
  426. addUsage,
  427. addVariableUsage,
  428. getTopLevelSymbol,
  429. setTopLevelSymbol,
  430. onUsage,
  431. tagTopLevelSymbol,
  432. inferDependencyUsage,
  433. release
  434. };
  435. innerGraphByCompilation.set(compilation, utils);
  436. return utils;
  437. };
  438. /**
  439. * Usage callback map.
  440. * @param {Dependency} dependency the dependency
  441. * @param {UsedByExports | undefined} usedByExports usedByExports info
  442. * @param {ModuleGraph} moduleGraph moduleGraph
  443. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  444. */
  445. module.exports.topLevelSymbolTag = topLevelSymbolTag;