InnerGraphPlugin.js 14 KB

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