InnerGraphPlugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. return true;
  159. }
  160. }
  161. });
  162. // During real walking we set the TopLevelSymbol state to the assigned
  163. // TopLevelSymbol by using the fill datastructures.
  164. // In addition to tracking TopLevelSymbols, we sometimes need to
  165. // add a PureExpressionDependency. This is needed to skip execution
  166. // of pure expressions, even when they are not dropped due to
  167. // minimizing. Otherwise symbols used there might not exist anymore
  168. // as they are removed as unused by this optimization
  169. // When we find a reference to a TopLevelSymbol, we register a
  170. // TopLevelSymbol dependency from TopLevelSymbol in state to the
  171. // referenced TopLevelSymbol. This way we get a graph of all
  172. // TopLevelSymbols.
  173. // The following hooks are called during walking:
  174. parser.hooks.statement.tap(PLUGIN_NAME, statement => {
  175. if (!InnerGraph.isEnabled(parser.state)) return;
  176. if (parser.scope.topLevelScope === true) {
  177. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  178. const fn = statementWithTopLevelSymbol.get(statement);
  179. if (fn) {
  180. InnerGraph.setTopLevelSymbol(parser.state, fn);
  181. const purePart = statementPurePart.get(statement);
  182. if (purePart) {
  183. InnerGraph.onUsage(parser.state, usedByExports => {
  184. switch (usedByExports) {
  185. case undefined:
  186. case true:
  187. return;
  188. default: {
  189. const dep = new PureExpressionDependency(
  190. purePart.range
  191. );
  192. dep.loc = statement.loc;
  193. dep.usedByExports = usedByExports;
  194. parser.state.module.addDependency(dep);
  195. break;
  196. }
  197. }
  198. });
  199. }
  200. }
  201. }
  202. });
  203. parser.hooks.classExtendsExpression.tap(
  204. PLUGIN_NAME,
  205. (expr, statement) => {
  206. if (!InnerGraph.isEnabled(parser.state)) return;
  207. if (parser.scope.topLevelScope === true) {
  208. const fn = classWithTopLevelSymbol.get(statement);
  209. if (
  210. fn &&
  211. parser.isPure(
  212. expr,
  213. statement.id ? statement.id.range[1] : statement.range[0]
  214. )
  215. ) {
  216. InnerGraph.setTopLevelSymbol(parser.state, fn);
  217. onUsageSuper(expr);
  218. }
  219. }
  220. }
  221. );
  222. parser.hooks.classBodyElement.tap(
  223. PLUGIN_NAME,
  224. (element, classDefinition) => {
  225. if (!InnerGraph.isEnabled(parser.state)) return;
  226. if (parser.scope.topLevelScope === true) {
  227. const fn = classWithTopLevelSymbol.get(classDefinition);
  228. if (fn) {
  229. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  230. }
  231. }
  232. }
  233. );
  234. parser.hooks.classBodyValue.tap(
  235. PLUGIN_NAME,
  236. (expression, element, classDefinition) => {
  237. if (!InnerGraph.isEnabled(parser.state)) return;
  238. if (parser.scope.topLevelScope === true) {
  239. const fn = classWithTopLevelSymbol.get(classDefinition);
  240. if (fn) {
  241. if (
  242. !element.static ||
  243. parser.isPure(
  244. expression,
  245. element.key ? element.key.range[1] : element.range[0]
  246. )
  247. ) {
  248. InnerGraph.setTopLevelSymbol(parser.state, fn);
  249. if (element.type !== "MethodDefinition" && element.static) {
  250. InnerGraph.onUsage(parser.state, usedByExports => {
  251. switch (usedByExports) {
  252. case undefined:
  253. case true:
  254. return;
  255. default: {
  256. const dep = new PureExpressionDependency(
  257. expression.range
  258. );
  259. dep.loc = expression.loc;
  260. dep.usedByExports = usedByExports;
  261. parser.state.module.addDependency(dep);
  262. break;
  263. }
  264. }
  265. });
  266. }
  267. } else {
  268. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  269. }
  270. }
  271. }
  272. }
  273. );
  274. parser.hooks.declarator.tap(PLUGIN_NAME, (decl, statement) => {
  275. if (!InnerGraph.isEnabled(parser.state)) return;
  276. const fn = declWithTopLevelSymbol.get(decl);
  277. if (fn) {
  278. InnerGraph.setTopLevelSymbol(parser.state, fn);
  279. if (pureDeclarators.has(decl)) {
  280. if (decl.init.type === "ClassExpression") {
  281. if (decl.init.superClass) {
  282. onUsageSuper(decl.init.superClass);
  283. }
  284. } else {
  285. InnerGraph.onUsage(parser.state, usedByExports => {
  286. switch (usedByExports) {
  287. case undefined:
  288. case true:
  289. return;
  290. default: {
  291. const dep = new PureExpressionDependency(
  292. decl.init.range
  293. );
  294. dep.loc = decl.loc;
  295. dep.usedByExports = usedByExports;
  296. parser.state.module.addDependency(dep);
  297. break;
  298. }
  299. }
  300. });
  301. }
  302. }
  303. parser.walkExpression(decl.init);
  304. InnerGraph.setTopLevelSymbol(parser.state, undefined);
  305. return true;
  306. }
  307. });
  308. parser.hooks.expression
  309. .for(topLevelSymbolTag)
  310. .tap(PLUGIN_NAME, () => {
  311. const topLevelSymbol = /** @type {TopLevelSymbol} */ (
  312. parser.currentTagData
  313. );
  314. const currentTopLevelSymbol = InnerGraph.getTopLevelSymbol(
  315. parser.state
  316. );
  317. InnerGraph.addUsage(
  318. parser.state,
  319. topLevelSymbol,
  320. currentTopLevelSymbol || true
  321. );
  322. });
  323. parser.hooks.assign.for(topLevelSymbolTag).tap(PLUGIN_NAME, expr => {
  324. if (!InnerGraph.isEnabled(parser.state)) return;
  325. if (expr.operator === "=") return true;
  326. });
  327. };
  328. normalModuleFactory.hooks.parser
  329. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  330. .tap(PLUGIN_NAME, handler);
  331. normalModuleFactory.hooks.parser
  332. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  333. .tap(PLUGIN_NAME, handler);
  334. compilation.hooks.finishModules.tap(PLUGIN_NAME, () => {
  335. logger.timeAggregateEnd("infer dependency usage");
  336. });
  337. }
  338. );
  339. }
  340. }
  341. module.exports = InnerGraphPlugin;