123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- /*
- 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) {
- 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,
- /** @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;
|