| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- /*
- 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 RuntimeGlobals = require("../RuntimeGlobals");
- const {
- harmonySpecifierTag
- } = require("../dependencies/HarmonyImportDependencyParserPlugin");
- const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
- const PureExpressionDependency = require("../dependencies/PureExpressionDependency");
- const { getInnerGraphUtils, topLevelSymbolTag } = require("./InnerGraph");
- /**
- * @import {
- * CallExpression,
- * ClassDeclaration,
- * ClassExpression,
- * Expression,
- * MaybeNamedClassDeclaration,
- * MaybeNamedFunctionDeclaration,
- * Node,
- * VariableDeclarator
- * } from "estree"
- */
- /**
- * @import {
- * JavascriptParserOptions
- * } from "../../declarations/WebpackOptions"
- */
- /** @import Compilation from "../Compilation" */
- /** @import Compiler from "../Compiler" */
- /** @import Dependency from "../Dependency" */
- /** @import Module from "../Module" */
- /** @import JavascriptParser, { Range } from "../javascript/JavascriptParser" */
- /** @import { TopLevelSymbol } from "./InnerGraph" */
- const PLUGIN_NAME = "InnerGraphPlugin";
- const impureVariableDeclarationKinds = new Set(["using", "await using"]);
- class InnerGraphPlugin {
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- compiler.hooks.compilation.tap(
- PLUGIN_NAME,
- (compilation, { normalModuleFactory }) => {
- const logger = compilation.getLogger("webpack.InnerGraphPlugin");
- const innerGraph = getInnerGraphUtils(compilation);
- compilation.dependencyTemplates.set(
- PureExpressionDependency,
- new PureExpressionDependency.Template()
- );
- /**
- * Adds pure dependency added after parsing, with parents set so it survives persistent caching.
- * @param {Module} module module
- * @param {Dependency} dep pure dependency
- * @returns {void}
- */
- const addPureDependency = (module, dep) => {
- compilation.moduleGraph.setParents(dep, module, module, -1);
- module.addDependency(dep);
- };
- /**
- * Handles the hook callback for this code path.
- * @param {JavascriptParser} parser the parser
- * @param {JavascriptParserOptions} parserOptions options
- * @returns {void}
- */
- const handler = (parser, parserOptions) => {
- /**
- * Processes the provided sup.
- * @param {Expression} sup sup
- */
- const onUsageSuper = (sup) => {
- // the callback fires after parsing, when the parser can no
- // longer map offsets — take the location now
- const loc = parser.getLocation(sup);
- innerGraph.onUsage(parser.state, (usedByExports, module) => {
- switch (usedByExports) {
- case undefined:
- case true:
- return;
- default: {
- const dep = new PureExpressionDependency(
- /** @type {Range} */
- (sup.range)
- );
- dep.loc = loc;
- dep.usedByExports = usedByExports;
- addPureDependency(module, dep);
- break;
- }
- }
- });
- };
- parser.hooks.program.tap(PLUGIN_NAME, () => {
- innerGraph.enable(parser.state);
- statementWithTopLevelSymbol = new WeakMap();
- statementPurePart = new WeakMap();
- classWithTopLevelSymbol = new WeakMap();
- declWithTopLevelSymbol = new WeakMap();
- pureDeclarators = new WeakSet();
- pureConditionByCallExpr = new WeakMap();
- });
- // 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>} */
- let statementWithTopLevelSymbol = new WeakMap();
- /** @type {WeakMap<Node | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, Node>} */
- let statementPurePart = new WeakMap();
- /** @type {WeakMap<ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration, TopLevelSymbol>} */
- let classWithTopLevelSymbol = new WeakMap();
- /** @type {WeakMap<VariableDeclarator, TopLevelSymbol>} */
- let declWithTopLevelSymbol = new WeakMap();
- /** @type {WeakSet<VariableDeclarator>} */
- let pureDeclarators = new WeakSet();
- /** @type {WeakMap<CallExpression, (compilation: Compilation, module: Module) => boolean>} */
- let pureConditionByCallExpr = new WeakMap();
- parser.hooks.isPure.for("CallExpression").tap(
- {
- name: PLUGIN_NAME,
- stage: -10
- },
- (expression) => {
- const expr = /** @type {CallExpression} */ (expression);
- const callee = expr.callee;
- /** @type {Node} */
- let root;
- /** @type {string[]} */
- let chainMembers;
- if (callee.type === "Identifier") {
- root = callee;
- chainMembers = [];
- } else if (callee.type === "MemberExpression") {
- const chain = parser.extractMemberExpressionChain(callee);
- // optional chaining short-circuits and breaks straight purity
- if (chain.membersOptionals.some(Boolean)) return;
- root = /** @type {Node} */ (chain.object);
- // extractMemberExpressionChain returns members in reverse
- chainMembers = [...chain.members].reverse();
- } else {
- return;
- }
- if (root.type !== "Identifier") return;
- const harmonySettings =
- /** @type {{ source: string, ids: string[] } | undefined} */ (
- parser.getTagData(root.name, harmonySpecifierTag)
- );
- if (!harmonySettings) return;
- const ids = [...harmonySettings.ids, ...chainMembers];
- if (ids.length === 0) return;
- let pos = /** @type {Range} */ (callee.range)[1];
- for (const arg of expr.arguments) {
- if (arg.type === "SpreadElement") return;
- if (!parser.isPure(arg, pos)) return;
- pos = /** @type {Range} */ (arg.range)[1];
- }
- const source = harmonySettings.source;
- pureConditionByCallExpr.set(expr, (compilation, module) => {
- const moduleGraph = compilation.moduleGraph;
- for (const dep of module.dependencies) {
- if (
- dep instanceof HarmonyImportSideEffectDependency &&
- dep.request === source
- ) {
- const m = moduleGraph.getModule(dep);
- if (!m) return false;
- const exportInfo = moduleGraph
- .getExportsInfo(m)
- .getReadOnlyExportInfoRecursive(ids);
- if (!exportInfo) return false;
- const target = exportInfo.getTarget(moduleGraph);
- const final =
- target && target.export
- ? moduleGraph
- .getExportsInfo(target.module)
- .getReadOnlyExportInfoRecursive(target.export)
- : exportInfo;
- return final !== undefined && final.pureProvide === true;
- }
- }
- return false;
- });
- }
- );
- // The following hooks are used during prewalking:
- parser.hooks.preStatementByType
- .for("FunctionDeclaration")
- .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 symbol = /** @type {TopLevelSymbol} */ (
- innerGraph.tagTopLevelSymbol(
- parser,
- name,
- parser.isPure(
- statement,
- /** @type {Range} */ (statement.range)[0]
- )
- )
- );
- statementWithTopLevelSymbol.set(statement, symbol);
- return true;
- }
- });
- /**
- * @param {import("estree").Statement | import("estree").ModuleDeclaration | import("estree").MaybeNamedFunctionDeclaration | import("estree").MaybeNamedClassDeclaration} statement statement
- * @returns {boolean | void} true when handled
- */
- const blockPreStatementHandler = (statement) => {
- if (!innerGraph.isEnabled(parser.state)) return;
- if (parser.scope.topLevelScope === true) {
- if (statement.type === "ClassDeclaration") {
- const name = statement.id ? statement.id.name : "*default*";
- const pure = parser.isPure(
- statement,
- /** @type {Range} */ (statement.range)[0]
- );
- const symbol = /** @type {TopLevelSymbol} */ (
- innerGraph.tagTopLevelSymbol(parser, name, pure)
- );
- classWithTopLevelSymbol.set(statement, symbol);
- return true;
- }
- if (statement.type === "ExportDefaultDeclaration") {
- const name = "*default*";
- const decl = statement.declaration;
- /** @type {boolean | ((compilation: Compilation, module: Module) => boolean)} */
- let pure = parser.isPure(
- decl,
- /** @type {Range} */ (statement.range)[0]
- );
- if (!pure && decl.type === "CallExpression") {
- const deferred = pureConditionByCallExpr.get(decl);
- if (deferred) pure = deferred;
- }
- const symbol =
- /** @type {TopLevelSymbol} */
- (innerGraph.tagTopLevelSymbol(parser, name, pure));
- if (
- decl.type === "ClassExpression" ||
- decl.type === "ClassDeclaration"
- ) {
- classWithTopLevelSymbol.set(
- /** @type {ClassExpression | ClassDeclaration} */
- (decl),
- symbol
- );
- } else {
- statementWithTopLevelSymbol.set(statement, symbol);
- if (
- pure &&
- // body deferred to call-time, no eager read
- !decl.type.endsWith("FunctionExpression") &&
- // only FunctionDeclaration here (classes routed above), body deferred
- !decl.type.endsWith("Declaration") &&
- // literal references nothing
- decl.type !== "Literal"
- ) {
- statementPurePart.set(
- statement,
- /** @type {Expression} */
- (decl)
- );
- }
- }
- }
- }
- };
- parser.hooks.blockPreStatementByType
- .for("ClassDeclaration")
- .tap(PLUGIN_NAME, blockPreStatementHandler);
- parser.hooks.blockPreStatementByType
- .for("ExportDefaultDeclaration")
- .tap(PLUGIN_NAME, blockPreStatementHandler);
- parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => {
- if (!innerGraph.isEnabled(parser.state)) return;
- if (impureVariableDeclarationKinds.has(statement.kind)) return;
- if (
- parser.scope.topLevelScope === true &&
- decl.init &&
- decl.id.type === "Identifier"
- ) {
- const name = decl.id.name;
- // Skip webpack runtime variables handled by CompatibilityPlugin
- if (
- name === RuntimeGlobals.require ||
- name === RuntimeGlobals.exports
- ) {
- return;
- }
- /** @type {boolean | ((compilation: Compilation, module: Module) => boolean)} */
- let pure = parser.isPure(
- decl.init,
- /** @type {Range} */ (decl.id.range)[1]
- );
- if (!pure && decl.init.type === "CallExpression") {
- const deferred = pureConditionByCallExpr.get(decl.init);
- if (deferred) pure = deferred;
- }
- if (decl.init.type === "ClassExpression") {
- const symbol =
- /** @type {TopLevelSymbol} */
- (innerGraph.tagTopLevelSymbol(parser, name, pure));
- classWithTopLevelSymbol.set(decl.init, symbol);
- } else {
- const symbol =
- /** @type {TopLevelSymbol} */
- (innerGraph.tagTopLevelSymbol(parser, name, pure));
- declWithTopLevelSymbol.set(decl, symbol);
- if (
- pure &&
- !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 symbol = statementWithTopLevelSymbol.get(statement);
- if (symbol) {
- innerGraph.setTopLevelSymbol(parser.state, symbol);
- const purePart = statementPurePart.get(statement);
- if (purePart) {
- // the callback fires after parsing, when the parser can no
- // longer map offsets — take the location now
- const loc = parser.getLocation(statement);
- innerGraph.onUsage(parser.state, (usedByExports, module) => {
- switch (usedByExports) {
- case undefined:
- case true:
- return;
- default: {
- const dep = new PureExpressionDependency(
- /** @type {Range} */ (purePart.range)
- );
- dep.loc = loc;
- dep.usedByExports = usedByExports;
- addPureDependency(module, dep);
- break;
- }
- }
- });
- }
- }
- }
- });
- parser.hooks.classExtendsExpression.tap(
- PLUGIN_NAME,
- (expr, statement) => {
- if (!innerGraph.isEnabled(parser.state)) return;
- if (parser.scope.topLevelScope === true) {
- const symbol = classWithTopLevelSymbol.get(statement);
- if (
- symbol &&
- parser.isPure(
- expr,
- statement.id
- ? /** @type {Range} */ (statement.id.range)[1]
- : /** @type {Range} */ (statement.range)[0]
- )
- ) {
- innerGraph.setTopLevelSymbol(parser.state, symbol);
- onUsageSuper(expr);
- }
- }
- }
- );
- parser.hooks.classBodyElement.tap(
- PLUGIN_NAME,
- (element, classDefinition) => {
- if (!innerGraph.isEnabled(parser.state)) return;
- if (parser.scope.topLevelScope === true) {
- const symbol = classWithTopLevelSymbol.get(classDefinition);
- if (symbol) {
- 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 symbol = classWithTopLevelSymbol.get(classDefinition);
- if (symbol) {
- if (
- !element.static ||
- parser.isPure(
- expression,
- element.key
- ? /** @type {Range} */ (element.key.range)[1]
- : /** @type {Range} */ (element.range)[0]
- )
- ) {
- innerGraph.setTopLevelSymbol(parser.state, symbol);
- if (element.type !== "MethodDefinition" && element.static) {
- // the callback fires after parsing, when the parser can
- // no longer map offsets — take the location now
- const loc = parser.getLocation(expression);
- innerGraph.onUsage(
- parser.state,
- (usedByExports, module) => {
- switch (usedByExports) {
- case undefined:
- case true:
- return;
- default: {
- const dep = new PureExpressionDependency(
- /** @type {Range} */ (expression.range)
- );
- dep.loc = loc;
- dep.usedByExports = usedByExports;
- addPureDependency(module, dep);
- break;
- }
- }
- }
- );
- }
- } else {
- innerGraph.setTopLevelSymbol(parser.state, undefined);
- }
- }
- }
- }
- );
- parser.hooks.declarator.tap(PLUGIN_NAME, (decl, _statement) => {
- if (!innerGraph.isEnabled(parser.state)) return;
- const symbol = declWithTopLevelSymbol.get(decl);
- if (symbol) {
- innerGraph.setTopLevelSymbol(parser.state, symbol);
- if (pureDeclarators.has(decl)) {
- if (
- /** @type {ClassExpression} */
- (decl.init).type === "ClassExpression"
- ) {
- if (decl.init.superClass) {
- onUsageSuper(decl.init.superClass);
- }
- } else {
- // the callback fires after parsing, when the parser can no
- // longer map offsets — take the location now
- const loc = parser.getLocation(decl);
- innerGraph.onUsage(parser.state, (usedByExports, module) => {
- switch (usedByExports) {
- case undefined:
- case true:
- return;
- default: {
- const dep = new PureExpressionDependency(
- /** @type {Range} */ (
- /** @type {ClassExpression} */
- (decl.init).range
- )
- );
- dep.loc = loc;
- dep.usedByExports = usedByExports;
- addPureDependency(module, 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, (modules) => {
- logger.time("infer dependency usage");
- for (const module of modules) {
- innerGraph.inferDependencyUsage(module);
- // state is dead after inference; release it to not retain ASTs via callbacks
- innerGraph.release(module);
- }
- logger.timeEnd("infer dependency usage");
- });
- }
- );
- }
- }
- module.exports = InnerGraphPlugin;
|