diff --git a/lib/FlagUsingEvalPlugin.js b/lib/FlagUsingEvalPlugin.js index ca172d8f8..fb8ce2486 100644 --- a/lib/FlagUsingEvalPlugin.js +++ b/lib/FlagUsingEvalPlugin.js @@ -5,6 +5,8 @@ "use strict"; +const InnerGraph = require("./optimize/InnerGraph"); + /** @typedef {import("./Compiler")} Compiler */ class FlagUsingEvalPlugin { @@ -20,6 +22,7 @@ class FlagUsingEvalPlugin { parser.hooks.call.for("eval").tap("FlagUsingEvalPlugin", () => { parser.state.module.buildInfo.moduleConcatenationBailout = "eval()"; parser.state.module.buildInfo.usingEval = true; + InnerGraph.bailout(parser.state); }); }; diff --git a/lib/dependencies/HarmonyExportDependencyParserPlugin.js b/lib/dependencies/HarmonyExportDependencyParserPlugin.js index 955b856ab..afbc3c2c6 100644 --- a/lib/dependencies/HarmonyExportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyExportDependencyParserPlugin.js @@ -5,7 +5,7 @@ "use strict"; -const { topLevelSymbolTag } = require("../optimize/InnerGraphPlugin"); +const { topLevelSymbolTag } = require("../optimize/InnerGraph"); const ConstDependency = require("./ConstDependency"); const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency"); const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency"); diff --git a/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/lib/dependencies/HarmonyImportDependencyParserPlugin.js index 2d73b6994..8c2a10df2 100644 --- a/lib/dependencies/HarmonyImportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyImportDependencyParserPlugin.js @@ -6,6 +6,7 @@ "use strict"; const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin"); +const InnerGraph = require("../optimize/InnerGraph"); const ConstDependency = require("./ConstDependency"); const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); @@ -13,8 +14,8 @@ const HarmonyExports = require("./HarmonyExports"); const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); -/** @typedef {import("../optimize/InnerGraphPlugin").InnerGraph} InnerGraph */ -/** @typedef {import("../optimize/InnerGraphPlugin").TopLevelSymbol} TopLevelSymbol */ +/** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */ +/** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */ /** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */ const harmonySpecifierTag = Symbol("harmony import"); @@ -79,14 +80,14 @@ module.exports = class HarmonyImportDependencyParserPlugin { * @returns {void} */ const addDepToInnerGraph = dep => { - const { harmonyAllExportDependentDependencies } = parser.state; - if (!harmonyAllExportDependentDependencies) return; - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); - if (!innerGraph) return; - harmonyAllExportDependentDependencies.add(dep); - const currentTopLevelSymbol = - /** @type {TopLevelSymbol} */ (parser.state.currentTopLevelSymbol); + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; + const { + innerGraph, + allExportDependentDependencies, + currentTopLevelSymbol + } = innerGraphState; + allExportDependentDependencies.add(dep); if (!currentTopLevelSymbol) { innerGraph.set(dep, true); } else { diff --git a/lib/optimize/InnerGraph.js b/lib/optimize/InnerGraph.js new file mode 100644 index 000000000..a9769a1c7 --- /dev/null +++ b/lib/optimize/InnerGraph.js @@ -0,0 +1,90 @@ +/* + 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("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */ +/** @typedef {import("../dependencies/PureExpressionDependency")} PureExpressionDependency */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {Map | true>} InnerGraph */ +/** @typedef {false|{innerGraph: InnerGraph, allExportDependentDependencies: Set, currentTopLevelSymbol: TopLevelSymbol|void}} State */ + +/** @type {WeakMap} */ +const parserStateMap = new WeakMap(); +const topLevelSymbolTag = Symbol("top level symbol"); + +exports.parserStateMap = parserStateMap; + +/** + * @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(), + allExportDependentDependencies: new Set(), + currentTopLevelSymbol: undefined + }); +}; + +/** + * @param {ParserState} parserState parser state + * @returns {boolean} true, when enabled + */ +exports.isEnabled = parserState => { + const state = parserStateMap.get(parserState); + return !!state; +}; + +/** + * @param {ParserState} parserState parser state + * @returns {State} state + */ +exports.getState = parserState => { + return parserStateMap.get(parserState); +}; + +class TopLevelSymbol { + /** + * @param {string} name name of the function + * @param {InnerGraph} innerGraph reference to the graph + */ + constructor(name, innerGraph) { + this.name = name; + this.innerGraph = innerGraph; + } + + /** + * @param {string | TopLevelSymbol | true} dep export or top level symbol or always + * @returns {void} + */ + addDependency(dep) { + const info = this.innerGraph.get(this); + if (dep === true) { + this.innerGraph.set(this, true); + } else if (info === undefined) { + this.innerGraph.set(this, new Set([dep])); + } else if (info !== true) { + info.add(dep); + } + } +} + +exports.TopLevelSymbol = TopLevelSymbol; +exports.topLevelSymbolTag = topLevelSymbolTag; diff --git a/lib/optimize/InnerGraphPlugin.js b/lib/optimize/InnerGraphPlugin.js index 5dccd1af9..01e97a51c 100644 --- a/lib/optimize/InnerGraphPlugin.js +++ b/lib/optimize/InnerGraphPlugin.js @@ -9,14 +9,16 @@ const { harmonySpecifierTag } = require("../dependencies/HarmonyImportDependencyParserPlugin"); const PureExpressionDependency = require("../dependencies/PureExpressionDependency"); +const InnerGraph = require("./InnerGraph"); /** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */ /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./InnerGraph").InnerGraph} InnerGraph */ +/** @typedef {import("./InnerGraph").TopLevelSymbol} TopLevelSymbol */ -const topLevelSymbolTag = Symbol("top level symbol"); - -/** @typedef {Map | true>} InnerGraph */ +const { TopLevelSymbol, topLevelSymbolTag } = InnerGraph; /** * @param {any} expr an expression @@ -90,32 +92,6 @@ const isPure = (expr, parser, commentsStartPos) => { return false; }; -class TopLevelSymbol { - /** - * @param {string} name name of the function - * @param {InnerGraph} innerGraph reference to the graph - */ - constructor(name, innerGraph) { - this.name = name; - this.innerGraph = innerGraph; - } - - /** - * @param {string | TopLevelSymbol | true} dep export or top level symbol or always - * @returns {void} - */ - addDependency(dep) { - const info = this.innerGraph.get(this); - if (dep === true) { - this.innerGraph.set(this, true); - } else if (info === undefined) { - this.innerGraph.set(this, new Set([dep])); - } else if (info !== true) { - info.add(dep); - } - } -} - class InnerGraphPlugin { /** * @param {Compiler} compiler webpack compiler @@ -136,14 +112,17 @@ class InnerGraphPlugin { */ const handler = (parser, parserOptions) => { parser.hooks.program.tap("InnerGraphPlugin", () => { - parser.state.harmonyInnerGraph = new Map(); - parser.state.harmonyAllExportDependentDependencies = new Set(); + InnerGraph.enable(parser.state); }); parser.hooks.finish.tap("InnerGraphPlugin", () => { - if (parser.state.module.buildInfo.usingEval) return; - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; + + const { + innerGraph, + allExportDependentDependencies + } = innerGraphState; // flatten graph to terminal nodes (string, undefined or true) const nonTerminal = new Set(innerGraph.keys()); while (nonTerminal.size > 0) { @@ -188,8 +167,7 @@ class InnerGraphPlugin { } } - for (const dep of parser.state - .harmonyAllExportDependentDependencies) { + for (const dep of allExportDependentDependencies) { const value = innerGraph.get(dep); switch (value) { case undefined: @@ -199,7 +177,7 @@ class InnerGraphPlugin { dep.usedByExports = true; break; default: - dep.usedByExports = value; + dep.usedByExports = /** @type {Set} */ (value); break; } } @@ -207,10 +185,12 @@ class InnerGraphPlugin { /** @type {WeakMap<{}, TopLevelSymbol>} */ const statementWithTopLevelSymbol = new WeakMap(); parser.hooks.preStatement.tap("InnerGraphPlugin", statement => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; + if (parser.scope.topLevelScope === true) { if (statement.type === "FunctionDeclaration") { - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); + const { innerGraph } = innerGraphState; const name = statement.id ? statement.id.name : "*default*"; parser.defineVariable(name); const fn = new TopLevelSymbol(name, innerGraph); @@ -221,10 +201,12 @@ class InnerGraphPlugin { } }); parser.hooks.blockPreStatement.tap("InnerGraphPlugin", statement => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; + if (parser.scope.topLevelScope === true) { if (statement.type === "ClassDeclaration") { - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); + const { innerGraph } = innerGraphState; const name = statement.id ? statement.id.name : "*default*"; parser.defineVariable(name); const fn = new TopLevelSymbol(name, innerGraph); @@ -240,8 +222,7 @@ class InnerGraphPlugin { decl.type === "ClassExpression" || decl.type === "Identifier" ) { - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); + const { innerGraph } = innerGraphState; const name = "*default*"; parser.defineVariable(name); const fn = new TopLevelSymbol(name, innerGraph); @@ -251,9 +232,8 @@ class InnerGraphPlugin { } } }); - const tagVar = name => { - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); + const tagVar = (innerGraphState, name) => { + const { innerGraph } = innerGraphState; parser.defineVariable(name); const existingTag = parser.getTagData(name, topLevelSymbolTag); const fn = existingTag || new TopLevelSymbol(name, innerGraph); @@ -268,6 +248,8 @@ class InnerGraphPlugin { parser.hooks.preDeclarator.tap( "InnerGraphPlugin", (decl, statement) => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; if ( parser.scope.topLevelScope === true && decl.init && @@ -279,13 +261,13 @@ class InnerGraphPlugin { decl.init.type === "ClassExpression" ) { const name = decl.id.name; - const fn = tagVar(name); + const fn = tagVar(innerGraphState, name); declWithTopLevelSymbol.set(decl, fn); return true; } if (isPure(decl.init, parser, decl.id.range[1])) { const name = decl.id.name; - const fn = tagVar(name); + const fn = tagVar(innerGraphState, name); declWithTopLevelSymbol.set(decl, fn); pureDeclarators.add(decl); return true; @@ -294,43 +276,52 @@ class InnerGraphPlugin { } ); parser.hooks.statement.tap("InnerGraphPlugin", statement => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; if (parser.scope.topLevelScope === true) { - parser.state.currentTopLevelSymbol = undefined; + innerGraphState.currentTopLevelSymbol = undefined; const fn = statementWithTopLevelSymbol.get(statement); if (fn) { - parser.state.currentTopLevelSymbol = fn; + innerGraphState.currentTopLevelSymbol = fn; } } }); parser.hooks.declarator.tap("InnerGraphPlugin", (decl, statement) => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; + const { + innerGraph, + allExportDependentDependencies + } = innerGraphState; const fn = declWithTopLevelSymbol.get(decl); if (fn) { if (pureDeclarators.has(decl)) { - const innerGraph = - /** @type {InnerGraph} */ (parser.state.harmonyInnerGraph); const dep = new PureExpressionDependency(decl.init.range); dep.loc = decl.loc; parser.state.module.addDependency(dep); innerGraph.set(dep, new Set([fn])); - parser.state.harmonyAllExportDependentDependencies.add(dep); + allExportDependentDependencies.add(dep); } - parser.state.currentTopLevelSymbol = fn; + innerGraphState.currentTopLevelSymbol = fn; parser.walkExpression(decl.init); - parser.state.currentTopLevelSymbol = undefined; + innerGraphState.currentTopLevelSymbol = undefined; return true; } }); parser.hooks.expression .for(topLevelSymbolTag) .tap("InnerGraphPlugin", expr => { + const innerGraphState = InnerGraph.getState(parser.state); + if (!innerGraphState) return; const topLevelSymbol = /** @type {TopLevelSymbol} */ (parser.currentTagData); - const currentTopLevelSymbol = parser.state.currentTopLevelSymbol; + const { currentTopLevelSymbol } = innerGraphState; topLevelSymbol.addDependency(currentTopLevelSymbol || true); }); parser.hooks.assign .for(topLevelSymbolTag) .tap("InnerGraphPlugin", expr => { + if (!InnerGraph.isEnabled(parser.state)) return; if (expr.operator === "=") return true; }); }; @@ -340,16 +331,9 @@ class InnerGraphPlugin { normalModuleFactory.hooks.parser .for("javascript/esm") .tap("InnerGraphPlugin", handler); - - compilation.hooks.optimizeDependencies.tap( - "InnerGraphPlugin", - modules => {} - ); } ); } } module.exports = InnerGraphPlugin; -module.exports.TopLevelSymbol = TopLevelSymbol; -module.exports.topLevelSymbolTag = topLevelSymbolTag;