CommonJsExportsParserPlugin.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
  8. const formatLocation = require("../util/formatLocation");
  9. const { propertyAccess } = require("../util/property");
  10. const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
  11. const CommonJsExportsDependency = require("./CommonJsExportsDependency");
  12. const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
  13. const CommonJsObjectExportDependency = require("./CommonJsObjectExportDependency");
  14. const CommonJsRequireDependency = require("./CommonJsRequireDependency");
  15. const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
  16. const DynamicExports = require("./DynamicExports");
  17. const HarmonyExports = require("./HarmonyExports");
  18. const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
  19. const RuntimeRequirementsDependency = require("./RuntimeRequirementsDependency");
  20. const FUNCTION_PROPERTY_DROP_HEAD =
  21. CommonJsObjectExportDependency.FUNCTION_PROPERTY_DROP_HEAD;
  22. /**
  23. * @import {
  24. * AssignmentExpression,
  25. * CallExpression,
  26. * Expression,
  27. * FunctionExpression,
  28. * Node,
  29. * ObjectExpression,
  30. * Property,
  31. * SpreadElement,
  32. * Super,
  33. * ThisExpression
  34. * } from "estree"
  35. */
  36. /** @import { ExportInfoName } from "../Dependency" */
  37. /** @import ModuleGraph from "../ModuleGraph" */
  38. /** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
  39. /**
  40. * @import JavascriptParser, {
  41. * Range,
  42. * Members,
  43. * StatementPath
  44. * } from "../javascript/JavascriptParser"
  45. */
  46. /**
  47. * @import {
  48. * CommonJSDependencyBaseKeywords
  49. * } from "./CommonJsDependencyHelpers"
  50. */
  51. /**
  52. * @import {
  53. * CommonJsObjectExportKind
  54. * } from "./CommonJsObjectExportDependency"
  55. */
  56. /**
  57. * @import {
  58. * JavascriptModuleBuildMeta
  59. * } from "../javascript/JavascriptModule"
  60. */
  61. /**
  62. * This function takes a generic expression and detects whether it is an ObjectExpression.
  63. * This is used in the context of parsing CommonJS exports to get the value of the property descriptor
  64. * when the `exports` object is assigned to `Object.defineProperty`.
  65. *
  66. * In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore
  67. * webpack has to detect this case and get the value key of the property descriptor. See the following example
  68. * for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57
  69. *
  70. * This would be an example of a CommonJS module that exports an object with a property descriptor:
  71. * ```js
  72. * Object.defineProperty(exports, "__esModule", { value: true });
  73. * exports.foo = void 0;
  74. * exports.foo = "bar";
  75. * ```
  76. * @param {Expression} expr expression
  77. * @returns {Expression | undefined} returns the value of property descriptor
  78. */
  79. const getValueOfPropertyDescription = (expr) => {
  80. if (expr.type !== "ObjectExpression") return;
  81. for (const property of expr.properties) {
  82. if (property.type === "SpreadElement" || property.computed) continue;
  83. const key = property.key;
  84. if (key.type !== "Identifier" || key.name !== "value") continue;
  85. return /** @type {Expression} */ (property.value);
  86. }
  87. };
  88. /**
  89. * Extracts the re-exportable expression from a property descriptor, handling
  90. * both the eager `{ value: <expr> }` form and the lazy
  91. * `{ get: () => <expr> }` / `{ get() { return <expr>; } }` accessor form used
  92. * by barrel files (e.g. webpack's own `lib/index.js`). A `set` accessor cannot
  93. * be reproduced by the rewritten descriptor, so descriptors with a setter are
  94. * left to the generic handler (which keeps the descriptor verbatim).
  95. * @param {Expression} expr property descriptor expression
  96. * @returns {{ expr: Expression, getter: boolean } | undefined} the value expression and whether it is a lazy getter
  97. */
  98. const getReexportOfPropertyDescriptor = (expr) => {
  99. if (expr.type !== "ObjectExpression") return;
  100. /** @type {Expression | undefined} */
  101. let valueExpr;
  102. /** @type {Expression | undefined} */
  103. let getFn;
  104. for (const property of expr.properties) {
  105. if (property.type === "SpreadElement" || property.computed) continue;
  106. const key = property.key;
  107. if (key.type !== "Identifier") continue;
  108. // A setter would be silently dropped by the rewrite — bail out.
  109. if (key.name === "set") return;
  110. if (key.name === "value") {
  111. valueExpr = /** @type {Expression} */ (property.value);
  112. } else if (key.name === "get") {
  113. getFn = /** @type {Expression} */ (property.value);
  114. }
  115. }
  116. if (valueExpr) return { expr: valueExpr, getter: false };
  117. if (
  118. getFn &&
  119. (getFn.type === "FunctionExpression" ||
  120. getFn.type === "ArrowFunctionExpression")
  121. ) {
  122. // Arrow with expression body: `() => require("./x")`.
  123. if (getFn.body.type !== "BlockStatement") {
  124. return { expr: /** @type {Expression} */ (getFn.body), getter: true };
  125. }
  126. // Function body must be exactly `return <expr>;`.
  127. if (
  128. getFn.body.body.length === 1 &&
  129. getFn.body.body[0].type === "ReturnStatement" &&
  130. getFn.body.body[0].argument
  131. ) {
  132. return { expr: getFn.body.body[0].argument, getter: true };
  133. }
  134. }
  135. };
  136. /**
  137. * The purpose of this function is to check whether an expression is a truthy literal or not. This is
  138. * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
  139. * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
  140. * @param {Expression} expr expression being checked
  141. * @returns {boolean} true, when the expression is a truthy literal
  142. */
  143. const isTruthyLiteral = (expr) => {
  144. switch (expr.type) {
  145. case "Literal":
  146. return Boolean(expr.value);
  147. case "UnaryExpression":
  148. if (expr.operator === "!") return isFalsyLiteral(expr.argument);
  149. }
  150. return false;
  151. };
  152. /**
  153. * The purpose of this function is to check whether an expression is a falsy literal or not. This is
  154. * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
  155. * values like `null` and `false`. However, exports should only be created if the exported value is truthy.
  156. * @param {Expression} expr expression being checked
  157. * @returns {boolean} true, when the expression is a falsy literal
  158. */
  159. const isFalsyLiteral = (expr) => {
  160. switch (expr.type) {
  161. case "Literal":
  162. return !expr.value;
  163. case "UnaryExpression":
  164. if (expr.operator === "!") return isTruthyLiteral(expr.argument);
  165. }
  166. return false;
  167. };
  168. /**
  169. * Parses require call.
  170. * @param {JavascriptParser} parser the parser
  171. * @param {Expression} expr expression
  172. * @returns {{ argument: BasicEvaluatedExpression, ids: ExportInfoName[] } | undefined} parsed call
  173. */
  174. const parseRequireCall = (parser, expr) => {
  175. /** @type {ExportInfoName[]} */
  176. const ids = [];
  177. while (expr.type === "MemberExpression") {
  178. if (expr.object.type === "Super") return;
  179. if (!expr.property) return;
  180. const prop = expr.property;
  181. if (expr.computed) {
  182. if (prop.type !== "Literal") return;
  183. ids.push(`${prop.value}`);
  184. } else {
  185. if (prop.type !== "Identifier") return;
  186. ids.push(prop.name);
  187. }
  188. expr = expr.object;
  189. }
  190. if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return;
  191. const callee = expr.callee;
  192. if (
  193. callee.type !== "Identifier" ||
  194. parser.getVariableInfo(callee.name) !== "require"
  195. ) {
  196. return;
  197. }
  198. const arg = expr.arguments[0];
  199. if (arg.type === "SpreadElement") return;
  200. const argValue = parser.evaluateExpression(arg);
  201. return { argument: argValue, ids: ids.reverse() };
  202. };
  203. /**
  204. * Checks if a value is an AST node.
  205. * @param {unknown} value candidate
  206. * @returns {value is Node} true when the value is an AST node
  207. */
  208. const isNode = (value) =>
  209. typeof value === "object" &&
  210. value !== null &&
  211. typeof (/** @type {{ type?: unknown }} */ (value).type) === "string";
  212. /** Positional/metadata keys that never hold child AST nodes. */
  213. const NON_CHILD_KEYS = new Set([
  214. "type",
  215. "start",
  216. "end",
  217. "loc",
  218. "range",
  219. "leadingComments",
  220. "trailingComments"
  221. ]);
  222. /** @type {Map<string, string[]>} per node type: own keys that may hold child nodes */
  223. const childKeysByType = new Map();
  224. /**
  225. * Returns the keys of a node that may hold child nodes, excluding positional
  226. * metadata. Computed once per node type and cached: acorn initializes every
  227. * field of a given type (to `null` when absent), so the key set is stable, and
  228. * the cache stays allocation-free on the hot path after the first node of each
  229. * type is seen.
  230. * @param {Node} node node
  231. * @returns {string[]} candidate child keys
  232. */
  233. const getChildKeys = (node) => {
  234. let keys = childKeysByType.get(node.type);
  235. if (keys === undefined) {
  236. keys = Object.keys(node).filter((key) => !NON_CHILD_KEYS.has(key));
  237. childKeysByType.set(node.type, keys);
  238. }
  239. return keys;
  240. };
  241. /**
  242. * Searches for a `this` belonging to the scanned function's own `this`-scope.
  243. * Descends into arrow functions, but not into nested functions, static blocks
  244. * and class field values, which have their own `this`. Class method bodies are
  245. * function expressions and are skipped by the same rule, while computed keys
  246. * and `extends` clauses evaluate in the outer scope and are searched. The
  247. * `this`-scope boundary is enforced when a node is popped, so the generic
  248. * branch may enumerate any child without crossing it.
  249. * @param {Node} body function body
  250. * @param {Node[]} params function params
  251. * @returns {ThisExpression | undefined} first own `this` expression
  252. */
  253. const findOwnThisExpression = (body, params) => {
  254. // the body is popped first, so its `this` wins over a default parameter's
  255. const stack = [];
  256. for (let i = 0; i < params.length; i++) stack.push(params[i]);
  257. stack.push(body);
  258. let node;
  259. while ((node = stack.pop()) !== undefined) {
  260. switch (node.type) {
  261. case "ThisExpression":
  262. return node;
  263. case "FunctionExpression":
  264. case "FunctionDeclaration":
  265. case "StaticBlock":
  266. break;
  267. case "PropertyDefinition":
  268. if (node.computed) stack.push(node.key);
  269. break;
  270. default: {
  271. const children =
  272. /** @type {Record<string, unknown>} */
  273. (/** @type {unknown} */ (node));
  274. const keys = getChildKeys(node);
  275. for (let i = 0; i < keys.length; i++) {
  276. const value = children[keys[i]];
  277. if (Array.isArray(value)) {
  278. for (const item of value) {
  279. if (isNode(item)) stack.push(item);
  280. }
  281. } else if (isNode(value)) {
  282. stack.push(value);
  283. }
  284. }
  285. }
  286. }
  287. }
  288. };
  289. /**
  290. * Returns the own `this` of a function-expression export value. When such a
  291. * function is called as a method of the exports object, `this` is the exports
  292. * object and may reach any sibling export (#21178).
  293. * @param {JavascriptParser} parser the parser
  294. * @param {Node} expr the exported value
  295. * @returns {ThisExpression | undefined} first own `this` expression
  296. */
  297. const getThisAccessInExportedValue = (parser, expr) =>
  298. expr.type === "FunctionExpression" &&
  299. // scanning the body of every exported function is expensive and most of them
  300. // never mention `this`, so filter on the source text first
  301. parser.sourceMayContainIdentifier(/** @type {Range} */ (expr.range), "this")
  302. ? findOwnThisExpression(expr.body, expr.params)
  303. : undefined;
  304. /**
  305. * Returns the export name an object-literal property contributes, or
  306. * `undefined` when it cannot contribute one: a spread, a computed or
  307. * non-string key is unknown at compile time, and `__proto__` sets
  308. * [[Prototype]] instead of an own property.
  309. * @param {Property | SpreadElement} property property
  310. * @returns {string | undefined} export name
  311. */
  312. const getStaticPropertyName = (property) => {
  313. if (property.type === "SpreadElement" || property.computed) return;
  314. const key = property.key;
  315. /** @type {string | undefined} */
  316. let name;
  317. if (key.type === "Identifier") {
  318. name = key.name;
  319. } else if (key.type === "Literal" && typeof key.value === "string") {
  320. name = key.value;
  321. }
  322. return name === "__proto__" ? undefined : name;
  323. };
  324. /**
  325. * @param {Property} property property
  326. * @returns {CommonJsObjectExportKind} property kind
  327. */
  328. const getObjectExportKind = (property) => {
  329. if (property.kind === "get" || property.kind === "set") return property.kind;
  330. if (property.method) {
  331. const { async, generator } = /** @type {FunctionExpression} */ (
  332. property.value
  333. );
  334. if (async) return generator ? "asyncGeneratorMethod" : "asyncMethod";
  335. return generator ? "generatorMethod" : "method";
  336. }
  337. return property.shorthand ? "shorthand" : "keyValue";
  338. };
  339. const PLUGIN_NAME = "CommonJsExportsParserPlugin";
  340. class CommonJsExportsParserPlugin {
  341. /**
  342. * Creates an instance of CommonJsExportsParserPlugin.
  343. * @param {ModuleGraph} moduleGraph module graph
  344. */
  345. constructor(moduleGraph) {
  346. /** @type {ModuleGraph} */
  347. this.moduleGraph = moduleGraph;
  348. }
  349. /**
  350. * Applies the plugin by registering its hooks on the compiler.
  351. * @param {JavascriptParser} parser the parser
  352. * @returns {void}
  353. */
  354. apply(parser) {
  355. const enableStructuredExports = () => {
  356. DynamicExports.enable(parser.state);
  357. };
  358. /**
  359. * Checks namespace.
  360. * @param {boolean} topLevel true, when the export is on top level
  361. * @param {Members} members members of the export
  362. * @param {Expression | undefined} valueExpr expression for the value
  363. * @returns {void}
  364. */
  365. const checkNamespace = (topLevel, members, valueExpr) => {
  366. if (!DynamicExports.isEnabled(parser.state)) return;
  367. if (members.length > 0 && members[0] === "__esModule") {
  368. if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) {
  369. DynamicExports.setFlagged(parser.state);
  370. } else {
  371. DynamicExports.setDynamic(parser.state);
  372. }
  373. }
  374. };
  375. /**
  376. * Processes the provided reason.
  377. * @param {string=} reason reason
  378. */
  379. const bailout = (reason) => {
  380. DynamicExports.bailout(parser.state);
  381. if (reason) bailoutHint(reason);
  382. };
  383. /**
  384. * Processes the provided reason.
  385. * @param {string} reason reason
  386. */
  387. const bailoutHint = (reason) => {
  388. this.moduleGraph
  389. .getOptimizationBailout(parser.state.module)
  390. .push(`CommonJS bailout: ${reason}`);
  391. };
  392. /** @type {unknown} */
  393. let keptAllExportsForState;
  394. /**
  395. * An exported function accessing its own `this` may reach any export
  396. * through it when called as a method of the exports object, so the
  397. * whole exports object must be kept (#21178).
  398. * @param {Node} valueExpr the exported value
  399. * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
  400. * @returns {void}
  401. */
  402. const keepAllExportsOnThisAccess = (valueExpr, base) => {
  403. // One bailout per module is enough
  404. if (keptAllExportsForState === parser.state) return;
  405. const thisExpr = getThisAccessInExportedValue(parser, valueExpr);
  406. if (thisExpr === undefined) return;
  407. keptAllExportsForState = parser.state;
  408. bailoutHint(
  409. `this in exported function may access any export, so all exports are kept, at ${formatLocation(
  410. parser.getLocation(thisExpr)
  411. )}`
  412. );
  413. const dep = new CommonJsSelfReferenceDependency(
  414. /** @type {Range} */ (thisExpr.range),
  415. base,
  416. [],
  417. false
  418. );
  419. dep.loc = parser.getLocation(thisExpr);
  420. parser.state.module.addDependency(dep);
  421. };
  422. // metadata //
  423. parser.hooks.evaluateTypeof
  424. .for("module")
  425. .tap(PLUGIN_NAME, evaluateToString("object"));
  426. parser.hooks.evaluateTypeof
  427. .for("exports")
  428. .tap(PLUGIN_NAME, evaluateToString("object"));
  429. // exporting //
  430. /**
  431. * Handle assign export.
  432. * @param {AssignmentExpression} expr expression
  433. * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
  434. * @param {Members} members members of the export
  435. * @returns {boolean | undefined} true, when the expression was handled
  436. */
  437. const handleAssignExport = (expr, base, members) => {
  438. if (HarmonyExports.isEnabled(parser.state)) return;
  439. // Handle reexporting
  440. const requireCall = parseRequireCall(parser, expr.right);
  441. if (
  442. requireCall &&
  443. requireCall.argument.isString() &&
  444. (members.length === 0 || members[0] !== "__esModule")
  445. ) {
  446. enableStructuredExports();
  447. // It's possible to reexport __esModule, so we must convert to a dynamic module
  448. if (members.length === 0) DynamicExports.setDynamic(parser.state);
  449. const dep = new CommonJsExportRequireDependency(
  450. /** @type {Range} */ (expr.range),
  451. null,
  452. base,
  453. members,
  454. /** @type {string} */ (requireCall.argument.string),
  455. requireCall.ids,
  456. !parser.isStatementLevelExpression(expr)
  457. );
  458. dep.loc = parser.getLocation(expr);
  459. dep.optional = Boolean(parser.scope.inTry);
  460. parser.state.module.addDependency(dep);
  461. /** @type {JavascriptModuleBuildMeta} */ (
  462. parser.state.module.buildMeta
  463. ).treatAsCommonJs = true;
  464. return true;
  465. }
  466. // `module.exports = { … }` — properties are the module's named exports.
  467. if (members.length === 0) {
  468. return handleObjectLiteralExport(expr, base);
  469. }
  470. enableStructuredExports();
  471. const remainingMembers = members;
  472. checkNamespace(
  473. /** @type {StatementPath} */
  474. (parser.statementPath).length === 1 &&
  475. parser.isStatementLevelExpression(expr),
  476. remainingMembers,
  477. expr.right
  478. );
  479. const dep = new CommonJsExportsDependency(
  480. /** @type {Range} */ (expr.left.range),
  481. null,
  482. base,
  483. remainingMembers
  484. );
  485. dep.loc = parser.getLocation(expr);
  486. parser.state.module.addDependency(dep);
  487. /** @type {JavascriptModuleBuildMeta} */ (
  488. parser.state.module.buildMeta
  489. ).treatAsCommonJs = true;
  490. keepAllExportsOnThisAccess(expr.right, base);
  491. parser.walkExpression(expr.right);
  492. return true;
  493. };
  494. /**
  495. * Handle top-level `module.exports = { … }` object-literal exports.
  496. * @param {AssignmentExpression} expr expression
  497. * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
  498. * @returns {boolean | undefined} true, when the expression was handled
  499. */
  500. const handleObjectLiteralExport = (expr, base) => {
  501. // Only `module.exports = { … }` replaces the whole exports object.
  502. if (base !== "module.exports") return;
  503. if (
  504. /** @type {StatementPath} */ (parser.statementPath).length !== 1 ||
  505. !parser.isStatementLevelExpression(expr)
  506. ) {
  507. return;
  508. }
  509. const object = expr.right;
  510. if (object.type !== "ObjectExpression") return;
  511. const module = parser.state.module;
  512. /** @type {JavascriptModuleBuildMeta} */ (
  513. module.buildMeta
  514. ).treatAsCommonJs = true;
  515. // LHS stays as the text `module.exports` (moduleArgument is "module"
  516. // unless a top-level `module` binding shadows it, which prevents this hook).
  517. module.addPresentationalDependency(
  518. new RuntimeRequirementsDependency([RuntimeGlobals.module])
  519. );
  520. // one unknown key makes the whole object opaque, so name them all first
  521. /** @type {string[]} */
  522. const names = [];
  523. for (const property of object.properties) {
  524. const name = getStaticPropertyName(property);
  525. if (name === undefined) {
  526. bailout(
  527. `module.exports = {…} cannot be statically analysed at ${formatLocation(
  528. parser.getLocation(expr)
  529. )}`
  530. );
  531. parser.walkExpression(object);
  532. return true;
  533. }
  534. names.push(name);
  535. }
  536. enableStructuredExports();
  537. for (let i = 0; i < object.properties.length; i++) {
  538. const property = /** @type {Property} */ (object.properties[i]);
  539. const name = names[i];
  540. const value = /** @type {Expression} */ (property.value);
  541. const kind = getObjectExportKind(property);
  542. // a shorthand has no value text of its own to replace
  543. const valueRange = /** @type {Range} */ (
  544. kind === "shorthand" ? property.range : value.range
  545. );
  546. // scans the key/value gap, so `/*#__PURE__*/` is picked up here
  547. const pure =
  548. kind === "keyValue" &&
  549. parser.isPure(value, /** @type {Range} */ (property.key.range)[1]);
  550. if (name === "__esModule") {
  551. checkNamespace(true, ["__esModule"], value);
  552. }
  553. const dep = new CommonJsObjectExportDependency(
  554. /** @type {Range} */ (property.range),
  555. /** @type {Range} */ (property.key.range),
  556. valueRange,
  557. name,
  558. kind,
  559. pure
  560. );
  561. dep.loc = parser.getLocation(property);
  562. module.addDependency(dep);
  563. keepAllExportsOnThisAccess(value, base);
  564. // Tag nested requires so unused get/set/method can disconnect them.
  565. const start = FUNCTION_PROPERTY_DROP_HEAD.has(kind)
  566. ? module.dependencies.length
  567. : -1;
  568. parser.walkExpression(value);
  569. if (start !== -1) {
  570. const usedByExports = new Set([name]);
  571. for (let j = start; j < module.dependencies.length; j++) {
  572. const nested = module.dependencies[j];
  573. if (
  574. nested instanceof CommonJsRequireDependency ||
  575. nested instanceof CommonJsFullRequireDependency
  576. ) {
  577. nested.usedByExports = usedByExports;
  578. }
  579. }
  580. }
  581. }
  582. return true;
  583. };
  584. parser.hooks.assignMemberChain
  585. .for("exports")
  586. .tap(PLUGIN_NAME, (expr, members) =>
  587. handleAssignExport(expr, "exports", members)
  588. );
  589. parser.hooks.assignMemberChain
  590. .for("this")
  591. .tap(PLUGIN_NAME, (expr, members) => {
  592. if (!parser.scope.topLevelScope) return;
  593. return handleAssignExport(expr, "this", members);
  594. });
  595. parser.hooks.assignMemberChain
  596. .for("module")
  597. .tap(PLUGIN_NAME, (expr, members) => {
  598. if (members[0] !== "exports") return;
  599. return handleAssignExport(expr, "module.exports", members.slice(1));
  600. });
  601. parser.hooks.call
  602. .for("Object.defineProperty")
  603. .tap(PLUGIN_NAME, (expression) => {
  604. const expr = /** @type {CallExpression} */ (expression);
  605. if (!parser.isStatementLevelExpression(expr)) return;
  606. if (expr.arguments.length !== 3) return;
  607. if (expr.arguments[0].type === "SpreadElement") return;
  608. if (expr.arguments[1].type === "SpreadElement") return;
  609. if (expr.arguments[2].type === "SpreadElement") return;
  610. const exportsArg = parser.evaluateExpression(expr.arguments[0]);
  611. if (!exportsArg.isIdentifier()) return;
  612. if (
  613. exportsArg.identifier !== "exports" &&
  614. exportsArg.identifier !== "module.exports" &&
  615. (exportsArg.identifier !== "this" || !parser.scope.topLevelScope)
  616. ) {
  617. return;
  618. }
  619. const propertyArg = parser.evaluateExpression(expr.arguments[1]);
  620. const property = propertyArg.asString();
  621. if (typeof property !== "string") return;
  622. enableStructuredExports();
  623. const descArg = expr.arguments[2];
  624. // Handle reexporting: `Object.defineProperty(exports, "x", { value: require("./y")[.z] })`
  625. // and the lazy getter form `{ get: () => require("./y")[.z] }`.
  626. if (property !== "__esModule") {
  627. const reexport = getReexportOfPropertyDescriptor(descArg);
  628. const requireCall =
  629. reexport && parseRequireCall(parser, reexport.expr);
  630. if (reexport && requireCall && requireCall.argument.isString()) {
  631. const dep = new CommonJsExportRequireDependency(
  632. /** @type {Range} */ (expr.range),
  633. /** @type {Range} */ (reexport.expr.range),
  634. `Object.defineProperty(${exportsArg.identifier})`,
  635. [property],
  636. /** @type {string} */ (requireCall.argument.string),
  637. requireCall.ids,
  638. false
  639. );
  640. dep.getter = reexport.getter;
  641. dep.loc = parser.getLocation(expr);
  642. dep.optional = Boolean(parser.scope.inTry);
  643. parser.state.module.addDependency(dep);
  644. /** @type {JavascriptModuleBuildMeta} */ (
  645. parser.state.module.buildMeta
  646. ).treatAsCommonJs = true;
  647. return true;
  648. }
  649. }
  650. checkNamespace(
  651. /** @type {StatementPath} */
  652. (parser.statementPath).length === 1,
  653. [property],
  654. getValueOfPropertyDescription(descArg)
  655. );
  656. const dep = new CommonJsExportsDependency(
  657. /** @type {Range} */ (expr.range),
  658. /** @type {Range} */ (expr.arguments[2].range),
  659. `Object.defineProperty(${exportsArg.identifier})`,
  660. [property]
  661. );
  662. dep.loc = parser.getLocation(expr);
  663. parser.state.module.addDependency(dep);
  664. /** @type {JavascriptModuleBuildMeta} */ (
  665. parser.state.module.buildMeta
  666. ).treatAsCommonJs = true;
  667. if (descArg.type === "ObjectExpression") {
  668. // `value`, `get` and `set` descriptor functions are all called
  669. // with the exports object as `this`
  670. for (const descProperty of descArg.properties) {
  671. if (descProperty.type === "SpreadElement") continue;
  672. keepAllExportsOnThisAccess(
  673. descProperty.value,
  674. /** @type {CommonJSDependencyBaseKeywords} */
  675. (exportsArg.identifier)
  676. );
  677. }
  678. }
  679. parser.walkExpression(expr.arguments[2]);
  680. return true;
  681. });
  682. // Self reference //
  683. /**
  684. * Handle access export.
  685. * @param {Expression | Super} expr expression
  686. * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
  687. * @param {Members} members members of the export
  688. * @param {CallExpression=} call call expression
  689. * @returns {boolean | void} true, when the expression was handled
  690. */
  691. const handleAccessExport = (expr, base, members, call) => {
  692. if (HarmonyExports.isEnabled(parser.state)) return;
  693. if (members.length === 0) {
  694. bailout(
  695. `${base} is used directly at ${formatLocation(
  696. parser.getLocation(expr)
  697. )}`
  698. );
  699. }
  700. if (call && members.length === 1) {
  701. bailoutHint(
  702. `${base}${propertyAccess(
  703. members
  704. )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation(
  705. parser.getLocation(expr)
  706. )}`
  707. );
  708. }
  709. const dep = new CommonJsSelfReferenceDependency(
  710. /** @type {Range} */ (expr.range),
  711. base,
  712. members,
  713. Boolean(call)
  714. );
  715. dep.loc = parser.getLocation(expr);
  716. parser.state.module.addDependency(dep);
  717. /** @type {JavascriptModuleBuildMeta} */ (
  718. parser.state.module.buildMeta
  719. ).treatAsCommonJs = true;
  720. if (call) {
  721. parser.walkExpressions(call.arguments);
  722. }
  723. return true;
  724. };
  725. parser.hooks.callMemberChain
  726. .for("exports")
  727. .tap(PLUGIN_NAME, (expr, members) =>
  728. handleAccessExport(expr.callee, "exports", members, expr)
  729. );
  730. parser.hooks.expressionMemberChain
  731. .for("exports")
  732. .tap(PLUGIN_NAME, (expr, members) =>
  733. handleAccessExport(expr, "exports", members)
  734. );
  735. parser.hooks.expression
  736. .for("exports")
  737. .tap(PLUGIN_NAME, (expr) => handleAccessExport(expr, "exports", []));
  738. parser.hooks.callMemberChain
  739. .for("module")
  740. .tap(PLUGIN_NAME, (expr, members) => {
  741. if (members[0] !== "exports") return;
  742. return handleAccessExport(
  743. expr.callee,
  744. "module.exports",
  745. members.slice(1),
  746. expr
  747. );
  748. });
  749. parser.hooks.expressionMemberChain
  750. .for("module")
  751. .tap(PLUGIN_NAME, (expr, members) => {
  752. if (members[0] !== "exports") return;
  753. return handleAccessExport(expr, "module.exports", members.slice(1));
  754. });
  755. parser.hooks.expression
  756. .for("module.exports")
  757. .tap(PLUGIN_NAME, (expr) =>
  758. handleAccessExport(expr, "module.exports", [])
  759. );
  760. parser.hooks.callMemberChain
  761. .for("this")
  762. .tap(PLUGIN_NAME, (expr, members) => {
  763. if (!parser.scope.topLevelScope) return;
  764. return handleAccessExport(expr.callee, "this", members, expr);
  765. });
  766. parser.hooks.expressionMemberChain
  767. .for("this")
  768. .tap(PLUGIN_NAME, (expr, members) => {
  769. if (!parser.scope.topLevelScope) return;
  770. return handleAccessExport(expr, "this", members);
  771. });
  772. parser.hooks.expression.for("this").tap(PLUGIN_NAME, (expr) => {
  773. if (!parser.scope.topLevelScope) return;
  774. return handleAccessExport(expr, "this", []);
  775. });
  776. // Bailouts //
  777. parser.hooks.expression.for("module").tap(PLUGIN_NAME, (expr) => {
  778. const isHarmony = HarmonyExports.isEnabled(parser.state);
  779. // Silent under ESM, where `HarmonyExports` governs and nothing was lost.
  780. bailout(
  781. isHarmony
  782. ? undefined
  783. : `module is used directly at ${formatLocation(
  784. parser.getLocation(expr)
  785. )}`
  786. );
  787. const dep = new ModuleDecoratorDependency(
  788. isHarmony
  789. ? RuntimeGlobals.harmonyModuleDecorator
  790. : RuntimeGlobals.nodeModuleDecorator,
  791. !isHarmony
  792. );
  793. dep.loc = parser.getLocation(expr);
  794. parser.state.module.addDependency(dep);
  795. return true;
  796. });
  797. }
  798. }
  799. module.exports = CommonJsExportsParserPlugin;