| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const RuntimeGlobals = require("../RuntimeGlobals");
- const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
- const formatLocation = require("../util/formatLocation");
- const { propertyAccess } = require("../util/property");
- const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
- const CommonJsExportsDependency = require("./CommonJsExportsDependency");
- const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
- const CommonJsObjectExportDependency = require("./CommonJsObjectExportDependency");
- const CommonJsRequireDependency = require("./CommonJsRequireDependency");
- const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
- const DynamicExports = require("./DynamicExports");
- const HarmonyExports = require("./HarmonyExports");
- const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
- const RuntimeRequirementsDependency = require("./RuntimeRequirementsDependency");
- const FUNCTION_PROPERTY_DROP_HEAD =
- CommonJsObjectExportDependency.FUNCTION_PROPERTY_DROP_HEAD;
- /**
- * @import {
- * AssignmentExpression,
- * CallExpression,
- * Expression,
- * FunctionExpression,
- * Node,
- * ObjectExpression,
- * Property,
- * SpreadElement,
- * Super,
- * ThisExpression
- * } from "estree"
- */
- /** @import { ExportInfoName } from "../Dependency" */
- /** @import ModuleGraph from "../ModuleGraph" */
- /** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
- /**
- * @import JavascriptParser, {
- * Range,
- * Members,
- * StatementPath
- * } from "../javascript/JavascriptParser"
- */
- /**
- * @import {
- * CommonJSDependencyBaseKeywords
- * } from "./CommonJsDependencyHelpers"
- */
- /**
- * @import {
- * CommonJsObjectExportKind
- * } from "./CommonJsObjectExportDependency"
- */
- /**
- * @import {
- * JavascriptModuleBuildMeta
- * } from "../javascript/JavascriptModule"
- */
- /**
- * This function takes a generic expression and detects whether it is an ObjectExpression.
- * This is used in the context of parsing CommonJS exports to get the value of the property descriptor
- * when the `exports` object is assigned to `Object.defineProperty`.
- *
- * In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore
- * webpack has to detect this case and get the value key of the property descriptor. See the following example
- * for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57
- *
- * This would be an example of a CommonJS module that exports an object with a property descriptor:
- * ```js
- * Object.defineProperty(exports, "__esModule", { value: true });
- * exports.foo = void 0;
- * exports.foo = "bar";
- * ```
- * @param {Expression} expr expression
- * @returns {Expression | undefined} returns the value of property descriptor
- */
- const getValueOfPropertyDescription = (expr) => {
- if (expr.type !== "ObjectExpression") return;
- for (const property of expr.properties) {
- if (property.type === "SpreadElement" || property.computed) continue;
- const key = property.key;
- if (key.type !== "Identifier" || key.name !== "value") continue;
- return /** @type {Expression} */ (property.value);
- }
- };
- /**
- * Extracts the re-exportable expression from a property descriptor, handling
- * both the eager `{ value: <expr> }` form and the lazy
- * `{ get: () => <expr> }` / `{ get() { return <expr>; } }` accessor form used
- * by barrel files (e.g. webpack's own `lib/index.js`). A `set` accessor cannot
- * be reproduced by the rewritten descriptor, so descriptors with a setter are
- * left to the generic handler (which keeps the descriptor verbatim).
- * @param {Expression} expr property descriptor expression
- * @returns {{ expr: Expression, getter: boolean } | undefined} the value expression and whether it is a lazy getter
- */
- const getReexportOfPropertyDescriptor = (expr) => {
- if (expr.type !== "ObjectExpression") return;
- /** @type {Expression | undefined} */
- let valueExpr;
- /** @type {Expression | undefined} */
- let getFn;
- for (const property of expr.properties) {
- if (property.type === "SpreadElement" || property.computed) continue;
- const key = property.key;
- if (key.type !== "Identifier") continue;
- // A setter would be silently dropped by the rewrite — bail out.
- if (key.name === "set") return;
- if (key.name === "value") {
- valueExpr = /** @type {Expression} */ (property.value);
- } else if (key.name === "get") {
- getFn = /** @type {Expression} */ (property.value);
- }
- }
- if (valueExpr) return { expr: valueExpr, getter: false };
- if (
- getFn &&
- (getFn.type === "FunctionExpression" ||
- getFn.type === "ArrowFunctionExpression")
- ) {
- // Arrow with expression body: `() => require("./x")`.
- if (getFn.body.type !== "BlockStatement") {
- return { expr: /** @type {Expression} */ (getFn.body), getter: true };
- }
- // Function body must be exactly `return <expr>;`.
- if (
- getFn.body.body.length === 1 &&
- getFn.body.body[0].type === "ReturnStatement" &&
- getFn.body.body[0].argument
- ) {
- return { expr: getFn.body.body[0].argument, getter: true };
- }
- }
- };
- /**
- * The purpose of this function is to check whether an expression is a truthy literal or not. This is
- * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
- * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
- * @param {Expression} expr expression being checked
- * @returns {boolean} true, when the expression is a truthy literal
- */
- const isTruthyLiteral = (expr) => {
- switch (expr.type) {
- case "Literal":
- return Boolean(expr.value);
- case "UnaryExpression":
- if (expr.operator === "!") return isFalsyLiteral(expr.argument);
- }
- return false;
- };
- /**
- * The purpose of this function is to check whether an expression is a falsy literal or not. This is
- * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
- * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
- * @param {Expression} expr expression being checked
- * @returns {boolean} true, when the expression is a falsy literal
- */
- const isFalsyLiteral = (expr) => {
- switch (expr.type) {
- case "Literal":
- return !expr.value;
- case "UnaryExpression":
- if (expr.operator === "!") return isTruthyLiteral(expr.argument);
- }
- return false;
- };
- /**
- * Parses require call.
- * @param {JavascriptParser} parser the parser
- * @param {Expression} expr expression
- * @returns {{ argument: BasicEvaluatedExpression, ids: ExportInfoName[] } | undefined} parsed call
- */
- const parseRequireCall = (parser, expr) => {
- /** @type {ExportInfoName[]} */
- 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() };
- };
- /**
- * Checks if a value is an AST node.
- * @param {unknown} value candidate
- * @returns {value is Node} true when the value is an AST node
- */
- const isNode = (value) =>
- typeof value === "object" &&
- value !== null &&
- typeof (/** @type {{ type?: unknown }} */ (value).type) === "string";
- /** Positional/metadata keys that never hold child AST nodes. */
- const NON_CHILD_KEYS = new Set([
- "type",
- "start",
- "end",
- "loc",
- "range",
- "leadingComments",
- "trailingComments"
- ]);
- /** @type {Map<string, string[]>} per node type: own keys that may hold child nodes */
- const childKeysByType = new Map();
- /**
- * Returns the keys of a node that may hold child nodes, excluding positional
- * metadata. Computed once per node type and cached: acorn initializes every
- * field of a given type (to `null` when absent), so the key set is stable, and
- * the cache stays allocation-free on the hot path after the first node of each
- * type is seen.
- * @param {Node} node node
- * @returns {string[]} candidate child keys
- */
- const getChildKeys = (node) => {
- let keys = childKeysByType.get(node.type);
- if (keys === undefined) {
- keys = Object.keys(node).filter((key) => !NON_CHILD_KEYS.has(key));
- childKeysByType.set(node.type, keys);
- }
- return keys;
- };
- /**
- * Searches for a `this` belonging to the scanned function's own `this`-scope.
- * Descends into arrow functions, but not into nested functions, static blocks
- * and class field values, which have their own `this`. Class method bodies are
- * function expressions and are skipped by the same rule, while computed keys
- * and `extends` clauses evaluate in the outer scope and are searched. The
- * `this`-scope boundary is enforced when a node is popped, so the generic
- * branch may enumerate any child without crossing it.
- * @param {Node} body function body
- * @param {Node[]} params function params
- * @returns {ThisExpression | undefined} first own `this` expression
- */
- const findOwnThisExpression = (body, params) => {
- // the body is popped first, so its `this` wins over a default parameter's
- const stack = [];
- for (let i = 0; i < params.length; i++) stack.push(params[i]);
- stack.push(body);
- let node;
- while ((node = stack.pop()) !== undefined) {
- switch (node.type) {
- case "ThisExpression":
- return node;
- case "FunctionExpression":
- case "FunctionDeclaration":
- case "StaticBlock":
- break;
- case "PropertyDefinition":
- if (node.computed) stack.push(node.key);
- break;
- default: {
- const children =
- /** @type {Record<string, unknown>} */
- (/** @type {unknown} */ (node));
- const keys = getChildKeys(node);
- for (let i = 0; i < keys.length; i++) {
- const value = children[keys[i]];
- if (Array.isArray(value)) {
- for (const item of value) {
- if (isNode(item)) stack.push(item);
- }
- } else if (isNode(value)) {
- stack.push(value);
- }
- }
- }
- }
- }
- };
- /**
- * Returns the own `this` of a function-expression export value. When such a
- * function is called as a method of the exports object, `this` is the exports
- * object and may reach any sibling export (#21178).
- * @param {JavascriptParser} parser the parser
- * @param {Node} expr the exported value
- * @returns {ThisExpression | undefined} first own `this` expression
- */
- const getThisAccessInExportedValue = (parser, expr) =>
- expr.type === "FunctionExpression" &&
- // scanning the body of every exported function is expensive and most of them
- // never mention `this`, so filter on the source text first
- parser.sourceMayContainIdentifier(/** @type {Range} */ (expr.range), "this")
- ? findOwnThisExpression(expr.body, expr.params)
- : undefined;
- /**
- * Returns the export name an object-literal property contributes, or
- * `undefined` when it cannot contribute one: a spread, a computed or
- * non-string key is unknown at compile time, and `__proto__` sets
- * [[Prototype]] instead of an own property.
- * @param {Property | SpreadElement} property property
- * @returns {string | undefined} export name
- */
- const getStaticPropertyName = (property) => {
- if (property.type === "SpreadElement" || property.computed) return;
- const key = property.key;
- /** @type {string | undefined} */
- let name;
- if (key.type === "Identifier") {
- name = key.name;
- } else if (key.type === "Literal" && typeof key.value === "string") {
- name = key.value;
- }
- return name === "__proto__" ? undefined : name;
- };
- /**
- * @param {Property} property property
- * @returns {CommonJsObjectExportKind} property kind
- */
- const getObjectExportKind = (property) => {
- if (property.kind === "get" || property.kind === "set") return property.kind;
- if (property.method) {
- const { async, generator } = /** @type {FunctionExpression} */ (
- property.value
- );
- if (async) return generator ? "asyncGeneratorMethod" : "asyncMethod";
- return generator ? "generatorMethod" : "method";
- }
- return property.shorthand ? "shorthand" : "keyValue";
- };
- const PLUGIN_NAME = "CommonJsExportsParserPlugin";
- class CommonJsExportsParserPlugin {
- /**
- * Creates an instance of CommonJsExportsParserPlugin.
- * @param {ModuleGraph} moduleGraph module graph
- */
- constructor(moduleGraph) {
- /** @type {ModuleGraph} */
- this.moduleGraph = moduleGraph;
- }
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- apply(parser) {
- const enableStructuredExports = () => {
- DynamicExports.enable(parser.state);
- };
- /**
- * Checks namespace.
- * @param {boolean} topLevel true, when the export is on top level
- * @param {Members} members members of the export
- * @param {Expression | undefined} valueExpr expression for the value
- * @returns {void}
- */
- 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);
- }
- }
- };
- /**
- * Processes the provided reason.
- * @param {string=} reason reason
- */
- const bailout = (reason) => {
- DynamicExports.bailout(parser.state);
- if (reason) bailoutHint(reason);
- };
- /**
- * Processes the provided reason.
- * @param {string} reason reason
- */
- const bailoutHint = (reason) => {
- this.moduleGraph
- .getOptimizationBailout(parser.state.module)
- .push(`CommonJS bailout: ${reason}`);
- };
- /** @type {unknown} */
- let keptAllExportsForState;
- /**
- * An exported function accessing its own `this` may reach any export
- * through it when called as a method of the exports object, so the
- * whole exports object must be kept (#21178).
- * @param {Node} valueExpr the exported value
- * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
- * @returns {void}
- */
- const keepAllExportsOnThisAccess = (valueExpr, base) => {
- // One bailout per module is enough
- if (keptAllExportsForState === parser.state) return;
- const thisExpr = getThisAccessInExportedValue(parser, valueExpr);
- if (thisExpr === undefined) return;
- keptAllExportsForState = parser.state;
- bailoutHint(
- `this in exported function may access any export, so all exports are kept, at ${formatLocation(
- parser.getLocation(thisExpr)
- )}`
- );
- const dep = new CommonJsSelfReferenceDependency(
- /** @type {Range} */ (thisExpr.range),
- base,
- [],
- false
- );
- dep.loc = parser.getLocation(thisExpr);
- parser.state.module.addDependency(dep);
- };
- // metadata //
- parser.hooks.evaluateTypeof
- .for("module")
- .tap(PLUGIN_NAME, evaluateToString("object"));
- parser.hooks.evaluateTypeof
- .for("exports")
- .tap(PLUGIN_NAME, evaluateToString("object"));
- // exporting //
- /**
- * Handle assign export.
- * @param {AssignmentExpression} expr expression
- * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
- * @param {Members} members members of the export
- * @returns {boolean | undefined} true, when the expression was handled
- */
- 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(
- /** @type {Range} */ (expr.range),
- null,
- base,
- members,
- /** @type {string} */ (requireCall.argument.string),
- requireCall.ids,
- !parser.isStatementLevelExpression(expr)
- );
- dep.loc = parser.getLocation(expr);
- dep.optional = Boolean(parser.scope.inTry);
- parser.state.module.addDependency(dep);
- /** @type {JavascriptModuleBuildMeta} */ (
- parser.state.module.buildMeta
- ).treatAsCommonJs = true;
- return true;
- }
- // `module.exports = { … }` — properties are the module's named exports.
- if (members.length === 0) {
- return handleObjectLiteralExport(expr, base);
- }
- enableStructuredExports();
- const remainingMembers = members;
- checkNamespace(
- /** @type {StatementPath} */
- (parser.statementPath).length === 1 &&
- parser.isStatementLevelExpression(expr),
- remainingMembers,
- expr.right
- );
- const dep = new CommonJsExportsDependency(
- /** @type {Range} */ (expr.left.range),
- null,
- base,
- remainingMembers
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- /** @type {JavascriptModuleBuildMeta} */ (
- parser.state.module.buildMeta
- ).treatAsCommonJs = true;
- keepAllExportsOnThisAccess(expr.right, base);
- parser.walkExpression(expr.right);
- return true;
- };
- /**
- * Handle top-level `module.exports = { … }` object-literal exports.
- * @param {AssignmentExpression} expr expression
- * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
- * @returns {boolean | undefined} true, when the expression was handled
- */
- const handleObjectLiteralExport = (expr, base) => {
- // Only `module.exports = { … }` replaces the whole exports object.
- if (base !== "module.exports") return;
- if (
- /** @type {StatementPath} */ (parser.statementPath).length !== 1 ||
- !parser.isStatementLevelExpression(expr)
- ) {
- return;
- }
- const object = expr.right;
- if (object.type !== "ObjectExpression") return;
- const module = parser.state.module;
- /** @type {JavascriptModuleBuildMeta} */ (
- module.buildMeta
- ).treatAsCommonJs = true;
- // LHS stays as the text `module.exports` (moduleArgument is "module"
- // unless a top-level `module` binding shadows it, which prevents this hook).
- module.addPresentationalDependency(
- new RuntimeRequirementsDependency([RuntimeGlobals.module])
- );
- // one unknown key makes the whole object opaque, so name them all first
- /** @type {string[]} */
- const names = [];
- for (const property of object.properties) {
- const name = getStaticPropertyName(property);
- if (name === undefined) {
- bailout(
- `module.exports = {…} cannot be statically analysed at ${formatLocation(
- parser.getLocation(expr)
- )}`
- );
- parser.walkExpression(object);
- return true;
- }
- names.push(name);
- }
- enableStructuredExports();
- for (let i = 0; i < object.properties.length; i++) {
- const property = /** @type {Property} */ (object.properties[i]);
- const name = names[i];
- const value = /** @type {Expression} */ (property.value);
- const kind = getObjectExportKind(property);
- // a shorthand has no value text of its own to replace
- const valueRange = /** @type {Range} */ (
- kind === "shorthand" ? property.range : value.range
- );
- // scans the key/value gap, so `/*#__PURE__*/` is picked up here
- const pure =
- kind === "keyValue" &&
- parser.isPure(value, /** @type {Range} */ (property.key.range)[1]);
- if (name === "__esModule") {
- checkNamespace(true, ["__esModule"], value);
- }
- const dep = new CommonJsObjectExportDependency(
- /** @type {Range} */ (property.range),
- /** @type {Range} */ (property.key.range),
- valueRange,
- name,
- kind,
- pure
- );
- dep.loc = parser.getLocation(property);
- module.addDependency(dep);
- keepAllExportsOnThisAccess(value, base);
- // Tag nested requires so unused get/set/method can disconnect them.
- const start = FUNCTION_PROPERTY_DROP_HEAD.has(kind)
- ? module.dependencies.length
- : -1;
- parser.walkExpression(value);
- if (start !== -1) {
- const usedByExports = new Set([name]);
- for (let j = start; j < module.dependencies.length; j++) {
- const nested = module.dependencies[j];
- if (
- nested instanceof CommonJsRequireDependency ||
- nested instanceof CommonJsFullRequireDependency
- ) {
- nested.usedByExports = usedByExports;
- }
- }
- }
- }
- return true;
- };
- parser.hooks.assignMemberChain
- .for("exports")
- .tap(PLUGIN_NAME, (expr, members) =>
- handleAssignExport(expr, "exports", members)
- );
- parser.hooks.assignMemberChain
- .for("this")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAssignExport(expr, "this", members);
- });
- parser.hooks.assignMemberChain
- .for("module")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAssignExport(expr, "module.exports", members.slice(1));
- });
- parser.hooks.call
- .for("Object.defineProperty")
- .tap(PLUGIN_NAME, (expression) => {
- const expr = /** @type {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];
- // Handle reexporting: `Object.defineProperty(exports, "x", { value: require("./y")[.z] })`
- // and the lazy getter form `{ get: () => require("./y")[.z] }`.
- if (property !== "__esModule") {
- const reexport = getReexportOfPropertyDescriptor(descArg);
- const requireCall =
- reexport && parseRequireCall(parser, reexport.expr);
- if (reexport && requireCall && requireCall.argument.isString()) {
- const dep = new CommonJsExportRequireDependency(
- /** @type {Range} */ (expr.range),
- /** @type {Range} */ (reexport.expr.range),
- `Object.defineProperty(${exportsArg.identifier})`,
- [property],
- /** @type {string} */ (requireCall.argument.string),
- requireCall.ids,
- false
- );
- dep.getter = reexport.getter;
- dep.loc = parser.getLocation(expr);
- dep.optional = Boolean(parser.scope.inTry);
- parser.state.module.addDependency(dep);
- /** @type {JavascriptModuleBuildMeta} */ (
- parser.state.module.buildMeta
- ).treatAsCommonJs = true;
- return true;
- }
- }
- checkNamespace(
- /** @type {StatementPath} */
- (parser.statementPath).length === 1,
- [property],
- getValueOfPropertyDescription(descArg)
- );
- const dep = new CommonJsExportsDependency(
- /** @type {Range} */ (expr.range),
- /** @type {Range} */ (expr.arguments[2].range),
- `Object.defineProperty(${exportsArg.identifier})`,
- [property]
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- /** @type {JavascriptModuleBuildMeta} */ (
- parser.state.module.buildMeta
- ).treatAsCommonJs = true;
- if (descArg.type === "ObjectExpression") {
- // `value`, `get` and `set` descriptor functions are all called
- // with the exports object as `this`
- for (const descProperty of descArg.properties) {
- if (descProperty.type === "SpreadElement") continue;
- keepAllExportsOnThisAccess(
- descProperty.value,
- /** @type {CommonJSDependencyBaseKeywords} */
- (exportsArg.identifier)
- );
- }
- }
- parser.walkExpression(expr.arguments[2]);
- return true;
- });
- // Self reference //
- /**
- * Handle access export.
- * @param {Expression | Super} expr expression
- * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
- * @param {Members} members members of the export
- * @param {CallExpression=} call call expression
- * @returns {boolean | void} true, when the expression was handled
- */
- const handleAccessExport = (expr, base, members, call) => {
- if (HarmonyExports.isEnabled(parser.state)) return;
- if (members.length === 0) {
- bailout(
- `${base} is used directly at ${formatLocation(
- parser.getLocation(expr)
- )}`
- );
- }
- if (call && members.length === 1) {
- bailoutHint(
- `${base}${propertyAccess(
- members
- )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation(
- parser.getLocation(expr)
- )}`
- );
- }
- const dep = new CommonJsSelfReferenceDependency(
- /** @type {Range} */ (expr.range),
- base,
- members,
- Boolean(call)
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- /** @type {JavascriptModuleBuildMeta} */ (
- parser.state.module.buildMeta
- ).treatAsCommonJs = true;
- if (call) {
- parser.walkExpressions(call.arguments);
- }
- return true;
- };
- parser.hooks.callMemberChain
- .for("exports")
- .tap(PLUGIN_NAME, (expr, members) =>
- handleAccessExport(expr.callee, "exports", members, expr)
- );
- parser.hooks.expressionMemberChain
- .for("exports")
- .tap(PLUGIN_NAME, (expr, members) =>
- handleAccessExport(expr, "exports", members)
- );
- parser.hooks.expression
- .for("exports")
- .tap(PLUGIN_NAME, (expr) => handleAccessExport(expr, "exports", []));
- parser.hooks.callMemberChain
- .for("module")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAccessExport(
- expr.callee,
- "module.exports",
- members.slice(1),
- expr
- );
- });
- parser.hooks.expressionMemberChain
- .for("module")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (members[0] !== "exports") return;
- return handleAccessExport(expr, "module.exports", members.slice(1));
- });
- parser.hooks.expression
- .for("module.exports")
- .tap(PLUGIN_NAME, (expr) =>
- handleAccessExport(expr, "module.exports", [])
- );
- parser.hooks.callMemberChain
- .for("this")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr.callee, "this", members, expr);
- });
- parser.hooks.expressionMemberChain
- .for("this")
- .tap(PLUGIN_NAME, (expr, members) => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr, "this", members);
- });
- parser.hooks.expression.for("this").tap(PLUGIN_NAME, (expr) => {
- if (!parser.scope.topLevelScope) return;
- return handleAccessExport(expr, "this", []);
- });
- // Bailouts //
- parser.hooks.expression.for("module").tap(PLUGIN_NAME, (expr) => {
- const isHarmony = HarmonyExports.isEnabled(parser.state);
- // Silent under ESM, where `HarmonyExports` governs and nothing was lost.
- bailout(
- isHarmony
- ? undefined
- : `module is used directly at ${formatLocation(
- parser.getLocation(expr)
- )}`
- );
- const dep = new ModuleDecoratorDependency(
- isHarmony
- ? RuntimeGlobals.harmonyModuleDecorator
- : RuntimeGlobals.nodeModuleDecorator,
- !isHarmony
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- return true;
- });
- }
- }
- module.exports = CommonJsExportsParserPlugin;
|