You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			378 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	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} ClassDeclarationNode */ | ||
|  | /** @typedef {import("estree").ClassExpression} ClassExpressionNode */ | ||
|  | /** @typedef {import("estree").Node} Node */ | ||
|  | /** @typedef {import("estree").VariableDeclarator} VariableDeclaratorNode */ | ||
|  | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ | ||
|  | /** @typedef {import("../Compiler")} Compiler */ | ||
|  | /** @typedef {import("../Dependency")} Dependency */ | ||
|  | /** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */ | ||
|  | /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ | ||
|  | /** @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) => { | ||
|  | 					const onUsageSuper = sup => { | ||
|  | 						InnerGraph.onUsage(parser.state, usedByExports => { | ||
|  | 							switch (usedByExports) { | ||
|  | 								case undefined: | ||
|  | 								case true: | ||
|  | 									return; | ||
|  | 								default: { | ||
|  | 									const dep = new PureExpressionDependency(sup.range); | ||
|  | 									dep.loc = 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, TopLevelSymbol>} */ | ||
|  | 					const statementWithTopLevelSymbol = new WeakMap(); | ||
|  | 					/** @type {WeakMap<Node, Node>} */ | ||
|  | 					const statementPurePart = new WeakMap(); | ||
|  | 
 | ||
|  | 					/** @type {WeakMap<ClassExpressionNode | ClassDeclarationNode, TopLevelSymbol>} */ | ||
|  | 					const classWithTopLevelSymbol = new WeakMap(); | ||
|  | 
 | ||
|  | 					/** @type {WeakMap<VariableDeclaratorNode, TopLevelSymbol>} */ | ||
|  | 					const declWithTopLevelSymbol = new WeakMap(); | ||
|  | 					/** @type {WeakSet<VariableDeclaratorNode>} */ | ||
|  | 					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) { | ||
|  | 							if (statement.type === "FunctionDeclaration") { | ||
|  | 								const name = statement.id ? statement.id.name : "*default*"; | ||
|  | 								const fn = 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, statement.range[0]) | ||
|  | 							) { | ||
|  | 								const name = statement.id ? statement.id.name : "*default*"; | ||
|  | 								const fn = InnerGraph.tagTopLevelSymbol(parser, name); | ||
|  | 								classWithTopLevelSymbol.set(statement, fn); | ||
|  | 								return true; | ||
|  | 							} | ||
|  | 							if (statement.type === "ExportDefaultDeclaration") { | ||
|  | 								const name = "*default*"; | ||
|  | 								const fn = InnerGraph.tagTopLevelSymbol(parser, name); | ||
|  | 								const decl = statement.declaration; | ||
|  | 								if ( | ||
|  | 									(decl.type === "ClassExpression" || | ||
|  | 										decl.type === "ClassDeclaration") && | ||
|  | 									parser.isPure(decl, decl.range[0]) | ||
|  | 								) { | ||
|  | 									classWithTopLevelSymbol.set(decl, fn); | ||
|  | 								} else if (parser.isPure(decl, statement.range[0])) { | ||
|  | 									statementWithTopLevelSymbol.set(statement, fn); | ||
|  | 									if ( | ||
|  | 										!decl.type.endsWith("FunctionExpression") && | ||
|  | 										!decl.type.endsWith("Declaration") && | ||
|  | 										decl.type !== "Literal" | ||
|  | 									) { | ||
|  | 										statementPurePart.set(statement, 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, decl.id.range[1]) | ||
|  | 							) { | ||
|  | 								const fn = InnerGraph.tagTopLevelSymbol(parser, name); | ||
|  | 								classWithTopLevelSymbol.set(decl.init, fn); | ||
|  | 							} else if (parser.isPure(decl.init, decl.id.range[1])) { | ||
|  | 								const fn = InnerGraph.tagTopLevelSymbol(parser, name); | ||
|  | 								declWithTopLevelSymbol.set(decl, fn); | ||
|  | 								if ( | ||
|  | 									!decl.init.type.endsWith("FunctionExpression") && | ||
|  | 									decl.init.type !== "Literal" | ||
|  | 								) { | ||
|  | 									pureDeclarators.add(decl); | ||
|  | 								} | ||
|  | 								return true; | ||
|  | 							} | ||
|  | 						} | ||
|  | 					}); | ||
|  | 
 | ||
|  | 					// 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( | ||
|  | 													purePart.range | ||
|  | 												); | ||
|  | 												dep.loc = 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 ? statement.id.range[1] : 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 ? element.key.range[1] : 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( | ||
|  | 															expression.range | ||
|  | 														); | ||
|  | 														dep.loc = 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 (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( | ||
|  | 													decl.init.range | ||
|  | 												); | ||
|  | 												dep.loc = decl.loc; | ||
|  | 												dep.usedByExports = usedByExports; | ||
|  | 												parser.state.module.addDependency(dep); | ||
|  | 												break; | ||
|  | 											} | ||
|  | 										} | ||
|  | 									}); | ||
|  | 								} | ||
|  | 							} | ||
|  | 							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; |