InnerGraphPlugin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const {
  7. JAVASCRIPT_MODULE_TYPE_AUTO,
  8. JAVASCRIPT_MODULE_TYPE_ESM
  9. } = require("../ModuleTypeConstants");
  10. const PureExpressionDependency = require("../dependencies/PureExpressionDependency");
  11. const InnerGraph = require("./InnerGraph");
  12. /** @typedef {import("estree").ClassDeclaration} ClassDeclaration */
  13. /** @typedef {import("estree").ClassExpression} ClassExpression */
  14. /** @typedef {import("estree").Expression} Expression */
  15. /** @typedef {import("estree").MaybeNamedClassDeclaration} MaybeNamedClassDeclaration */
  16. /** @typedef {import("estree").MaybeNamedFunctionDeclaration} MaybeNamedFunctionDeclaration */
  17. /** @typedef {import("estree").Node} Node */
  18. /** @typedef {import("estree").VariableDeclarator} VariableDeclarator */
  19. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  20. /** @typedef {import("../Compiler")} Compiler */
  21. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  22. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  23. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  24. /** @typedef {import("./InnerGraph").TopLevelSymbol} TopLevelSymbol */
  25. const { topLevelSymbolTag } = InnerGraph;
  26. const PLUGIN_NAME = "InnerGraphPlugin";
  27. class InnerGraphPlugin {
  28. /**
  29. * Applies the plugin by registering its hooks on the compiler.
  30. * @param {Compiler} compiler the compiler instance
  31. * @returns {void}
  32. */
  33. apply(compiler) {
  34. compiler.hooks.compilation.tap(
  35. PLUGIN_NAME,
  36. (compilation, { normalModuleFactory }) => {
  37. const logger = compilation.getLogger("webpack.InnerGraphPlugin");
  38. compilation.dependencyTemplates.set(
  39. PureExpressionDependency,
  40. new PureExpressionDependency.Template()
  41. );
  42. /**
  43. * Handles the hook callback for this code path.
  44. * @param {JavascriptParser} parser the parser
  45. * @param {JavascriptParserOptions} parserOptions options
  46. * @returns {void}
  47. */
  48. const handler = (parser, parserOptions) => {
  49. /**
  50. * Processes the provided sup.
  51. * @param {Expression} sup sup
  52. */
  53. const onUsageSuper = (sup) => {
  54. InnerGraph.onUsage(parser.state, (usedByExports) => {
  55. switch (usedByExports) {
  56. case undefined:
  57. case true:
  58. return;
  59. default: {
  60. const dep = new PureExpressionDependency(
  61. /** @type {Range} */
  62. (sup.range)
  63. );
  64. dep.loc = /** @type {DependencyLocation} */ (sup.loc);
  65. dep.usedByExports = usedByExports;
  66. parser.state.module.addDependency(dep);
  67. break;
  68. }
  69. }
  70. });
  71. };
  72. parser.hooks.program.tap(PLUGIN_NAME, () => {
  73. InnerGraph.enable(parser.state);
  74. statementWithTopLevelSymbol = new WeakMap();
  75. statementPurePart = new WeakMap();
  76. classWithTopLevelSymbol = new WeakMap();
  77. declWithTopLevelSymbol = new WeakMap();
  78. pureDeclarators = new WeakSet();
  79. });
  80. parser.hooks.finish.tap(PLUGIN_NAME, () => {
  81. if (!InnerGraph.isEnabled(parser.state)) return;
  82. logger.time("infer dependency usage");
  83. InnerGraph.inferDependencyUsage(parser.state);
  84. logger.timeAggregate("infer dependency usage");
  85. });
  86. // During prewalking the following datastructures are filled with
  87. // nodes that have a TopLevelSymbol assigned and
  88. // variables are tagged with the assigned TopLevelSymbol
  89. // We differ 3 types of nodes:
  90. // 1. full statements (export default, function declaration)
  91. // 2. classes (class declaration, class expression)
  92. // 3. variable declarators (const x = ...)
  93. /** @type {WeakMap<Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, TopLevelSymbol>} */
  94. let statementWithTopLevelSymbol = new WeakMap();
  95. /** @type {WeakMap<Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, Node>} */
  96. let statementPurePart = new WeakMap();
  97. /** @type {WeakMap<ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration, TopLevelSymbol>} */
  98. let classWithTopLevelSymbol = new WeakMap();
  99. /** @type {WeakMap<VariableDeclarator, TopLevelSymbol>} */
  100. let declWithTopLevelSymbol = new WeakMap();
  101. /** @type {WeakSet<VariableDeclarator>} */
  102. let pureDeclarators = new WeakSet();
  103. // The following hooks are used during prewalking:
  104. parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => {
  105. if (!InnerGraph.isEnabled(parser.state)) return;
  106. if (
  107. parser.scope.topLevelScope === true &&
  108. statement.type === "FunctionDeclaration"
  109. ) {
  110. const name = statement.id ? statement.id.name : "*default*";
  111. const symbol =
  112. /** @type {TopLevelSymbol} */
  113. (InnerGraph.tagTopLevelSymbol(parser, name));
  114. statementWithTopLevelSymbol.set(statement, symbol);
  115. return true;
  116. }
  117. });
  118. parser.hooks.blockPreStatement.tap(PLUGIN_NAME, (statement) => {
  119. if (!InnerGraph.isEnabled(parser.state)) return;
  120. if (parser.scope.topLevelScope === true) {
  121. if (
  122. statement.type === "ClassDeclaration" &&
  123. parser.isPure(
  124. statement,
  125. /** @type {Range} */ (statement.range)[0]
  126. )
  127. ) {
  128. const name = statement.id ? statement.id.name : "*default*";
  129. const symbol = /** @type {TopLevelSymbol} */ (
  130. InnerGraph.tagTopLevelSymbol(parser, name)
  131. );
  132. classWithTopLevelSymbol.set(statement, symbol);
  133. return true;
  134. }
  135. if (statement.type === "ExportDefaultDeclaration") {
  136. const name = "*default*";
  137. const symbol =
  138. /** @type {TopLevelSymbol} */
  139. (InnerGraph.tagTopLevelSymbol(parser, name));
  140. const decl = statement.declaration;
  141. if (
  142. (decl.type === "ClassExpression" ||
  143. decl.type === "ClassDeclaration") &&
  144. parser.isPure(
  145. /** @type {ClassExpression | ClassDeclaration} */
  146. (decl),
  147. /** @type {Range} */
  148. (decl.range)[0]
  149. )
  150. ) {
  151. classWithTopLevelSymbol.set(
  152. /** @type {ClassExpression | ClassDeclaration} */
  153. (decl),
  154. symbol
  155. );
  156. } else if (
  157. parser.isPure(
  158. /** @type {Expression} */
  159. (decl),
  160. /** @type {Range} */
  161. (statement.range)[0]
  162. )
  163. ) {
  164. statementWithTopLevelSymbol.set(statement, symbol);
  165. if (
  166. !decl.type.endsWith("FunctionExpression") &&
  167. !decl.type.endsWith("Declaration") &&
  168. decl.type !== "Literal"
  169. ) {
  170. statementPurePart.set(
  171. statement,
  172. /** @type {Expression} */
  173. (decl)
  174. );
  175. }
  176. }
  177. }
  178. }
  179. });
  180. parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, _statement) => {
  181. if (!InnerGraph.isEnabled(parser.state)) return;
  182. if (
  183. parser.scope.topLevelScope === true &&
  184. decl.init &&
  185. decl.id.type === "Identifier"
  186. ) {
  187. const name = decl.id.name;
  188. if (
  189. decl.init.type === "ClassExpression" &&
  190. parser.isPure(
  191. decl.init,
  192. /** @type {Range} */ (decl.id.range)[1]
  193. )
  194. ) {
  195. const symbol =
  196. /** @type {TopLevelSymbol} */
  197. (InnerGraph.tagTopLevelSymbol(parser, name));
  198. classWithTopLevelSymbol.set(decl.init, symbol);
  199. } else if (
  200. parser.isPure(
  201. decl.init,
  202. /** @type {Range} */ (decl.id.range)[1]
  203. )
  204. ) {
  205. const symbol =
  206. /** @type {TopLevelSymbol} */
  207. (InnerGraph.tagTopLevelSymbol(parser, name));
  208. declWithTopLevelSymbol.set(decl, symbol);
  209. if (
  210. !decl.init.type.endsWith("FunctionExpression") &&
  211. decl.init.type !== "Literal"
  212. ) {
  213. pureDeclarators.add(decl);
  214. }
  215. }
  216. }
  217. });
  218. // During real walking we set the TopLevelSymbol state to the assigned
  219. // TopLevelSymbol by using the fill datastructures.
  220. // In addition to tracking TopLevelSymbols, we sometimes need to
  221. // add a PureExpressionDependency. This is needed to skip execution
  222. // of pure expressions, even when they are not dropped due to
  223. // minimizing. Otherwise symbols used there might not exist anymore
  224. // as they are removed as unused by this optimization
  225. // When we find a reference to a TopLevelSymbol, we register a
  226. // TopLevelSymbol dependency from TopLevelSymbol in state to the
  227. // referenced TopLevelSymbol. This way we get a graph of all
  228. // TopLevelSymbols.
  229. // The following hooks are called during walking:
  230. parser.hooks.statement.tap(PLUGIN_NAME, (statement) => {
  231. if (!InnerGraph.isEnabled(parser.state)) return;
  232. if (parser.scope.topLevelScope === true) {
  233. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  234. const symbol = statementWithTopLevelSymbol.get(statement);
  235. if (symbol) {
  236. InnerGraph.setTopLevelSymbol(parser.state, symbol);
  237. const purePart = statementPurePart.get(statement);
  238. if (purePart) {
  239. InnerGraph.onUsage(parser.state, (usedByExports) => {
  240. switch (usedByExports) {
  241. case undefined:
  242. case true:
  243. return;
  244. default: {
  245. const dep = new PureExpressionDependency(
  246. /** @type {Range} */ (purePart.range)
  247. );
  248. dep.loc =
  249. /** @type {DependencyLocation} */
  250. (statement.loc);
  251. dep.usedByExports = usedByExports;
  252. parser.state.module.addDependency(dep);
  253. break;
  254. }
  255. }
  256. });
  257. }
  258. }
  259. }
  260. });
  261. parser.hooks.classExtendsExpression.tap(
  262. PLUGIN_NAME,
  263. (expr, statement) => {
  264. if (!InnerGraph.isEnabled(parser.state)) return;
  265. if (parser.scope.topLevelScope === true) {
  266. const symbol = classWithTopLevelSymbol.get(statement);
  267. if (
  268. symbol &&
  269. parser.isPure(
  270. expr,
  271. statement.id
  272. ? /** @type {Range} */ (statement.id.range)[1]
  273. : /** @type {Range} */ (statement.range)[0]
  274. )
  275. ) {
  276. InnerGraph.setTopLevelSymbol(parser.state, symbol);
  277. onUsageSuper(expr);
  278. }
  279. }
  280. }
  281. );
  282. parser.hooks.classBodyElement.tap(
  283. PLUGIN_NAME,
  284. (element, classDefinition) => {
  285. if (!InnerGraph.isEnabled(parser.state)) return;
  286. if (parser.scope.topLevelScope === true) {
  287. const symbol = classWithTopLevelSymbol.get(classDefinition);
  288. if (symbol) {
  289. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  290. }
  291. }
  292. }
  293. );
  294. parser.hooks.classBodyValue.tap(
  295. PLUGIN_NAME,
  296. (expression, element, classDefinition) => {
  297. if (!InnerGraph.isEnabled(parser.state)) return;
  298. if (parser.scope.topLevelScope === true) {
  299. const symbol = classWithTopLevelSymbol.get(classDefinition);
  300. if (symbol) {
  301. if (
  302. !element.static ||
  303. parser.isPure(
  304. expression,
  305. element.key
  306. ? /** @type {Range} */ (element.key.range)[1]
  307. : /** @type {Range} */ (element.range)[0]
  308. )
  309. ) {
  310. InnerGraph.setTopLevelSymbol(parser.state, symbol);
  311. if (element.type !== "MethodDefinition" && element.static) {
  312. InnerGraph.onUsage(parser.state, (usedByExports) => {
  313. switch (usedByExports) {
  314. case undefined:
  315. case true:
  316. return;
  317. default: {
  318. const dep = new PureExpressionDependency(
  319. /** @type {Range} */ (expression.range)
  320. );
  321. dep.loc =
  322. /** @type {DependencyLocation} */
  323. (expression.loc);
  324. dep.usedByExports = usedByExports;
  325. parser.state.module.addDependency(dep);
  326. break;
  327. }
  328. }
  329. });
  330. }
  331. } else {
  332. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  333. }
  334. }
  335. }
  336. }
  337. );
  338. parser.hooks.declarator.tap(PLUGIN_NAME, (decl, _statement) => {
  339. if (!InnerGraph.isEnabled(parser.state)) return;
  340. const symbol = declWithTopLevelSymbol.get(decl);
  341. if (symbol) {
  342. InnerGraph.setTopLevelSymbol(parser.state, symbol);
  343. if (pureDeclarators.has(decl)) {
  344. if (
  345. /** @type {ClassExpression} */
  346. (decl.init).type === "ClassExpression"
  347. ) {
  348. if (decl.init.superClass) {
  349. onUsageSuper(decl.init.superClass);
  350. }
  351. } else {
  352. InnerGraph.onUsage(parser.state, (usedByExports) => {
  353. switch (usedByExports) {
  354. case undefined:
  355. case true:
  356. return;
  357. default: {
  358. const dep = new PureExpressionDependency(
  359. /** @type {Range} */ (
  360. /** @type {ClassExpression} */
  361. (decl.init).range
  362. )
  363. );
  364. dep.loc = /** @type {DependencyLocation} */ (decl.loc);
  365. dep.usedByExports = usedByExports;
  366. parser.state.module.addDependency(dep);
  367. break;
  368. }
  369. }
  370. });
  371. }
  372. }
  373. parser.walkExpression(
  374. /** @type {NonNullable<VariableDeclarator["init"]>} */ (
  375. decl.init
  376. )
  377. );
  378. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  379. return true;
  380. } else if (
  381. decl.id.type === "Identifier" &&
  382. decl.init &&
  383. decl.init.type === "ClassExpression" &&
  384. classWithTopLevelSymbol.has(decl.init)
  385. ) {
  386. parser.walkExpression(decl.init);
  387. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  388. return true;
  389. }
  390. });
  391. parser.hooks.expression
  392. .for(topLevelSymbolTag)
  393. .tap(PLUGIN_NAME, () => {
  394. const topLevelSymbol = /** @type {TopLevelSymbol} */ (
  395. parser.currentTagData
  396. );
  397. const currentTopLevelSymbol = InnerGraph.getTopLevelSymbol(
  398. parser.state
  399. );
  400. InnerGraph.addUsage(
  401. parser.state,
  402. topLevelSymbol,
  403. currentTopLevelSymbol || true
  404. );
  405. });
  406. parser.hooks.assign
  407. .for(topLevelSymbolTag)
  408. .tap(PLUGIN_NAME, (expr) => {
  409. if (!InnerGraph.isEnabled(parser.state)) return;
  410. if (expr.operator === "=") return true;
  411. });
  412. };
  413. normalModuleFactory.hooks.parser
  414. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  415. .tap(PLUGIN_NAME, handler);
  416. normalModuleFactory.hooks.parser
  417. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  418. .tap(PLUGIN_NAME, handler);
  419. compilation.hooks.finishModules.tap(PLUGIN_NAME, () => {
  420. logger.timeAggregateEnd("infer dependency usage");
  421. });
  422. }
  423. );
  424. }
  425. }
  426. module.exports = InnerGraphPlugin;