InnerGraphPlugin.js 12 KB

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