webpack/lib/optimize/InnerGraph.js

234 lines
5.7 KiB
JavaScript
Raw Normal View History

2020-01-21 22:25:40 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Sergey Melyukov @smelukov
*/
"use strict";
/** @typedef {import("estree").Node} AnyNode */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Parser").ParserState} ParserState */
2020-01-29 21:24:55 +08:00
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
2020-01-21 22:25:40 +08:00
/** @typedef {Map<TopLevelSymbol | Dependency, Set<string | TopLevelSymbol> | true>} InnerGraph */
2020-01-29 21:24:55 +08:00
/** @typedef {false|{innerGraph: InnerGraph, allExportDependentDependencies: Set<Dependency>, currentTopLevelSymbol: TopLevelSymbol|void}} State */
2020-01-21 22:25:40 +08:00
/** @type {WeakMap<ParserState, State>} */
const parserStateMap = new WeakMap();
const topLevelSymbolTag = Symbol("top level symbol");
2020-01-23 20:59:27 +08:00
/**
* @param {ParserState} parserState parser state
* @returns {State} state
*/
function getState(parserState) {
return parserStateMap.get(parserState);
}
2020-01-21 22:25:40 +08:00
/**
* @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, {
2020-01-22 01:50:27 +08:00
innerGraph: new Map(),
allExportDependentDependencies: new Set(),
2020-01-21 22:25:40 +08:00
currentTopLevelSymbol: undefined
});
};
/**
* @param {ParserState} parserState parser state
* @returns {boolean} true, when enabled
*/
exports.isEnabled = parserState => {
const state = parserStateMap.get(parserState);
return !!state;
};
/**
2020-01-23 20:59:27 +08:00
* @param {ParserState} state parser state
* @param {TopLevelSymbol | Dependency} symbol the symbol
2020-01-29 21:24:55 +08:00
* @param {string | TopLevelSymbol | true} usage usage data
* @returns {void}
2020-01-21 22:25:40 +08:00
*/
2020-01-29 21:24:55 +08:00
exports.addUsage = (state, symbol, usage) => {
2020-01-23 20:59:27 +08:00
const innerGraphState = getState(state);
if (innerGraphState) {
2020-01-29 21:24:55 +08:00
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);
}
2020-01-23 20:59:27 +08:00
}
};
/**
2020-01-29 21:24:55 +08:00
* @param {JavascriptParser} parser the parser
* @param {string} name name of variable
* @param {string | TopLevelSymbol | true} usage usage data
* @returns {void}
2020-01-23 20:59:27 +08:00
*/
2020-01-29 21:24:55 +08:00
exports.addVariableUsage = (parser, name, usage) => {
const symbol = /** @type {TopLevelSymbol} */ (parser.getTagData(
name,
topLevelSymbolTag
));
if (symbol) {
exports.addUsage(parser.state, symbol, usage);
2020-01-23 20:59:27 +08:00
}
};
/**
* @param {ParserState} state parser state
2020-01-29 21:24:55 +08:00
* @returns {Map<Dependency, Set<string> | true>} usage data
2020-01-23 20:59:27 +08:00
*/
2020-01-27 23:33:40 +08:00
exports.inferDependencyUsage = state => {
const innerGraphState = getState(state);
if (!innerGraphState) {
return;
}
const { allExportDependentDependencies, innerGraph } = innerGraphState;
// 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);
if (value !== true && value !== undefined) {
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 (value.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);
}
}
}
2020-01-29 21:24:55 +08:00
/** @type {Map<Dependency, true | Set<string>>} */
const result = new Map();
2020-01-27 23:33:40 +08:00
for (const dep of allExportDependentDependencies) {
2020-01-29 21:24:55 +08:00
const value = /** @type {true | Set<string>} */ (innerGraph.get(dep));
result.set(dep, value);
2020-01-27 23:33:40 +08:00
}
2020-01-29 21:24:55 +08:00
return result;
2020-01-27 23:33:40 +08:00
};
/**
* @param {ParserState} state parser state
2020-01-29 21:24:55 +08:00
* @param {Dependency} dep dependency
2020-01-27 23:33:40 +08:00
*/
exports.addDependency = (state, dep) => {
2020-01-23 20:59:27 +08:00
const innerGraphState = getState(state);
if (innerGraphState) {
2020-01-27 23:33:40 +08:00
innerGraphState.allExportDependentDependencies.add(dep);
2020-01-23 20:59:27 +08:00
}
};
/**
* @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;
}
};
/**
2020-01-29 21:24:55 +08:00
* @param {JavascriptParser} parser parser
* @param {string} name name of variable
* @returns {TopLevelSymbol} symbol
2020-01-23 20:59:27 +08:00
*/
2020-01-27 23:46:23 +08:00
exports.tagTopLevelSymbol = (parser, name) => {
const innerGraphState = getState(parser.state);
2020-01-29 21:24:55 +08:00
if (!innerGraphState) return;
2020-01-23 20:59:27 +08:00
2020-01-27 23:46:23 +08:00
parser.defineVariable(name);
2020-01-29 21:24:55 +08:00
const existingTag = /** @type {TopLevelSymbol} */ (parser.getTagData(
name,
topLevelSymbolTag
));
2020-01-27 23:46:23 +08:00
if (existingTag) {
return existingTag;
}
2020-01-29 21:24:55 +08:00
const fn = new TopLevelSymbol(name);
2020-01-27 23:46:23 +08:00
parser.tagVariable(name, topLevelSymbolTag, fn);
return fn;
2020-01-21 22:25:40 +08:00
};
class TopLevelSymbol {
/**
2020-01-29 21:24:55 +08:00
* @param {string} name name of the variable
2020-01-21 22:25:40 +08:00
*/
2020-01-29 21:24:55 +08:00
constructor(name) {
2020-01-21 22:25:40 +08:00
this.name = name;
}
}
exports.TopLevelSymbol = TopLevelSymbol;
exports.topLevelSymbolTag = topLevelSymbolTag;