| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Sergey Melyukov @smelukov
- */
- "use strict";
- const { UsageState } = require("../ExportsInfo");
- const JavascriptParser = require("../javascript/JavascriptParser");
- /** @import Compilation from "../Compilation" */
- /** @import Dependency, { GetConditionFn } from "../Dependency" */
- /** @import Module from "../Module" */
- /** @import ModuleGraph from "../ModuleGraph" */
- /** @import { ParserState } from "../Parser" */
- /** @import { RuntimeSpec } from "../util/runtime" */
- /** @typedef {boolean | ((compilation: Compilation, module: Module) => boolean)} PureCondition */
- /** @typedef {Set<string | TopLevelSymbol>} InnerGraphValueSet */
- /** @typedef {InnerGraphValueSet | true} InnerGraphValue */
- /** @typedef {TopLevelSymbol | null} InnerGraphKey */
- /** @typedef {Map<InnerGraphKey, InnerGraphValue | undefined>} InnerGraph */
- /** @typedef {(value: boolean | Set<string> | undefined, module: Module) => void} UsageCallback */
- /**
- * Defines the state object type used by this module.
- * @typedef {object} StateObject
- * @property {InnerGraph} innerGraph
- * @property {TopLevelSymbol=} currentTopLevelSymbol
- * @property {Map<TopLevelSymbol, Set<UsageCallback>>} usageCallbackMap
- */
- /** @typedef {false | StateObject} State */
- /** @typedef {string | TopLevelSymbol | true} Usage */
- /** @typedef {Set<string> | boolean} UsedByExports */
- /** @typedef {Dependency & { usedByExports: UsedByExports | undefined }} DependencyWithUsedByExports */
- class TopLevelSymbol {
- /**
- * Creates an instance of TopLevelSymbol.
- * @param {string} name name of the variable
- * @param {PureCondition=} pure pure condition
- */
- constructor(name, pure = false) {
- /** @type {string} */
- this.name = name;
- /** @type {boolean} */
- this.conditional = false;
- /** @type {boolean} */
- this._pure = false;
- /** @type {((compilation: Compilation, module: Module) => boolean) | undefined} */
- this.pureFn = undefined;
- this.setPure(pure);
- }
- /**
- * Sets the pure condition
- * @param {PureCondition} pure pure condition
- * @returns {void}
- */
- setPure(pure) {
- this.conditional = typeof pure === "function";
- this._pure = pure === true;
- this.pureFn = typeof pure === "function" ? pure : undefined;
- }
- /**
- * @param {Compilation} compilation compilation
- * @param {Module} module module
- * @returns {boolean} if the symbol is pure
- */
- isPure(compilation, module) {
- if (!this.conditional) return this._pure;
- const pureFn =
- /** @type {(compilation: Compilation, module: Module) => boolean} */ (
- this.pureFn
- );
- return pureFn(compilation, module);
- }
- }
- module.exports.TopLevelSymbol = TopLevelSymbol;
- const topLevelSymbolTag = Symbol("top level symbol");
- /** @type {WeakMap<Compilation, InnerGraphUtils>} */
- const innerGraphByCompilation = new WeakMap();
- /**
- * @typedef {object} InnerGraphUtils
- * @property {(parserState: ParserState) => void} enable
- * @property {(parserState: ParserState) => void} bailout
- * @property {(parserState: ParserState) => boolean} isEnabled
- * @property {(parserState: ParserState, symbol: TopLevelSymbol | null, usage: Usage) => void} addUsage
- * @property {(parserState: ParserState, onUsageCallback: UsageCallback) => void} onUsage
- * @property {(parserState: ParserState, symbol: TopLevelSymbol | undefined) => void} setTopLevelSymbol
- * @property {(parserState: ParserState) => TopLevelSymbol | void} getTopLevelSymbol
- * @property {(parser: JavascriptParser, name: string, pure?: PureCondition) => TopLevelSymbol | undefined} tagTopLevelSymbol
- * @property {(parser: JavascriptParser, name: string, usage: Usage) => void} addVariableUsage
- * @property {(module: Module) => void} inferDependencyUsage
- * @property {(module: Module) => void} release
- */
- /**
- * Returns false, when unused. Otherwise true.
- * @param {Dependency} dependency the dependency
- * @param {UsedByExports | undefined} usedByExports usedByExports info
- * @param {ModuleGraph} moduleGraph moduleGraph
- * @param {RuntimeSpec} runtime runtime
- * @returns {boolean} false, when unused. Otherwise true
- */
- function isDependencyUsedByExports(
- dependency,
- usedByExports,
- moduleGraph,
- runtime
- ) {
- if (usedByExports === false) return false;
- if (usedByExports !== true && usedByExports !== undefined) {
- const selfModule =
- /** @type {Module} */
- (moduleGraph.getParentModule(dependency));
- const exportsInfo = moduleGraph.getExportsInfo(selfModule);
- for (const exportName of usedByExports) {
- if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) {
- return true;
- }
- }
- return false;
- }
- return true;
- }
- /**
- * One condition per module graph rather than per dependency. The dependency the
- * condition was built for is always the one on the connection it is called with
- * (`ModuleGraph#setResolvedModule` is the only caller of `getCondition`, and it
- * pairs the two), so it can be read off the connection instead of captured.
- * @type {WeakMap<ModuleGraph, GetConditionFn>}
- */
- const usedByExportsConditions = new WeakMap();
- /**
- * Returns dependency condition
- * @param {Dependency} dependency the dependency
- * @param {ModuleGraph} moduleGraph moduleGraph
- * @returns {null | false | GetConditionFn} function to determine if the connection is active
- */
- module.exports.getDependencyUsedByExportsCondition = (
- dependency,
- moduleGraph
- ) => {
- switch (
- /** @type {DependencyWithUsedByExports} */ (dependency).usedByExports
- ) {
- case false:
- return false;
- case true:
- return null;
- default: {
- const existing = usedByExportsConditions.get(moduleGraph);
- if (existing !== undefined) return existing;
- /** @type {GetConditionFn} */
- const condition = (connection, runtime) => {
- const dep = /** @type {Dependency} */ (connection.dependency);
- return isDependencyUsedByExports(
- dep,
- /** @type {DependencyWithUsedByExports} */ (dep).usedByExports,
- moduleGraph,
- runtime
- );
- };
- usedByExportsConditions.set(moduleGraph, condition);
- return condition;
- }
- }
- };
- /**
- * Returns the InnerGraph utils scoped to a single compilation.
- * @param {Compilation} compilation the compilation
- * @returns {InnerGraphUtils} utils
- */
- module.exports.getInnerGraphUtils = (compilation) => {
- let utils = innerGraphByCompilation.get(compilation);
- if (utils) return utils;
- /** @type {WeakMap<Module, State>} */
- const states = new WeakMap();
- /**
- * @param {ParserState} parserState parser state
- * @returns {State | undefined} state
- */
- function getState(parserState) {
- return states.get(parserState.module);
- }
- /**
- * @param {ParserState} parserState parser state
- * @returns {void}
- */
- function enable(parserState) {
- const state = states.get(parserState.module);
- if (state === false) {
- return;
- }
- states.set(parserState.module, {
- innerGraph: new Map(),
- currentTopLevelSymbol: undefined,
- usageCallbackMap: new Map()
- });
- }
- /**
- * @param {ParserState} parserState parser state
- * @returns {void}
- */
- function bailout(parserState) {
- states.set(parserState.module, false);
- }
- /**
- * @param {ParserState} parserState parser state
- * @returns {boolean} true, when enabled
- */
- function isEnabled(parserState) {
- return Boolean(states.get(parserState.module));
- }
- /**
- * @param {ParserState} state parser state
- * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols
- * @param {Usage} usage usage data
- * @returns {void}
- */
- function addUsage(state, symbol, usage) {
- const innerGraphState = getState(state);
- if (innerGraphState) {
- const { innerGraph } = innerGraphState;
- const info = innerGraph.get(symbol);
- if (usage === true) {
- innerGraph.set(symbol, true);
- } else if (info === undefined) {
- innerGraph.set(symbol, new Set([usage]));
- } else if (info !== true) {
- info.add(usage);
- }
- }
- }
- /**
- * @param {JavascriptParser} parser the parser
- * @param {string} name name of variable
- * @param {Usage} usage usage data
- * @returns {void}
- */
- function addVariableUsage(parser, name, usage) {
- const symbol =
- /** @type {TopLevelSymbol} */ (
- parser.getTagData(name, topLevelSymbolTag)
- ) || tagTopLevelSymbol(parser, name);
- if (symbol) {
- addUsage(parser.state, symbol, usage);
- }
- }
- /**
- * @param {ParserState} state parser state
- * @returns {TopLevelSymbol | void} usage data
- */
- function getTopLevelSymbol(state) {
- const innerGraphState = getState(state);
- if (innerGraphState) {
- return innerGraphState.currentTopLevelSymbol;
- }
- }
- /**
- * @param {ParserState} state parser state
- * @param {TopLevelSymbol | undefined} symbol the symbol
- */
- function setTopLevelSymbol(state, symbol) {
- const innerGraphState = getState(state);
- if (innerGraphState) {
- innerGraphState.currentTopLevelSymbol = symbol;
- }
- }
- /**
- * @param {ParserState} state parser state
- * @param {UsageCallback} onUsageCallback on usage callback
- */
- function onUsage(state, onUsageCallback) {
- const innerGraphState = getState(state);
- if (innerGraphState) {
- const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState;
- if (currentTopLevelSymbol) {
- let callbacks = usageCallbackMap.get(currentTopLevelSymbol);
- if (callbacks === undefined) {
- /** @type {Set<UsageCallback>} */
- callbacks = new Set();
- usageCallbackMap.set(currentTopLevelSymbol, callbacks);
- }
- callbacks.add(onUsageCallback);
- } else {
- onUsageCallback(true, state.module);
- }
- } else {
- onUsageCallback(undefined, state.module);
- }
- }
- /**
- * @param {JavascriptParser} parser parser
- * @param {string} name name of variable
- * @param {PureCondition=} pure pure condition
- * @returns {TopLevelSymbol=} symbol
- */
- function tagTopLevelSymbol(parser, name, pure) {
- const innerGraphState = getState(parser.state);
- if (!innerGraphState) return;
- parser.defineVariable(name);
- const existingTag = /** @type {TopLevelSymbol} */ (
- parser.getTagData(name, topLevelSymbolTag)
- );
- if (existingTag) {
- if (pure !== undefined) {
- existingTag.setPure(pure);
- }
- return existingTag;
- }
- const symbol = new TopLevelSymbol(name, pure);
- parser.tagVariable(
- name,
- topLevelSymbolTag,
- symbol,
- JavascriptParser.VariableInfoFlags.Normal
- );
- return symbol;
- }
- /**
- * @param {Module} module module
- * @returns {void}
- */
- function inferDependencyUsage(module) {
- const innerGraphState = states.get(module);
- if (!innerGraphState) {
- return;
- }
- const { innerGraph, usageCallbackMap } = innerGraphState;
- /** @type {Map<InnerGraphKey, InnerGraphValueSet | undefined>} */
- const processed = new Map();
- // flatten graph to terminal nodes (string, undefined or true)
- const nonTerminal = new Set(innerGraph.keys());
- while (nonTerminal.size > 0) {
- for (const key of nonTerminal) {
- if (key !== null && !key.isPure(compilation, module)) {
- innerGraph.set(key, true);
- nonTerminal.delete(key);
- continue;
- }
- /** @type {InnerGraphValue} */
- let newSet = new Set();
- let isTerminal = true;
- const value = innerGraph.get(key);
- let alreadyProcessed = processed.get(key);
- if (alreadyProcessed === undefined) {
- /** @type {InnerGraphValueSet} */
- alreadyProcessed = new Set();
- processed.set(key, alreadyProcessed);
- }
- if (value !== true && value !== undefined) {
- for (const item of value) {
- alreadyProcessed.add(item);
- }
- for (const item of value) {
- if (typeof item === "string") {
- newSet.add(item);
- } else if (!item.isPure(compilation, module)) {
- newSet = true;
- break;
- } else {
- const itemValue = innerGraph.get(item);
- if (itemValue === true) {
- newSet = true;
- break;
- }
- if (itemValue !== undefined) {
- for (const i of itemValue) {
- if (i === key) continue;
- if (alreadyProcessed.has(i)) continue;
- newSet.add(i);
- if (typeof i !== "string") {
- isTerminal = false;
- }
- }
- }
- }
- }
- if (newSet === true) {
- innerGraph.set(key, true);
- } else if (newSet.size === 0) {
- innerGraph.set(key, undefined);
- } else {
- innerGraph.set(key, newSet);
- }
- }
- if (isTerminal) {
- nonTerminal.delete(key);
- // For the global key, merge with all other keys
- if (key === null) {
- const globalValue = innerGraph.get(null);
- if (globalValue) {
- for (const [key, value] of innerGraph) {
- if (key !== null && value !== true) {
- if (globalValue === true) {
- innerGraph.set(key, true);
- } else {
- const newSet = new Set(value);
- for (const item of globalValue) {
- newSet.add(item);
- }
- innerGraph.set(key, newSet);
- }
- }
- }
- }
- }
- }
- }
- }
- /** @type {Map<Dependency, true | Set<string>>} */
- for (const [symbol, callbacks] of usageCallbackMap) {
- const usage = symbol.isPure(compilation, module)
- ? /** @type {true | Set<string> | undefined} */ (innerGraph.get(symbol))
- : true;
- for (const callback of callbacks) {
- callback(usage === undefined ? false : usage, module);
- }
- }
- }
- /**
- * @param {Module} module module
- * @returns {void}
- */
- function release(module) {
- states.delete(module);
- }
- utils = {
- enable,
- bailout,
- isEnabled,
- addUsage,
- addVariableUsage,
- getTopLevelSymbol,
- setTopLevelSymbol,
- onUsage,
- tagTopLevelSymbol,
- inferDependencyUsage,
- release
- };
- innerGraphByCompilation.set(compilation, utils);
- return utils;
- };
- /**
- * Usage callback map.
- * @param {Dependency} dependency the dependency
- * @param {UsedByExports | undefined} usedByExports usedByExports info
- * @param {ModuleGraph} moduleGraph moduleGraph
- * @returns {null | false | GetConditionFn} function to determine if the connection is active
- */
- module.exports.topLevelSymbolTag = topLevelSymbolTag;
|