| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const {	JAVASCRIPT_MODULE_TYPE_AUTO,	JAVASCRIPT_MODULE_TYPE_ESM} = require("../ModuleTypeConstants");const PureExpressionDependency = require("../dependencies/PureExpressionDependency");const InnerGraph = require("./InnerGraph");/** @typedef {import("estree").ClassDeclaration} ClassDeclaration *//** @typedef {import("estree").ClassExpression} ClassExpression *//** @typedef {import("estree").Expression} Expression *//** @typedef {import("estree").MaybeNamedClassDeclaration} MaybeNamedClassDeclaration *//** @typedef {import("estree").MaybeNamedFunctionDeclaration} MaybeNamedFunctionDeclaration *//** @typedef {import("estree").Node} Node *//** @typedef {import("estree").VariableDeclarator} VariableDeclarator *//** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions *//** @typedef {import("../Compiler")} Compiler *//** @typedef {import("../Dependency")} Dependency *//** @typedef {import("../Dependency").DependencyLocation} DependencyLocation *//** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency *//** @typedef {import("../javascript/JavascriptParser")} JavascriptParser *//** @typedef {import("../javascript/JavascriptParser").Range} Range *//** @typedef {import("./InnerGraph").InnerGraph} InnerGraph *//** @typedef {import("./InnerGraph").TopLevelSymbol} TopLevelSymbol */const { topLevelSymbolTag } = InnerGraph;const PLUGIN_NAME = "InnerGraphPlugin";class InnerGraphPlugin {	/**	 * Apply the plugin	 * @param {Compiler} compiler the compiler instance	 * @returns {void}	 */	apply(compiler) {		compiler.hooks.compilation.tap(			PLUGIN_NAME,			(compilation, { normalModuleFactory }) => {				const logger = compilation.getLogger("webpack.InnerGraphPlugin");				compilation.dependencyTemplates.set(					PureExpressionDependency,					new PureExpressionDependency.Template()				);				/**				 * @param {JavascriptParser} parser the parser				 * @param {JavascriptParserOptions} parserOptions options				 * @returns {void}				 */				const handler = (parser, parserOptions) => {					/**					 * @param {Expression} sup sup					 */					const onUsageSuper = sup => {						InnerGraph.onUsage(parser.state, usedByExports => {							switch (usedByExports) {								case undefined:								case true:									return;								default: {									const dep = new PureExpressionDependency(										/** @type {Range} */										(sup.range)									);									dep.loc = /** @type {DependencyLocation} */ (sup.loc);									dep.usedByExports = usedByExports;									parser.state.module.addDependency(dep);									break;								}							}						});					};					parser.hooks.program.tap(PLUGIN_NAME, () => {						InnerGraph.enable(parser.state);					});					parser.hooks.finish.tap(PLUGIN_NAME, () => {						if (!InnerGraph.isEnabled(parser.state)) return;						logger.time("infer dependency usage");						InnerGraph.inferDependencyUsage(parser.state);						logger.timeAggregate("infer dependency usage");					});					// During prewalking the following datastructures are filled with					// nodes that have a TopLevelSymbol assigned and					// variables are tagged with the assigned TopLevelSymbol					// We differ 3 types of nodes:					// 1. full statements (export default, function declaration)					// 2. classes (class declaration, class expression)					// 3. variable declarators (const x = ...)					/** @type {WeakMap<Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, TopLevelSymbol>} */					const statementWithTopLevelSymbol = new WeakMap();					/** @type {WeakMap<Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, Node>} */					const statementPurePart = new WeakMap();					/** @type {WeakMap<ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration, TopLevelSymbol>} */					const classWithTopLevelSymbol = new WeakMap();					/** @type {WeakMap<VariableDeclarator, TopLevelSymbol>} */					const declWithTopLevelSymbol = new WeakMap();					/** @type {WeakSet<VariableDeclarator>} */					const pureDeclarators = new WeakSet();					// The following hooks are used during prewalking:					parser.hooks.preStatement.tap(PLUGIN_NAME, statement => {						if (!InnerGraph.isEnabled(parser.state)) return;						if (							parser.scope.topLevelScope === true &&							statement.type === "FunctionDeclaration"						) {							const name = statement.id ? statement.id.name : "*default*";							const fn =								/** @type {TopLevelSymbol} */								(InnerGraph.tagTopLevelSymbol(parser, name));							statementWithTopLevelSymbol.set(statement, fn);							return true;						}					});					parser.hooks.blockPreStatement.tap(PLUGIN_NAME, statement => {						if (!InnerGraph.isEnabled(parser.state)) return;						if (parser.scope.topLevelScope === true) {							if (								statement.type === "ClassDeclaration" &&								parser.isPure(									statement,									/** @type {Range} */ (statement.range)[0]								)							) {								const name = statement.id ? statement.id.name : "*default*";								const fn = /** @type {TopLevelSymbol} */ (									InnerGraph.tagTopLevelSymbol(parser, name)								);								classWithTopLevelSymbol.set(statement, fn);								return true;							}							if (statement.type === "ExportDefaultDeclaration") {								const name = "*default*";								const fn =									/** @type {TopLevelSymbol} */									(InnerGraph.tagTopLevelSymbol(parser, name));								const decl = statement.declaration;								if (									(decl.type === "ClassExpression" ||										decl.type === "ClassDeclaration") &&									parser.isPure(										/** @type {ClassExpression | ClassDeclaration} */										(decl),										/** @type {Range} */										(decl.range)[0]									)								) {									classWithTopLevelSymbol.set(										/** @type {ClassExpression | ClassDeclaration} */										(decl),										fn									);								} else if (									parser.isPure(										/** @type {Expression} */										(decl),										/** @type {Range} */										(statement.range)[0]									)								) {									statementWithTopLevelSymbol.set(statement, fn);									if (										!decl.type.endsWith("FunctionExpression") &&										!decl.type.endsWith("Declaration") &&										decl.type !== "Literal"									) {										statementPurePart.set(											statement,											/** @type {Expression} */											(decl)										);									}								}							}						}					});					parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, _statement) => {						if (!InnerGraph.isEnabled(parser.state)) return;						if (							parser.scope.topLevelScope === true &&							decl.init &&							decl.id.type === "Identifier"						) {							const name = decl.id.name;							if (								decl.init.type === "ClassExpression" &&								parser.isPure(									decl.init,									/** @type {Range} */ (decl.id.range)[1]								)							) {								const fn =									/** @type {TopLevelSymbol} */									(InnerGraph.tagTopLevelSymbol(parser, name));								classWithTopLevelSymbol.set(decl.init, fn);							} else if (								parser.isPure(									decl.init,									/** @type {Range} */ (decl.id.range)[1]								)							) {								const fn =									/** @type {TopLevelSymbol} */									(InnerGraph.tagTopLevelSymbol(parser, name));								declWithTopLevelSymbol.set(decl, fn);								if (									!decl.init.type.endsWith("FunctionExpression") &&									decl.init.type !== "Literal"								) {									pureDeclarators.add(decl);								}							}						}					});					// During real walking we set the TopLevelSymbol state to the assigned					// TopLevelSymbol by using the fill datastructures.					// In addition to tracking TopLevelSymbols, we sometimes need to					// add a PureExpressionDependency. This is needed to skip execution					// of pure expressions, even when they are not dropped due to					// minimizing. Otherwise symbols used there might not exist anymore					// as they are removed as unused by this optimization					// When we find a reference to a TopLevelSymbol, we register a					// TopLevelSymbol dependency from TopLevelSymbol in state to the					// referenced TopLevelSymbol. This way we get a graph of all					// TopLevelSymbols.					// The following hooks are called during walking:					parser.hooks.statement.tap(PLUGIN_NAME, statement => {						if (!InnerGraph.isEnabled(parser.state)) return;						if (parser.scope.topLevelScope === true) {							InnerGraph.setTopLevelSymbol(parser.state, undefined);							const fn = statementWithTopLevelSymbol.get(statement);							if (fn) {								InnerGraph.setTopLevelSymbol(parser.state, fn);								const purePart = statementPurePart.get(statement);								if (purePart) {									InnerGraph.onUsage(parser.state, usedByExports => {										switch (usedByExports) {											case undefined:											case true:												return;											default: {												const dep = new PureExpressionDependency(													/** @type {Range} */ (purePart.range)												);												dep.loc =													/** @type {DependencyLocation} */													(statement.loc);												dep.usedByExports = usedByExports;												parser.state.module.addDependency(dep);												break;											}										}									});								}							}						}					});					parser.hooks.classExtendsExpression.tap(						PLUGIN_NAME,						(expr, statement) => {							if (!InnerGraph.isEnabled(parser.state)) return;							if (parser.scope.topLevelScope === true) {								const fn = classWithTopLevelSymbol.get(statement);								if (									fn &&									parser.isPure(										expr,										statement.id											? /** @type {Range} */ (statement.id.range)[1]											: /** @type {Range} */ (statement.range)[0]									)								) {									InnerGraph.setTopLevelSymbol(parser.state, fn);									onUsageSuper(expr);								}							}						}					);					parser.hooks.classBodyElement.tap(						PLUGIN_NAME,						(element, classDefinition) => {							if (!InnerGraph.isEnabled(parser.state)) return;							if (parser.scope.topLevelScope === true) {								const fn = classWithTopLevelSymbol.get(classDefinition);								if (fn) {									InnerGraph.setTopLevelSymbol(parser.state, undefined);								}							}						}					);					parser.hooks.classBodyValue.tap(						PLUGIN_NAME,						(expression, element, classDefinition) => {							if (!InnerGraph.isEnabled(parser.state)) return;							if (parser.scope.topLevelScope === true) {								const fn = classWithTopLevelSymbol.get(classDefinition);								if (fn) {									if (										!element.static ||										parser.isPure(											expression,											element.key												? /** @type {Range} */ (element.key.range)[1]												: /** @type {Range} */ (element.range)[0]										)									) {										InnerGraph.setTopLevelSymbol(parser.state, fn);										if (element.type !== "MethodDefinition" && element.static) {											InnerGraph.onUsage(parser.state, usedByExports => {												switch (usedByExports) {													case undefined:													case true:														return;													default: {														const dep = new PureExpressionDependency(															/** @type {Range} */ (expression.range)														);														dep.loc =															/** @type {DependencyLocation} */															(expression.loc);														dep.usedByExports = usedByExports;														parser.state.module.addDependency(dep);														break;													}												}											});										}									} else {										InnerGraph.setTopLevelSymbol(parser.state, undefined);									}								}							}						}					);					parser.hooks.declarator.tap(PLUGIN_NAME, (decl, _statement) => {						if (!InnerGraph.isEnabled(parser.state)) return;						const fn = declWithTopLevelSymbol.get(decl);						if (fn) {							InnerGraph.setTopLevelSymbol(parser.state, fn);							if (pureDeclarators.has(decl)) {								if (									/** @type {ClassExpression} */									(decl.init).type === "ClassExpression"								) {									if (decl.init.superClass) {										onUsageSuper(decl.init.superClass);									}								} else {									InnerGraph.onUsage(parser.state, usedByExports => {										switch (usedByExports) {											case undefined:											case true:												return;											default: {												const dep = new PureExpressionDependency(													/** @type {Range} */ (														/** @type {ClassExpression} */														(decl.init).range													)												);												dep.loc = /** @type {DependencyLocation} */ (decl.loc);												dep.usedByExports = usedByExports;												parser.state.module.addDependency(dep);												break;											}										}									});								}							}							parser.walkExpression(								/** @type {NonNullable<VariableDeclarator["init"]>} */ (									decl.init								)							);							InnerGraph.setTopLevelSymbol(parser.state, undefined);							return true;						} else if (							decl.id.type === "Identifier" &&							decl.init &&							decl.init.type === "ClassExpression" &&							classWithTopLevelSymbol.has(decl.init)						) {							parser.walkExpression(decl.init);							InnerGraph.setTopLevelSymbol(parser.state, undefined);							return true;						}					});					parser.hooks.expression						.for(topLevelSymbolTag)						.tap(PLUGIN_NAME, () => {							const topLevelSymbol = /** @type {TopLevelSymbol} */ (								parser.currentTagData							);							const currentTopLevelSymbol = InnerGraph.getTopLevelSymbol(								parser.state							);							InnerGraph.addUsage(								parser.state,								topLevelSymbol,								currentTopLevelSymbol || true							);						});					parser.hooks.assign.for(topLevelSymbolTag).tap(PLUGIN_NAME, expr => {						if (!InnerGraph.isEnabled(parser.state)) return;						if (expr.operator === "=") return true;					});				};				normalModuleFactory.hooks.parser					.for(JAVASCRIPT_MODULE_TYPE_AUTO)					.tap(PLUGIN_NAME, handler);				normalModuleFactory.hooks.parser					.for(JAVASCRIPT_MODULE_TYPE_ESM)					.tap(PLUGIN_NAME, handler);				compilation.hooks.finishModules.tap(PLUGIN_NAME, () => {					logger.timeAggregateEnd("infer dependency usage");				});			}		);	}}module.exports = InnerGraphPlugin;
 |