123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const RuntimeGlobals = require("../RuntimeGlobals");
- const formatLocation = require("../formatLocation");
- const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
- const propertyAccess = require("../util/propertyAccess");
- const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
- const CommonJsExportsDependency = require("./CommonJsExportsDependency");
- const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
- const DynamicExports = require("./DynamicExports");
- const HarmonyExports = require("./HarmonyExports");
- const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
- /** @typedef {import("estree").Expression} ExpressionNode */
- /** @typedef {import("../NormalModule")} NormalModule */
- /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
- /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
- const getValueOfPropertyDescription = expr => {
- if (expr.type !== "ObjectExpression") return;
- for (const property of expr.properties) {
- if (property.computed) continue;
- const key = property.key;
- if (key.type !== "Identifier" || key.name !== "value") continue;
- return property.value;
- }
- };
- const isTruthyLiteral = expr => {
- switch (expr.type) {
- case "Literal":
- return !!expr.value;
- case "UnaryExpression":
- if (expr.operator === "!") return isFalsyLiteral(expr.argument);
- }
- return false;
- };
- const isFalsyLiteral = expr => {
- switch (expr.type) {
- case "Literal":
- return !expr.value;
- case "UnaryExpression":
- if (expr.operator === "!") return isTruthyLiteral(expr.argument);
- }
- return false;
- };
- /**
- * @param {JavascriptParser} parser the parser
- * @param {ExpressionNode} expr expression
- * @returns {{ argument: BasicEvaluatedExpression, ids: string[] } | undefined} parsed call
- */
- const parseRequireCall = (parser, expr) => {
- const ids = [];
- while (expr.type === "MemberExpression") {
- if (expr.object.type === "Super") return;
- if (!expr.property) return;
- const prop = expr.property;
- if (expr.computed) {
- if (prop.type !== "Literal") return;
- ids.push(`${prop.value}`);
- } else {
- if (prop.type !== "Identifier") return;
- ids.push(prop.name);
- }
- expr = expr.object;
- }
- if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return;
- const callee = expr.callee;
- if (
- callee.type !== "Identifier" ||
- parser.getVariableInfo(callee.name) !== "require"
- ) {
- return;
- }
- const arg = expr.arguments[0];
- if (arg.type === "SpreadElement") return;
- const argValue = parser.evaluateExpression(arg);
- return { argument: argValue, ids: ids.reverse() };
- };
- class CommonJsExportsParserPlugin {
- constructor(moduleGraph) {
- this.moduleGraph = moduleGraph;
- }
- /**
- * @param {JavascriptParser} parser the parser
- */
- apply(parser) {
- const enableStructuredExports = () => {
- DynamicExports.enable(parser.state);
- };
- const checkNamespace = (topLevel, members, valueExpr) => {
- if (!DynamicExports.isEnabled(parser.state)) return;
- if (members.length > 0 && members[0] === "__esModule") {
- if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) {
- DynamicExports.setFlagged(parser.state);
- } else {
- DynamicExports.setDynamic(parser.state);
- }
- }
- };
- const bailout = reason => {
- DynamicExports.bailout(parser.state);
- if (reason) bailoutHint(reason);
- };
- const bailoutHint = reason => {
- this.moduleGraph
- .getOptimizationBailout(parser.state.module)
- .push(`CommonJS bailout: ${reason}`);
- };
- // metadata //
- parser.hooks.evaluateTypeof
- .for("module")
- .tap("CommonJsExportsParserPlugin", evaluateToString("object"));
- parser.hooks.evaluateTypeof
- .for("exports")
- .tap("CommonJsPlugin", evaluateToString("object"));
- // exporting //
- const handleAssignExport = (expr, base, members) => {
- if (HarmonyExports.isEnabled(parser.state)) return;
- // Handle reexporting
- const requireCall = parseRequireCall(parser, expr.right);
- if (
- requireCall &&
- requireCall.argument.isString() &&
- (members.length === 0 || members[0] !== "__esModule")
- ) {
- enableStructuredExports();
- // It's possible to reexport __esModule, so we must convert to a dynamic module
- if (members.length === 0) DynamicExports.setDynamic(parser.state);
- const dep = new CommonJsExportRequireDependency(
- expr.range,
- null,
- base,
- members,
- requireCall.argument.string,
- requireCall.ids,
- !parser.isStatementLevelExpression(expr)
- );
- dep.loc = expr.loc;
- dep.optional = !!parser.scope.inTry;
- parser.state.module.addDependency(dep);
- return true;
- }
- if (members.length === 0) return;
- enableStructuredExports();
- const remainingMembers = members;
- checkNamespace(
- parser.statementPath.length === 1 &&
- parser.isStatementLevelExpression(expr),
- remainingMembers,
- expr.right
- );
- const dep = new CommonJsExportsDependency(
- expr.left.range,
- null,
- base,
- remainingMembers
- );
- dep.loc = expr.loc;
- parser.state.module.addDependency(dep);
- parser.walkExpression(expr.right);
- return true;
- };
- parser.hooks.assignMemberChain
- .for("exports")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- return handleAssignExport(expr, "exports", members);
- });
- parser.hooks.assignMemberChain
- .for("this")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAssignExport(expr, "this", members);
- });
- parser.hooks.assignMemberChain
- .for("module")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAssignExport(expr, "module.exports", members.slice(1));
- });
- parser.hooks.call
- .for("Object.defineProperty")
- .tap("CommonJsExportsParserPlugin", expression => {
- const expr = /** @type {import("estree").CallExpression} */ (
- expression
- );
- if (!parser.isStatementLevelExpression(expr)) return;
- if (expr.arguments.length !== 3) return;
- if (expr.arguments[0].type === "SpreadElement") return;
- if (expr.arguments[1].type === "SpreadElement") return;
- if (expr.arguments[2].type === "SpreadElement") return;
- const exportsArg = parser.evaluateExpression(expr.arguments[0]);
- if (!exportsArg.isIdentifier()) return;
- if (
- exportsArg.identifier !== "exports" &&
- exportsArg.identifier !== "module.exports" &&
- (exportsArg.identifier !== "this" || !parser.scope.topLevelScope)
- ) {
- return;
- }
- const propertyArg = parser.evaluateExpression(expr.arguments[1]);
- const property = propertyArg.asString();
- if (typeof property !== "string") return;
- enableStructuredExports();
- const descArg = expr.arguments[2];
- checkNamespace(
- parser.statementPath.length === 1,
- [property],
- getValueOfPropertyDescription(descArg)
- );
- const dep = new CommonJsExportsDependency(
- expr.range,
- expr.arguments[2].range,
- `Object.defineProperty(${exportsArg.identifier})`,
- [property]
- );
- dep.loc = expr.loc;
- parser.state.module.addDependency(dep);
- parser.walkExpression(expr.arguments[2]);
- return true;
- });
- // Self reference //
- const handleAccessExport = (expr, base, members, call = undefined) => {
- if (HarmonyExports.isEnabled(parser.state)) return;
- if (members.length === 0) {
- bailout(`${base} is used directly at ${formatLocation(expr.loc)}`);
- }
- if (call && members.length === 1) {
- bailoutHint(
- `${base}${propertyAccess(
- members
- )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation(
- expr.loc
- )}`
- );
- }
- const dep = new CommonJsSelfReferenceDependency(
- expr.range,
- base,
- members,
- !!call
- );
- dep.loc = expr.loc;
- parser.state.module.addDependency(dep);
- if (call) {
- parser.walkExpressions(call.arguments);
- }
- return true;
- };
- parser.hooks.callMemberChain
- .for("exports")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- return handleAccessExport(expr.callee, "exports", members, expr);
- });
- parser.hooks.expressionMemberChain
- .for("exports")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- return handleAccessExport(expr, "exports", members);
- });
- parser.hooks.expression
- .for("exports")
- .tap("CommonJsExportsParserPlugin", expr => {
- return handleAccessExport(expr, "exports", []);
- });
- parser.hooks.callMemberChain
- .for("module")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAccessExport(
- expr.callee,
- "module.exports",
- members.slice(1),
- expr
- );
- });
- parser.hooks.expressionMemberChain
- .for("module")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAccessExport(expr, "module.exports", members.slice(1));
- });
- parser.hooks.expression
- .for("module.exports")
- .tap("CommonJsExportsParserPlugin", expr => {
- return handleAccessExport(expr, "module.exports", []);
- });
- parser.hooks.callMemberChain
- .for("this")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr.callee, "this", members, expr);
- });
- parser.hooks.expressionMemberChain
- .for("this")
- .tap("CommonJsExportsParserPlugin", (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr, "this", members);
- });
- parser.hooks.expression
- .for("this")
- .tap("CommonJsExportsParserPlugin", expr => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr, "this", []);
- });
- // Bailouts //
- parser.hooks.expression.for("module").tap("CommonJsPlugin", expr => {
- bailout();
- const isHarmony = HarmonyExports.isEnabled(parser.state);
- const dep = new ModuleDecoratorDependency(
- isHarmony
- ? RuntimeGlobals.harmonyModuleDecorator
- : RuntimeGlobals.nodeModuleDecorator,
- !isHarmony
- );
- dep.loc = expr.loc;
- parser.state.module.addDependency(dep);
- return true;
- });
- }
- }
- module.exports = CommonJsExportsParserPlugin;
|