InnerGraphPlugin.js 12 KB

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