/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/optimize/InnerGraph.js
497 строк
14 KB
Alexander Akait
perf(css,html,js): cut parser and graph memory, speed up line-start scanning (#21579)
02 авг 2026, 13:04
Не верифицирован
02 авг 2026, 13:04
ff7e175
Код
Авторство
О чём код?
/* 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"); /** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../Parser").ParserState} ParserState */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ /** @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;