UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

481 lines (430 loc) 13.4 kB
/* 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; } /** * 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: return (_connections, runtime) => isDependencyUsedByExports( dependency, /** @type {DependencyWithUsedByExports} */ (dependency).usedByExports, moduleGraph, runtime ); } }; /** * 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;