diff options
Diffstat (limited to 'node_modules/webpack/lib/optimize/InnerGraphPlugin.js')
| -rw-r--r-- | node_modules/webpack/lib/optimize/InnerGraphPlugin.js | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/optimize/InnerGraphPlugin.js b/node_modules/webpack/lib/optimize/InnerGraphPlugin.js new file mode 100644 index 00000000..9d24abe3 --- /dev/null +++ b/node_modules/webpack/lib/optimize/InnerGraphPlugin.js @@ -0,0 +1,409 @@ +/* + 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("../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) => { + 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 && + 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, + /** @type {Range} */ (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, /** @type {Range} */ (decl.range)[0]) + ) { + classWithTopLevelSymbol.set(decl, fn); + } else if ( + parser.isPure(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, 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 = InnerGraph.tagTopLevelSymbol(parser, name); + classWithTopLevelSymbol.set(decl.init, fn); + } else if ( + parser.isPure( + decl.init, + /** @type {Range} */ (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); + } + } + } + }); + + // 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 (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} */ (decl.init.range) + ); + dep.loc = /** @type {DependencyLocation} */ (decl.loc); + dep.usedByExports = usedByExports; + parser.state.module.addDependency(dep); + break; + } + } + }); + } + } + parser.walkExpression(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; |
