Buckets:
| /* | |
| MIT License http://www.opensource.org/licenses/mit-license.php | |
| Author Tobias Koppers @sokra | |
| */ | |
| ; | |
| const { | |
| JAVASCRIPT_MODULE_TYPE_AUTO, | |
| JAVASCRIPT_MODULE_TYPE_DYNAMIC, | |
| JAVASCRIPT_MODULE_TYPE_ESM | |
| } = require("./ModuleTypeConstants"); | |
| const CachedConstDependency = require("./dependencies/CachedConstDependency"); | |
| const ConstDependency = require("./dependencies/ConstDependency"); | |
| const { evaluateToString } = require("./javascript/JavascriptParserHelpers"); | |
| const { parseResource } = require("./util/identifier"); | |
| /** @typedef {import("estree").AssignmentProperty} AssignmentProperty */ | |
| /** @typedef {import("estree").Expression} Expression */ | |
| /** @typedef {import("estree").Identifier} Identifier */ | |
| /** @typedef {import("estree").Pattern} Pattern */ | |
| /** @typedef {import("estree").SourceLocation} SourceLocation */ | |
| /** @typedef {import("estree").Statement} Statement */ | |
| /** @typedef {import("estree").Super} Super */ | |
| /** @typedef {import("estree").VariableDeclaration} VariableDeclaration */ | |
| /** @typedef {import("./Compiler")} Compiler */ | |
| /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ | |
| /** @typedef {import("./javascript/JavascriptParser").Range} Range */ | |
| /** @typedef {Set<string>} Declarations */ | |
| /** | |
| * @param {Declarations} declarations set of declarations | |
| * @param {Identifier | Pattern} pattern pattern to collect declarations from | |
| */ | |
| const collectDeclaration = (declarations, pattern) => { | |
| const stack = [pattern]; | |
| while (stack.length > 0) { | |
| const node = /** @type {Pattern} */ (stack.pop()); | |
| switch (node.type) { | |
| case "Identifier": | |
| declarations.add(node.name); | |
| break; | |
| case "ArrayPattern": | |
| for (const element of node.elements) { | |
| if (element) { | |
| stack.push(element); | |
| } | |
| } | |
| break; | |
| case "AssignmentPattern": | |
| stack.push(node.left); | |
| break; | |
| case "ObjectPattern": | |
| for (const property of node.properties) { | |
| stack.push(/** @type {AssignmentProperty} */ (property).value); | |
| } | |
| break; | |
| case "RestElement": | |
| stack.push(node.argument); | |
| break; | |
| } | |
| } | |
| }; | |
| /** | |
| * @param {Statement} branch branch to get hoisted declarations from | |
| * @param {boolean} includeFunctionDeclarations whether to include function declarations | |
| * @returns {string[]} hoisted declarations | |
| */ | |
| const getHoistedDeclarations = (branch, includeFunctionDeclarations) => { | |
| /** @type {Declarations} */ | |
| const declarations = new Set(); | |
| /** @type {(Statement | null | undefined)[]} */ | |
| const stack = [branch]; | |
| while (stack.length > 0) { | |
| const node = stack.pop(); | |
| // Some node could be `null` or `undefined`. | |
| if (!node) continue; | |
| switch (node.type) { | |
| // Walk through control statements to look for hoisted declarations. | |
| // Some branches are skipped since they do not allow declarations. | |
| case "BlockStatement": | |
| for (const stmt of node.body) { | |
| stack.push(stmt); | |
| } | |
| break; | |
| case "IfStatement": | |
| stack.push(node.consequent); | |
| stack.push(node.alternate); | |
| break; | |
| case "ForStatement": | |
| stack.push(/** @type {VariableDeclaration} */ (node.init)); | |
| stack.push(node.body); | |
| break; | |
| case "ForInStatement": | |
| case "ForOfStatement": | |
| stack.push(/** @type {VariableDeclaration} */ (node.left)); | |
| stack.push(node.body); | |
| break; | |
| case "DoWhileStatement": | |
| case "WhileStatement": | |
| case "LabeledStatement": | |
| stack.push(node.body); | |
| break; | |
| case "SwitchStatement": | |
| for (const cs of node.cases) { | |
| for (const consequent of cs.consequent) { | |
| stack.push(consequent); | |
| } | |
| } | |
| break; | |
| case "TryStatement": | |
| stack.push(node.block); | |
| if (node.handler) { | |
| stack.push(node.handler.body); | |
| } | |
| stack.push(node.finalizer); | |
| break; | |
| case "FunctionDeclaration": | |
| if (includeFunctionDeclarations) { | |
| collectDeclaration(declarations, /** @type {Identifier} */ (node.id)); | |
| } | |
| break; | |
| case "VariableDeclaration": | |
| if (node.kind === "var") { | |
| for (const decl of node.declarations) { | |
| collectDeclaration(declarations, decl.id); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| return [...declarations]; | |
| }; | |
| const PLUGIN_NAME = "ConstPlugin"; | |
| class ConstPlugin { | |
| /** | |
| * Apply the plugin | |
| * @param {Compiler} compiler the compiler instance | |
| * @returns {void} | |
| */ | |
| apply(compiler) { | |
| const cachedParseResource = parseResource.bindCache(compiler.root); | |
| compiler.hooks.compilation.tap( | |
| PLUGIN_NAME, | |
| (compilation, { normalModuleFactory }) => { | |
| compilation.dependencyTemplates.set( | |
| ConstDependency, | |
| new ConstDependency.Template() | |
| ); | |
| compilation.dependencyTemplates.set( | |
| CachedConstDependency, | |
| new CachedConstDependency.Template() | |
| ); | |
| /** | |
| * @param {JavascriptParser} parser the parser | |
| */ | |
| const handler = (parser) => { | |
| parser.hooks.terminate.tap(PLUGIN_NAME, (_statement) => true); | |
| parser.hooks.statementIf.tap(PLUGIN_NAME, (statement) => { | |
| if (parser.scope.isAsmJs) return; | |
| const param = parser.evaluateExpression(statement.test); | |
| const bool = param.asBool(); | |
| if (typeof bool === "boolean") { | |
| if (!param.couldHaveSideEffects()) { | |
| const dep = new ConstDependency( | |
| `${bool}`, | |
| /** @type {Range} */ (param.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (statement.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } else { | |
| parser.walkExpression(statement.test); | |
| } | |
| const branchToRemove = bool | |
| ? statement.alternate | |
| : statement.consequent; | |
| if (branchToRemove) { | |
| this.eliminateUnusedStatement(parser, branchToRemove, true); | |
| } | |
| return bool; | |
| } | |
| }); | |
| parser.hooks.unusedStatement.tap(PLUGIN_NAME, (statement) => { | |
| if ( | |
| parser.scope.isAsmJs || | |
| // Check top level scope here again | |
| parser.scope.topLevelScope === true | |
| ) { | |
| return; | |
| } | |
| this.eliminateUnusedStatement(parser, statement, false); | |
| return true; | |
| }); | |
| parser.hooks.expressionConditionalOperator.tap( | |
| PLUGIN_NAME, | |
| (expression) => { | |
| if (parser.scope.isAsmJs) return; | |
| const param = parser.evaluateExpression(expression.test); | |
| const bool = param.asBool(); | |
| if (typeof bool === "boolean") { | |
| if (!param.couldHaveSideEffects()) { | |
| const dep = new ConstDependency( | |
| ` ${bool}`, | |
| /** @type {Range} */ (param.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expression.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } else { | |
| parser.walkExpression(expression.test); | |
| } | |
| // Expressions do not hoist. | |
| // It is safe to remove the dead branch. | |
| // | |
| // Given the following code: | |
| // | |
| // false ? someExpression() : otherExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // false ? 0 : otherExpression(); | |
| // | |
| const branchToRemove = bool | |
| ? expression.alternate | |
| : expression.consequent; | |
| const dep = new ConstDependency( | |
| "0", | |
| /** @type {Range} */ (branchToRemove.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (branchToRemove.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| return bool; | |
| } | |
| } | |
| ); | |
| parser.hooks.expressionLogicalOperator.tap( | |
| PLUGIN_NAME, | |
| (expression) => { | |
| if (parser.scope.isAsmJs) return; | |
| if ( | |
| expression.operator === "&&" || | |
| expression.operator === "||" | |
| ) { | |
| const param = parser.evaluateExpression(expression.left); | |
| const bool = param.asBool(); | |
| if (typeof bool === "boolean") { | |
| // Expressions do not hoist. | |
| // It is safe to remove the dead branch. | |
| // | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // falsyExpression() && someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // falsyExpression() && false; | |
| // | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // truthyExpression() && someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // true && someExpression(); | |
| // | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // truthyExpression() || someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // truthyExpression() || false; | |
| // | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // falsyExpression() || someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // false && someExpression(); | |
| // | |
| const keepRight = | |
| (expression.operator === "&&" && bool) || | |
| (expression.operator === "||" && !bool); | |
| if ( | |
| !param.couldHaveSideEffects() && | |
| (param.isBoolean() || keepRight) | |
| ) { | |
| // for case like | |
| // | |
| // return'development'===process.env.NODE_ENV&&'foo' | |
| // | |
| // we need a space before the bool to prevent result like | |
| // | |
| // returnfalse&&'foo' | |
| // | |
| const dep = new ConstDependency( | |
| ` ${bool}`, | |
| /** @type {Range} */ (param.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expression.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } else { | |
| parser.walkExpression(expression.left); | |
| } | |
| if (!keepRight) { | |
| const dep = new ConstDependency( | |
| "0", | |
| /** @type {Range} */ (expression.right.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expression.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } | |
| return keepRight; | |
| } | |
| } else if (expression.operator === "??") { | |
| const param = parser.evaluateExpression(expression.left); | |
| const keepRight = param.asNullish(); | |
| if (typeof keepRight === "boolean") { | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // nonNullish ?? someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // nonNullish ?? 0; | |
| // | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // nullish ?? someExpression(); | |
| // | |
| // the generated code is: | |
| // | |
| // null ?? someExpression(); | |
| // | |
| if (!param.couldHaveSideEffects() && keepRight) { | |
| // cspell:word returnnull | |
| // for case like | |
| // | |
| // return('development'===process.env.NODE_ENV&&null)??'foo' | |
| // | |
| // we need a space before the bool to prevent result like | |
| // | |
| // returnnull??'foo' | |
| // | |
| const dep = new ConstDependency( | |
| " null", | |
| /** @type {Range} */ (param.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expression.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } else { | |
| const dep = new ConstDependency( | |
| "0", | |
| /** @type {Range} */ (expression.right.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expression.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| parser.walkExpression(expression.left); | |
| } | |
| return keepRight; | |
| } | |
| } | |
| } | |
| ); | |
| parser.hooks.optionalChaining.tap(PLUGIN_NAME, (expr) => { | |
| /** @type {Expression[]} */ | |
| const optionalExpressionsStack = []; | |
| /** @type {Expression | Super} */ | |
| let next = expr.expression; | |
| while ( | |
| next.type === "MemberExpression" || | |
| next.type === "CallExpression" | |
| ) { | |
| if (next.type === "MemberExpression") { | |
| if (next.optional) { | |
| // SuperNode can not be optional | |
| optionalExpressionsStack.push( | |
| /** @type {Expression} */ (next.object) | |
| ); | |
| } | |
| next = next.object; | |
| } else { | |
| if (next.optional) { | |
| // SuperNode can not be optional | |
| optionalExpressionsStack.push( | |
| /** @type {Expression} */ (next.callee) | |
| ); | |
| } | |
| next = next.callee; | |
| } | |
| } | |
| while (optionalExpressionsStack.length) { | |
| const expression = optionalExpressionsStack.pop(); | |
| const evaluated = parser.evaluateExpression( | |
| /** @type {Expression} */ (expression) | |
| ); | |
| if (evaluated.asNullish()) { | |
| // ------------------------------------------ | |
| // | |
| // Given the following code: | |
| // | |
| // nullishMemberChain?.a.b(); | |
| // | |
| // the generated code is: | |
| // | |
| // undefined; | |
| // | |
| // ------------------------------------------ | |
| // | |
| const dep = new ConstDependency( | |
| " undefined", | |
| /** @type {Range} */ (expr.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expr.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| return true; | |
| } | |
| } | |
| }); | |
| parser.hooks.evaluateIdentifier | |
| .for("__resourceQuery") | |
| .tap(PLUGIN_NAME, (expr) => { | |
| if (parser.scope.isAsmJs) return; | |
| if (!parser.state.module) return; | |
| return evaluateToString( | |
| cachedParseResource(parser.state.module.resource).query | |
| )(expr); | |
| }); | |
| parser.hooks.expression | |
| .for("__resourceQuery") | |
| .tap(PLUGIN_NAME, (expr) => { | |
| if (parser.scope.isAsmJs) return; | |
| if (!parser.state.module) return; | |
| const dep = new CachedConstDependency( | |
| JSON.stringify( | |
| cachedParseResource(parser.state.module.resource).query | |
| ), | |
| /** @type {Range} */ (expr.range), | |
| "__resourceQuery" | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expr.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| return true; | |
| }); | |
| parser.hooks.evaluateIdentifier | |
| .for("__resourceFragment") | |
| .tap(PLUGIN_NAME, (expr) => { | |
| if (parser.scope.isAsmJs) return; | |
| if (!parser.state.module) return; | |
| return evaluateToString( | |
| cachedParseResource(parser.state.module.resource).fragment | |
| )(expr); | |
| }); | |
| parser.hooks.expression | |
| .for("__resourceFragment") | |
| .tap(PLUGIN_NAME, (expr) => { | |
| if (parser.scope.isAsmJs) return; | |
| if (!parser.state.module) return; | |
| const dep = new CachedConstDependency( | |
| JSON.stringify( | |
| cachedParseResource(parser.state.module.resource).fragment | |
| ), | |
| /** @type {Range} */ (expr.range), | |
| "__resourceFragment" | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (expr.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| return true; | |
| }); | |
| }; | |
| normalModuleFactory.hooks.parser | |
| .for(JAVASCRIPT_MODULE_TYPE_AUTO) | |
| .tap(PLUGIN_NAME, handler); | |
| normalModuleFactory.hooks.parser | |
| .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) | |
| .tap(PLUGIN_NAME, handler); | |
| normalModuleFactory.hooks.parser | |
| .for(JAVASCRIPT_MODULE_TYPE_ESM) | |
| .tap(PLUGIN_NAME, handler); | |
| } | |
| ); | |
| } | |
| /** | |
| * Eliminate an unused statement. | |
| * @param {JavascriptParser} parser the parser | |
| * @param {Statement} statement the statement to remove | |
| * @param {boolean} alwaysInBlock whether to always generate curly brackets | |
| * @returns {void} | |
| */ | |
| eliminateUnusedStatement(parser, statement, alwaysInBlock) { | |
| // Before removing the unused branch, the hoisted declarations | |
| // must be collected. | |
| // | |
| // Given the following code: | |
| // | |
| // if (true) f() else g() | |
| // if (false) { | |
| // function f() {} | |
| // const g = function g() {} | |
| // if (someTest) { | |
| // let a = 1 | |
| // var x, {y, z} = obj | |
| // } | |
| // } else { | |
| // … | |
| // } | |
| // | |
| // the generated code is: | |
| // | |
| // if (true) f() else {} | |
| // if (false) { | |
| // var f, x, y, z; (in loose mode) | |
| // var x, y, z; (in strict mode) | |
| // } else { | |
| // … | |
| // } | |
| // | |
| // NOTE: When code runs in strict mode, `var` declarations | |
| // are hoisted but `function` declarations don't. | |
| // | |
| const declarations = parser.scope.isStrict | |
| ? getHoistedDeclarations(statement, false) | |
| : getHoistedDeclarations(statement, true); | |
| const inBlock = alwaysInBlock || statement.type === "BlockStatement"; | |
| let replacement = inBlock ? "{" : ""; | |
| replacement += | |
| declarations.length > 0 ? ` var ${declarations.join(", ")}; ` : ""; | |
| replacement += inBlock ? "}" : ""; | |
| const dep = new ConstDependency( | |
| `// removed by dead control flow\n${replacement}`, | |
| /** @type {Range} */ (statement.range) | |
| ); | |
| dep.loc = /** @type {SourceLocation} */ (statement.loc); | |
| parser.state.module.addPresentationalDependency(dep); | |
| } | |
| } | |
| module.exports = ConstPlugin; | |
Xet Storage Details
- Size:
- 17.4 kB
- Xet hash:
- 5b735020523059a7b08085bbed3327b87baac6b71d5b2541785ac524a586068d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.