mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			347 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			347 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Sergey Melyukov @smelukov
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const { UsageState } = require("../ExportsInfo");
 | 
						|
 | 
						|
/** @typedef {import("estree").Node} AnyNode */
 | 
						|
/** @typedef {import("../Dependency")} Dependency */
 | 
						|
/** @typedef {import("../ModuleGraph")} ModuleGraph */
 | 
						|
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
 | 
						|
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
 | 
						|
/** @typedef {import("../Parser").ParserState} ParserState */
 | 
						|
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
 | 
						|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
 | 
						|
 | 
						|
/** @typedef {Map<TopLevelSymbol | null, Set<string | TopLevelSymbol> | true>} InnerGraph */
 | 
						|
/** @typedef {function(boolean | Set<string> | undefined): void} UsageCallback */
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef {Object} StateObject
 | 
						|
 * @property {InnerGraph} innerGraph
 | 
						|
 * @property {TopLevelSymbol=} currentTopLevelSymbol
 | 
						|
 * @property {Map<TopLevelSymbol, Set<UsageCallback>>} usageCallbackMap
 | 
						|
 */
 | 
						|
 | 
						|
/** @typedef {false|StateObject} State */
 | 
						|
 | 
						|
/** @type {WeakMap<ParserState, State>} */
 | 
						|
const parserStateMap = new WeakMap();
 | 
						|
const topLevelSymbolTag = Symbol("top level symbol");
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} parserState parser state
 | 
						|
 * @returns {State} state
 | 
						|
 */
 | 
						|
function getState(parserState) {
 | 
						|
	return parserStateMap.get(parserState);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} parserState parser state
 | 
						|
 * @returns {void}
 | 
						|
 */
 | 
						|
exports.bailout = parserState => {
 | 
						|
	parserStateMap.set(parserState, false);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} parserState parser state
 | 
						|
 * @returns {void}
 | 
						|
 */
 | 
						|
exports.enable = parserState => {
 | 
						|
	const state = parserStateMap.get(parserState);
 | 
						|
	if (state === false) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	parserStateMap.set(parserState, {
 | 
						|
		innerGraph: new Map(),
 | 
						|
		currentTopLevelSymbol: undefined,
 | 
						|
		usageCallbackMap: new Map()
 | 
						|
	});
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} parserState parser state
 | 
						|
 * @returns {boolean} true, when enabled
 | 
						|
 */
 | 
						|
exports.isEnabled = parserState => {
 | 
						|
	const state = parserStateMap.get(parserState);
 | 
						|
	return !!state;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} state parser state
 | 
						|
 * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols
 | 
						|
 * @param {string | TopLevelSymbol | true} usage usage data
 | 
						|
 * @returns {void}
 | 
						|
 */
 | 
						|
exports.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 {string | TopLevelSymbol | true} usage usage data
 | 
						|
 * @returns {void}
 | 
						|
 */
 | 
						|
exports.addVariableUsage = (parser, name, usage) => {
 | 
						|
	const symbol =
 | 
						|
		/** @type {TopLevelSymbol} */ (
 | 
						|
			parser.getTagData(name, topLevelSymbolTag)
 | 
						|
		) || exports.tagTopLevelSymbol(parser, name);
 | 
						|
	if (symbol) {
 | 
						|
		exports.addUsage(parser.state, symbol, usage);
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} state parser state
 | 
						|
 * @returns {void}
 | 
						|
 */
 | 
						|
exports.inferDependencyUsage = state => {
 | 
						|
	const innerGraphState = getState(state);
 | 
						|
 | 
						|
	if (!innerGraphState) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	const { innerGraph, usageCallbackMap } = innerGraphState;
 | 
						|
	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) {
 | 
						|
			/** @type {Set<string|TopLevelSymbol> | true} */
 | 
						|
			let newSet = new Set();
 | 
						|
			let isTerminal = true;
 | 
						|
			const value = innerGraph.get(key);
 | 
						|
			let alreadyProcessed = processed.get(key);
 | 
						|
			if (alreadyProcessed === undefined) {
 | 
						|
				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 {
 | 
						|
						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 = /** @type {true | Set<string> | undefined} */ (
 | 
						|
			innerGraph.get(symbol)
 | 
						|
		);
 | 
						|
		for (const callback of callbacks) {
 | 
						|
			callback(usage === undefined ? false : usage);
 | 
						|
		}
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} state parser state
 | 
						|
 * @param {UsageCallback} onUsageCallback on usage callback
 | 
						|
 */
 | 
						|
exports.onUsage = (state, onUsageCallback) => {
 | 
						|
	const innerGraphState = getState(state);
 | 
						|
 | 
						|
	if (innerGraphState) {
 | 
						|
		const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState;
 | 
						|
		if (currentTopLevelSymbol) {
 | 
						|
			let callbacks = usageCallbackMap.get(currentTopLevelSymbol);
 | 
						|
 | 
						|
			if (callbacks === undefined) {
 | 
						|
				callbacks = new Set();
 | 
						|
				usageCallbackMap.set(currentTopLevelSymbol, callbacks);
 | 
						|
			}
 | 
						|
 | 
						|
			callbacks.add(onUsageCallback);
 | 
						|
		} else {
 | 
						|
			onUsageCallback(true);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		onUsageCallback(undefined);
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} state parser state
 | 
						|
 * @param {TopLevelSymbol} symbol the symbol
 | 
						|
 */
 | 
						|
exports.setTopLevelSymbol = (state, symbol) => {
 | 
						|
	const innerGraphState = getState(state);
 | 
						|
 | 
						|
	if (innerGraphState) {
 | 
						|
		innerGraphState.currentTopLevelSymbol = symbol;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {ParserState} state parser state
 | 
						|
 * @returns {TopLevelSymbol|void} usage data
 | 
						|
 */
 | 
						|
exports.getTopLevelSymbol = state => {
 | 
						|
	const innerGraphState = getState(state);
 | 
						|
 | 
						|
	if (innerGraphState) {
 | 
						|
		return innerGraphState.currentTopLevelSymbol;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {JavascriptParser} parser parser
 | 
						|
 * @param {string} name name of variable
 | 
						|
 * @returns {TopLevelSymbol} symbol
 | 
						|
 */
 | 
						|
exports.tagTopLevelSymbol = (parser, name) => {
 | 
						|
	const innerGraphState = getState(parser.state);
 | 
						|
	if (!innerGraphState) return;
 | 
						|
 | 
						|
	parser.defineVariable(name);
 | 
						|
 | 
						|
	const existingTag = /** @type {TopLevelSymbol} */ (
 | 
						|
		parser.getTagData(name, topLevelSymbolTag)
 | 
						|
	);
 | 
						|
	if (existingTag) {
 | 
						|
		return existingTag;
 | 
						|
	}
 | 
						|
 | 
						|
	const fn = new TopLevelSymbol(name);
 | 
						|
	parser.tagVariable(name, topLevelSymbolTag, fn);
 | 
						|
	return fn;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Dependency} dependency the dependency
 | 
						|
 * @param {Set<string> | boolean} usedByExports usedByExports info
 | 
						|
 * @param {ModuleGraph} moduleGraph moduleGraph
 | 
						|
 * @param {RuntimeSpec} runtime runtime
 | 
						|
 * @returns {boolean} false, when unused. Otherwise true
 | 
						|
 */
 | 
						|
exports.isDependencyUsedByExports = (
 | 
						|
	dependency,
 | 
						|
	usedByExports,
 | 
						|
	moduleGraph,
 | 
						|
	runtime
 | 
						|
) => {
 | 
						|
	if (usedByExports === false) return false;
 | 
						|
	if (usedByExports !== true && usedByExports !== undefined) {
 | 
						|
		const selfModule = moduleGraph.getParentModule(dependency);
 | 
						|
		const exportsInfo = moduleGraph.getExportsInfo(selfModule);
 | 
						|
		let used = false;
 | 
						|
		for (const exportName of usedByExports) {
 | 
						|
			if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
 | 
						|
				used = true;
 | 
						|
		}
 | 
						|
		if (!used) return false;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {Dependency} dependency the dependency
 | 
						|
 * @param {Set<string> | boolean} usedByExports usedByExports info
 | 
						|
 * @param {ModuleGraph} moduleGraph moduleGraph
 | 
						|
 * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active
 | 
						|
 */
 | 
						|
exports.getDependencyUsedByExportsCondition = (
 | 
						|
	dependency,
 | 
						|
	usedByExports,
 | 
						|
	moduleGraph
 | 
						|
) => {
 | 
						|
	if (usedByExports === false) return false;
 | 
						|
	if (usedByExports !== true && usedByExports !== undefined) {
 | 
						|
		const selfModule = moduleGraph.getParentModule(dependency);
 | 
						|
		const exportsInfo = moduleGraph.getExportsInfo(selfModule);
 | 
						|
		return (connections, runtime) => {
 | 
						|
			for (const exportName of usedByExports) {
 | 
						|
				if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused)
 | 
						|
					return true;
 | 
						|
			}
 | 
						|
			return false;
 | 
						|
		};
 | 
						|
	}
 | 
						|
	return null;
 | 
						|
};
 | 
						|
 | 
						|
class TopLevelSymbol {
 | 
						|
	/**
 | 
						|
	 * @param {string} name name of the variable
 | 
						|
	 */
 | 
						|
	constructor(name) {
 | 
						|
		this.name = name;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
exports.TopLevelSymbol = TopLevelSymbol;
 | 
						|
exports.topLevelSymbolTag = topLevelSymbolTag;
 |