CommonJsImportsParserPlugin.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  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 {
  8. evaluateToIdentifier,
  9. evaluateToString,
  10. expressionIsUnsupported,
  11. toConstantDependency
  12. } = require("../javascript/JavascriptParserHelpers");
  13. const memoize = require("../util/memoize");
  14. const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties");
  15. const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
  16. const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency");
  17. const CommonJsRequireDependency = require("./CommonJsRequireDependency");
  18. const ConstDependency = require("./ConstDependency");
  19. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  20. const HarmonyImportGuard = require("./HarmonyImportGuard");
  21. const LocalModuleDependency = require("./LocalModuleDependency");
  22. const { getLocalModule } = require("./LocalModulesHelpers");
  23. const RequireHeaderDependency = require("./RequireHeaderDependency");
  24. const RequireResolveContextDependency = require("./RequireResolveContextDependency");
  25. const RequireResolveDependency = require("./RequireResolveDependency");
  26. const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency");
  27. const getUnsupportedFeatureWarning = memoize(() =>
  28. require("../errors/UnsupportedFeatureWarning")
  29. );
  30. const getCommentCompilationWarning = memoize(() =>
  31. require("../errors/CommentCompilationWarning")
  32. );
  33. /** @import { CallExpression, Expression, NewExpression } from "estree" */
  34. /**
  35. * @import {
  36. * JavascriptParserOptions
  37. * } from "../../declarations/WebpackOptions"
  38. */
  39. /** @import { RawReferencedExports } from "../Dependency" */
  40. /**
  41. * @import JavascriptParser, {
  42. * Range,
  43. * Members,
  44. * CalleeMembers
  45. * } from "../javascript/JavascriptParser"
  46. */
  47. /** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
  48. /** @import LocalModule from "./LocalModule" */
  49. /**
  50. * Defines the common js import settings type used by this module.
  51. * @typedef {object} CommonJsImportSettings
  52. * @property {string=} name
  53. * @property {string} context
  54. */
  55. /**
  56. * Per-`const NAME = require(LITERAL)` binding state used to forward
  57. * member-access references on `NAME` to the `CommonJsRequireDependency`
  58. * created for the `require()` call.
  59. * @typedef {object} RequireBindingData
  60. * @property {RawReferencedExports | null} referencedExports mutable list, pushed to as `NAME.x.y` accesses are walked; null means the whole namespace is used. Separate from `dep` so a deferred require (walked after accesses on `NAME`) still collects them; becomes the dep's shared list on creation.
  61. * @property {InstanceType<typeof import("./CommonJsRequireDependency")> | null} dep dependency for the `require()` call, null until the `require()` is walked; kept so a later whole-namespace use can null its list directly
  62. */
  63. const REQUIRE_BINDING_TAG = Symbol(
  64. "CommonJsImportsParserPlugin require binding"
  65. );
  66. const PLUGIN_NAME = "CommonJsImportsParserPlugin";
  67. /**
  68. * Checks whether this object is require call expression.
  69. * @param {Expression} expression expression
  70. * @returns {boolean} true, when expression is `require(...)` or `module.require(...)`
  71. */
  72. const isRequireCallExpression = (expression) => {
  73. if (expression.type !== "CallExpression") return false;
  74. const { callee } = expression;
  75. if (callee.type === "Identifier") {
  76. return callee.name === "require";
  77. }
  78. if (callee.type === "MemberExpression" && !callee.computed) {
  79. const object = callee.object;
  80. const property = callee.property;
  81. return (
  82. object.type === "Identifier" &&
  83. object.name === "module" &&
  84. property.type === "Identifier" &&
  85. property.name === "require"
  86. );
  87. }
  88. return false;
  89. };
  90. /**
  91. * Gets require referenced exports from destructuring.
  92. * @param {JavascriptParser} parser parser
  93. * @param {CallExpression | NewExpression} expr expression
  94. * @returns {RawReferencedExports | null} referenced exports from destructuring
  95. */
  96. const getRequireReferencedExportsFromDestructuring = (parser, expr) => {
  97. const referencedPropertiesInDestructuring =
  98. parser.destructuringAssignmentPropertiesFor(expr);
  99. if (!referencedPropertiesInDestructuring) return null;
  100. /** @type {RawReferencedExports} */
  101. const referencedExports = [];
  102. traverseDestructuringAssignmentProperties(
  103. referencedPropertiesInDestructuring,
  104. (stack) => referencedExports.push(stack.map((p) => p.id))
  105. );
  106. return referencedExports;
  107. };
  108. /**
  109. * Creates a require cache dependency.
  110. * @param {JavascriptParser} parser parser
  111. * @returns {(expr: Expression) => boolean} handler
  112. */
  113. const createRequireCacheDependency = (parser) =>
  114. toConstantDependency(parser, RuntimeGlobals.moduleCache, [
  115. RuntimeGlobals.moduleCache,
  116. RuntimeGlobals.moduleId,
  117. RuntimeGlobals.moduleLoaded
  118. ]);
  119. /**
  120. * Creates a require as expression handler.
  121. * @param {JavascriptParser} parser parser
  122. * @param {JavascriptParserOptions} options options
  123. * @param {() => undefined | string} getContext context accessor
  124. * @returns {(expr: Expression) => boolean} handler
  125. */
  126. const createRequireAsExpressionHandler =
  127. (parser, options, getContext) => (expr) => {
  128. const dep = new CommonJsRequireContextDependency(
  129. {
  130. request: /** @type {string} */ (options.unknownContextRequest),
  131. recursive: /** @type {boolean} */ (options.unknownContextRecursive),
  132. regExp: /** @type {RegExp} */ (options.unknownContextRegExp),
  133. mode: "sync"
  134. },
  135. /** @type {Range} */ (expr.range),
  136. undefined,
  137. parser.scope.inShorthand,
  138. getContext()
  139. );
  140. dep.critical =
  141. options.unknownContextCritical &&
  142. "require function is used in a way in which dependencies cannot be statically extracted";
  143. dep.loc = parser.getLocation(expr);
  144. dep.optional = Boolean(parser.scope.inTry);
  145. parser.state.current.addDependency(dep);
  146. return true;
  147. };
  148. /**
  149. * Creates a require call handler.
  150. * @param {JavascriptParser} parser parser
  151. * @param {JavascriptParserOptions} options options
  152. * @param {() => undefined | string} getContext context accessor
  153. * @param {WeakMap<CallExpression, RequireBindingData>=} requireBindingData per-parser `const NAME = require(LITERAL)` binding state; only the free-`require` plugin populates it
  154. * @returns {(callNew: boolean) => (expr: CallExpression | NewExpression) => (boolean | void)} handler factory
  155. */
  156. const createRequireCallHandler = (
  157. parser,
  158. options,
  159. getContext,
  160. requireBindingData
  161. ) => {
  162. /**
  163. * Process require item.
  164. * @param {CallExpression | NewExpression} expr expression
  165. * @param {BasicEvaluatedExpression} param param
  166. * @param {boolean=} allowEvaluationOnly when false (conditional require), keep full namespace use
  167. * @returns {CommonJsRequireDependency | undefined} created dependency when handled
  168. */
  169. const processRequireItem = (expr, param, allowEvaluationOnly = true) => {
  170. if (param.isString()) {
  171. let referencedExports = getRequireReferencedExportsFromDestructuring(
  172. parser,
  173. expr
  174. );
  175. const binding =
  176. requireBindingData &&
  177. requireBindingData.get(/** @type {CallExpression} */ (expr));
  178. if (binding && !referencedExports) {
  179. // `const NAME = require(LITERAL)` — let later member-access walks
  180. // on `NAME` populate the dependency's referenced exports.
  181. referencedExports = binding.referencedExports;
  182. } else if (
  183. allowEvaluationOnly &&
  184. referencedExports === null &&
  185. !binding &&
  186. parser.isStatementLevelExpression(expr)
  187. ) {
  188. // Bare `require(...)` as a statement: result discarded → evaluation only.
  189. referencedExports = [];
  190. }
  191. const dep = new CommonJsRequireDependency(
  192. /** @type {string} */ (param.string),
  193. /** @type {Range} */ (param.range),
  194. getContext(),
  195. referencedExports,
  196. /** @type {Range} */ (expr.range)
  197. );
  198. if (binding) binding.dep = dep;
  199. dep.loc = parser.getLocation(expr);
  200. dep.optional = Boolean(parser.scope.inTry);
  201. parser.state.current.addDependency(dep);
  202. HarmonyImportGuard.attachDependencyGuards(parser, dep);
  203. return dep;
  204. }
  205. };
  206. /**
  207. * Process require context.
  208. * @param {CallExpression | NewExpression} expr expression
  209. * @param {BasicEvaluatedExpression} param param
  210. * @returns {boolean | void} true when handled
  211. */
  212. const processRequireContext = (expr, param) => {
  213. const referencedExports = getRequireReferencedExportsFromDestructuring(
  214. parser,
  215. expr
  216. );
  217. const dep = ContextDependencyHelpers.create(
  218. CommonJsRequireContextDependency,
  219. /** @type {Range} */ (expr.range),
  220. param,
  221. expr,
  222. options,
  223. {
  224. category: "commonjs",
  225. referencedExports
  226. },
  227. parser,
  228. undefined,
  229. getContext()
  230. );
  231. if (!dep) return;
  232. dep.loc = parser.getLocation(expr);
  233. dep.optional = Boolean(parser.scope.inTry);
  234. parser.state.current.addDependency(dep);
  235. return true;
  236. };
  237. return (callNew) => (expr) => {
  238. if (options.commonjsMagicComments) {
  239. const { options: requireOptions, errors: commentErrors } =
  240. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  241. if (commentErrors) {
  242. for (const e of commentErrors) {
  243. const { comment } = e;
  244. parser.state.module.addWarning(
  245. new (getCommentCompilationWarning())(
  246. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  247. parser.getLocation(comment)
  248. )
  249. );
  250. }
  251. }
  252. if (requireOptions && requireOptions.webpackIgnore !== undefined) {
  253. if (typeof requireOptions.webpackIgnore !== "boolean") {
  254. parser.state.module.addWarning(
  255. new (getUnsupportedFeatureWarning())(
  256. `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
  257. parser.getLocation(expr)
  258. )
  259. );
  260. } else if (requireOptions.webpackIgnore) {
  261. // Do not instrument `require()` if `webpackIgnore` is `true`
  262. return true;
  263. }
  264. }
  265. }
  266. if (expr.arguments.length !== 1) return;
  267. /** @type {null | LocalModule} */
  268. let localModule;
  269. const param = parser.evaluateExpression(expr.arguments[0]);
  270. if (param.isConditional()) {
  271. let isExpression = false;
  272. for (const p of /** @type {BasicEvaluatedExpression[]} */ (
  273. param.options
  274. )) {
  275. // Shared valueRange across branches: never mark evaluation-only.
  276. const requireDep = processRequireItem(expr, p, false);
  277. if (requireDep === undefined) {
  278. isExpression = true;
  279. }
  280. }
  281. if (!isExpression) {
  282. const dep = new RequireHeaderDependency(
  283. /** @type {Range} */ (expr.callee.range)
  284. );
  285. dep.loc = parser.getLocation(expr);
  286. parser.state.module.addPresentationalDependency(dep);
  287. return true;
  288. }
  289. }
  290. if (
  291. param.isString() &&
  292. (localModule = getLocalModule(
  293. parser.state,
  294. /** @type {string} */ (param.string)
  295. ))
  296. ) {
  297. localModule.flagUsed();
  298. const dep = new LocalModuleDependency(
  299. localModule,
  300. /** @type {Range} */ (expr.range),
  301. callNew
  302. );
  303. dep.loc = parser.getLocation(expr);
  304. parser.state.module.addPresentationalDependency(dep);
  305. } else {
  306. const requireDep = processRequireItem(expr, param);
  307. if (requireDep === undefined) {
  308. processRequireContext(expr, param);
  309. } else {
  310. requireDep._canConcatenate = true;
  311. // concatenation replaces the whole NewExpression; the template
  312. // reapplies the `new` return-value rule
  313. requireDep.callNew = callNew;
  314. const dep = new RequireHeaderDependency(
  315. /** @type {Range} */ (expr.callee.range),
  316. requireDep
  317. );
  318. dep.loc = parser.getLocation(expr);
  319. parser.state.module.addPresentationalDependency(dep);
  320. }
  321. }
  322. return true;
  323. };
  324. };
  325. /**
  326. * Creates a process resolve handler.
  327. * @param {JavascriptParser} parser parser
  328. * @param {JavascriptParserOptions} options options
  329. * @param {() => undefined | string} getContext context accessor
  330. * @returns {(expr: CallExpression, weak: boolean) => (boolean | void)} resolver
  331. */
  332. const createProcessResolveHandler = (parser, options, getContext) => {
  333. /**
  334. * Process resolve item.
  335. * @param {CallExpression} expr call expression
  336. * @param {BasicEvaluatedExpression} param param
  337. * @param {boolean} weak weak
  338. * @returns {boolean | void} true when handled
  339. */
  340. const processResolveItem = (expr, param, weak) => {
  341. if (param.isString()) {
  342. const dep = new RequireResolveDependency(
  343. /** @type {string} */ (param.string),
  344. /** @type {Range} */ (param.range),
  345. getContext()
  346. );
  347. dep.loc = parser.getLocation(expr);
  348. dep.optional = Boolean(parser.scope.inTry);
  349. dep.weak = weak;
  350. parser.state.current.addDependency(dep);
  351. return true;
  352. }
  353. };
  354. /**
  355. * Process resolve context.
  356. * @param {CallExpression} expr call expression
  357. * @param {BasicEvaluatedExpression} param param
  358. * @param {boolean} weak weak
  359. * @returns {boolean | void} true when handled
  360. */
  361. const processResolveContext = (expr, param, weak) => {
  362. const dep = ContextDependencyHelpers.create(
  363. RequireResolveContextDependency,
  364. /** @type {Range} */ (param.range),
  365. param,
  366. expr,
  367. options,
  368. {
  369. category: "commonjs",
  370. mode: weak ? "weak" : "sync"
  371. },
  372. parser,
  373. getContext()
  374. );
  375. if (!dep) return;
  376. dep.loc = parser.getLocation(expr);
  377. dep.optional = Boolean(parser.scope.inTry);
  378. parser.state.current.addDependency(dep);
  379. return true;
  380. };
  381. return (expr, weak) => {
  382. if (!weak && options.commonjsMagicComments) {
  383. const { options: requireOptions, errors: commentErrors } =
  384. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  385. if (commentErrors) {
  386. for (const e of commentErrors) {
  387. const { comment } = e;
  388. parser.state.module.addWarning(
  389. new (getCommentCompilationWarning())(
  390. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  391. parser.getLocation(comment)
  392. )
  393. );
  394. }
  395. }
  396. if (requireOptions && requireOptions.webpackIgnore !== undefined) {
  397. if (typeof requireOptions.webpackIgnore !== "boolean") {
  398. parser.state.module.addWarning(
  399. new (getUnsupportedFeatureWarning())(
  400. `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
  401. parser.getLocation(expr)
  402. )
  403. );
  404. } else if (requireOptions.webpackIgnore) {
  405. // Do not instrument `require()` if `webpackIgnore` is `true`
  406. return true;
  407. }
  408. }
  409. }
  410. if (expr.arguments.length !== 1) return;
  411. const param = parser.evaluateExpression(expr.arguments[0]);
  412. if (param.isConditional()) {
  413. for (const option of /** @type {BasicEvaluatedExpression[]} */ (
  414. param.options
  415. )) {
  416. const result = processResolveItem(expr, option, weak);
  417. if (result === undefined) {
  418. processResolveContext(expr, option, weak);
  419. }
  420. }
  421. const dep = new RequireResolveHeaderDependency(
  422. /** @type {Range} */ (expr.callee.range)
  423. );
  424. dep.loc = parser.getLocation(expr);
  425. parser.state.module.addPresentationalDependency(dep);
  426. return true;
  427. }
  428. const result = processResolveItem(expr, param, weak);
  429. if (result === undefined) {
  430. processResolveContext(expr, param, weak);
  431. }
  432. const dep = new RequireResolveHeaderDependency(
  433. /** @type {Range} */ (expr.callee.range)
  434. );
  435. dep.loc = parser.getLocation(expr);
  436. parser.state.module.addPresentationalDependency(dep);
  437. return true;
  438. };
  439. };
  440. class CommonJsImportsParserPlugin {
  441. /**
  442. * Creates an instance of CommonJsImportsParserPlugin.
  443. * @param {JavascriptParserOptions} options parser options
  444. */
  445. constructor(options) {
  446. /** @type {JavascriptParserOptions} */
  447. this.options = options;
  448. }
  449. /**
  450. * Applies the plugin by registering its hooks on the compiler.
  451. * @param {JavascriptParser} parser the parser
  452. * @returns {void}
  453. */
  454. apply(parser) {
  455. const options = this.options;
  456. parser.hooks.collectDestructuringAssignmentProperties.tap(
  457. PLUGIN_NAME,
  458. (expr) => {
  459. if (isRequireCallExpression(expr)) return true;
  460. // `const NAME = require(…); const { a } = NAME;`
  461. if (
  462. expr.type === "Identifier" &&
  463. parser.getTagData(expr.name, REQUIRE_BINDING_TAG)
  464. ) {
  465. return true;
  466. }
  467. }
  468. );
  469. const getContext = () => {
  470. if (parser.currentTagData) {
  471. const { context } =
  472. /** @type {CommonJsImportSettings} */
  473. (parser.currentTagData);
  474. return context;
  475. }
  476. };
  477. // #region metadata
  478. /**
  479. * Tap require expression.
  480. * @param {string} expression expression
  481. * @param {() => Members} getMembers get members
  482. */
  483. const tapRequireExpression = (expression, getMembers) => {
  484. parser.hooks.typeof
  485. .for(expression)
  486. .tap(
  487. PLUGIN_NAME,
  488. toConstantDependency(parser, JSON.stringify("function"))
  489. );
  490. parser.hooks.evaluateTypeof
  491. .for(expression)
  492. .tap(PLUGIN_NAME, evaluateToString("function"));
  493. parser.hooks.evaluateIdentifier
  494. .for(expression)
  495. .tap(
  496. PLUGIN_NAME,
  497. evaluateToIdentifier(expression, "require", getMembers, true)
  498. );
  499. };
  500. tapRequireExpression("require", () => []);
  501. tapRequireExpression("require.resolve", () => ["resolve"]);
  502. tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
  503. // #endregion
  504. // Weird stuff //
  505. parser.hooks.assign.for("require").tap(PLUGIN_NAME, (expr) => {
  506. // to not leak to global "require", we need to define a local require here.
  507. const dep = new ConstDependency("var require;", 0);
  508. dep.loc = parser.getLocation(expr);
  509. parser.state.module.addPresentationalDependency(dep);
  510. return true;
  511. });
  512. // #region Unsupported
  513. parser.hooks.call
  514. .for("require.main.require")
  515. .tap(
  516. PLUGIN_NAME,
  517. expressionIsUnsupported(
  518. parser,
  519. "require.main.require is not supported by webpack."
  520. )
  521. );
  522. parser.hooks.expression
  523. .for("module.parent.require")
  524. .tap(
  525. PLUGIN_NAME,
  526. expressionIsUnsupported(
  527. parser,
  528. "module.parent.require is not supported by webpack."
  529. )
  530. );
  531. parser.hooks.call
  532. .for("module.parent.require")
  533. .tap(
  534. PLUGIN_NAME,
  535. expressionIsUnsupported(
  536. parser,
  537. "module.parent.require is not supported by webpack."
  538. )
  539. );
  540. // #endregion
  541. // #region Renaming
  542. /**
  543. * Returns true when set undefined.
  544. * @param {Expression} expr expression
  545. * @returns {boolean} true when set undefined
  546. */
  547. const defineUndefined = (expr) => {
  548. // To avoid "not defined" error, replace the value with undefined
  549. const dep = new ConstDependency(
  550. "undefined",
  551. /** @type {Range} */ (expr.range)
  552. );
  553. dep.loc = parser.getLocation(expr);
  554. parser.state.module.addPresentationalDependency(dep);
  555. return false;
  556. };
  557. parser.hooks.canRename.for("require").tap(PLUGIN_NAME, () => true);
  558. parser.hooks.rename.for("require").tap(PLUGIN_NAME, defineUndefined);
  559. // #endregion
  560. // #region Inspection
  561. const requireCache = createRequireCacheDependency(parser);
  562. parser.hooks.expression.for("require.cache").tap(PLUGIN_NAME, requireCache);
  563. // #endregion
  564. // #region Require as expression
  565. /**
  566. * Require as expression handler.
  567. * @param {Expression} expr expression
  568. * @returns {boolean} true when handled
  569. */
  570. const requireAsExpressionHandler = createRequireAsExpressionHandler(
  571. parser,
  572. options,
  573. getContext
  574. );
  575. parser.hooks.expression
  576. .for("require")
  577. .tap(PLUGIN_NAME, requireAsExpressionHandler);
  578. // #endregion
  579. // #region Require
  580. /**
  581. * Creates a require handler.
  582. * @param {boolean} callNew true, when require is called with new
  583. * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler
  584. */
  585. /** @type {WeakMap<CallExpression, RequireBindingData>} */
  586. const requireBindingData = new WeakMap();
  587. const createRequireHandler = createRequireCallHandler(
  588. parser,
  589. options,
  590. getContext,
  591. requireBindingData
  592. );
  593. parser.hooks.call
  594. .for("require")
  595. .tap(PLUGIN_NAME, createRequireHandler(false));
  596. parser.hooks.new
  597. .for("require")
  598. .tap(PLUGIN_NAME, createRequireHandler(true));
  599. parser.hooks.call
  600. .for("module.require")
  601. .tap(PLUGIN_NAME, createRequireHandler(false));
  602. parser.hooks.new
  603. .for("module.require")
  604. .tap(PLUGIN_NAME, createRequireHandler(true));
  605. // #endregion
  606. // #region Require with property access
  607. /**
  608. * Returns true when handled.
  609. * @param {Expression} expr expression
  610. * @param {CalleeMembers} calleeMembers callee members
  611. * @param {CallExpression} callExpr call expression
  612. * @param {Members} members members
  613. * @param {Range[]} memberRanges member ranges
  614. * @returns {boolean | void} true when handled
  615. */
  616. const chainHandler = (
  617. expr,
  618. calleeMembers,
  619. callExpr,
  620. members,
  621. memberRanges
  622. ) => {
  623. if (callExpr.arguments.length !== 1) return;
  624. const param = parser.evaluateExpression(callExpr.arguments[0]);
  625. if (
  626. param.isString() &&
  627. !getLocalModule(parser.state, /** @type {string} */ (param.string))
  628. ) {
  629. const dep = new CommonJsFullRequireDependency(
  630. /** @type {string} */ (param.string),
  631. /** @type {Range} */ (expr.range),
  632. members,
  633. /** @type {Range[]} */ memberRanges
  634. );
  635. dep.asiSafe = !parser.isAsiPosition(
  636. /** @type {Range} */ (expr.range)[0]
  637. );
  638. dep.optional = Boolean(parser.scope.inTry);
  639. dep._canConcatenate = true;
  640. dep.loc = parser.getLocation(expr);
  641. parser.state.current.addDependency(dep);
  642. HarmonyImportGuard.attachDependencyGuards(parser, dep);
  643. return true;
  644. }
  645. };
  646. /**
  647. * Call chain handler.
  648. * @param {CallExpression} expr expression
  649. * @param {CalleeMembers} calleeMembers callee members
  650. * @param {CallExpression} callExpr call expression
  651. * @param {Members} members members
  652. * @param {Range[]} memberRanges member ranges
  653. * @returns {boolean | void} true when handled
  654. */
  655. const callChainHandler = (
  656. expr,
  657. calleeMembers,
  658. callExpr,
  659. members,
  660. memberRanges
  661. ) => {
  662. if (callExpr.arguments.length !== 1) return;
  663. const param = parser.evaluateExpression(callExpr.arguments[0]);
  664. if (
  665. param.isString() &&
  666. !getLocalModule(parser.state, /** @type {string} */ (param.string))
  667. ) {
  668. const dep = new CommonJsFullRequireDependency(
  669. /** @type {string} */ (param.string),
  670. /** @type {Range} */ (expr.callee.range),
  671. members,
  672. /** @type {Range[]} */ memberRanges
  673. );
  674. dep.call = true;
  675. dep.namespaceObjectAsContext = Boolean(
  676. options.strictThisContextOnImports
  677. );
  678. dep.asiSafe = !parser.isAsiPosition(
  679. /** @type {Range} */ (expr.range)[0]
  680. );
  681. dep.optional = Boolean(parser.scope.inTry);
  682. dep._canConcatenate = true;
  683. dep.loc = parser.getLocation(expr.callee);
  684. parser.state.current.addDependency(dep);
  685. HarmonyImportGuard.attachDependencyGuards(parser, dep);
  686. parser.walkExpressions(expr.arguments);
  687. return true;
  688. }
  689. };
  690. parser.hooks.memberChainOfCallMemberChain
  691. .for("require")
  692. .tap(PLUGIN_NAME, chainHandler);
  693. parser.hooks.memberChainOfCallMemberChain
  694. .for("module.require")
  695. .tap(PLUGIN_NAME, chainHandler);
  696. parser.hooks.callMemberChainOfCallMemberChain
  697. .for("require")
  698. .tap(PLUGIN_NAME, callChainHandler);
  699. parser.hooks.callMemberChainOfCallMemberChain
  700. .for("module.require")
  701. .tap(PLUGIN_NAME, callChainHandler);
  702. // #endregion
  703. // #region Require bound to a const variable
  704. // Track `const NAME = require(LITERAL)` so that static member accesses on
  705. // `NAME` (e.g. `NAME.foo`, `NAME.foo()`) are forwarded to the same
  706. // `CommonJsRequireDependency` as referenced exports — enabling tree
  707. // shaking of CommonJS modules that are imported into a named binding
  708. // rather than destructured.
  709. parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator, statement) => {
  710. if (statement.kind !== "const") return;
  711. if (declarator.id.type !== "Identifier") return;
  712. if (!declarator.init || declarator.init.type !== "CallExpression") {
  713. return;
  714. }
  715. const init = declarator.init;
  716. if (
  717. init.callee.type !== "Identifier" ||
  718. init.callee.name !== "require" ||
  719. init.arguments.length !== 1
  720. ) {
  721. return;
  722. }
  723. const arg = init.arguments[0];
  724. if (arg.type !== "Literal" || typeof arg.value !== "string") return;
  725. // Only attach binding state when `require` resolves to the free
  726. // `require` (i.e. it isn't shadowed in the current scope).
  727. const requireInfo = parser.getFreeInfoFromVariable("require");
  728. if (!requireInfo || requireInfo.name !== "require") return;
  729. /** @type {RequireBindingData} */
  730. const binding = {
  731. referencedExports: [],
  732. dep: null
  733. };
  734. requireBindingData.set(init, binding);
  735. parser.tagVariable(declarator.id.name, REQUIRE_BINDING_TAG, binding);
  736. return true;
  737. });
  738. parser.hooks.expression
  739. .for(REQUIRE_BINDING_TAG)
  740. .tap(PLUGIN_NAME, (expr) => {
  741. const binding =
  742. /** @type {RequireBindingData} */
  743. (parser.currentTagData);
  744. if (binding) {
  745. const properties = parser.destructuringAssignmentPropertiesFor(expr);
  746. if (properties && binding.referencedExports) {
  747. // `const { a } = NAME` — only the destructured keys are read.
  748. const referencedExports = binding.referencedExports;
  749. traverseDestructuringAssignmentProperties(properties, (stack) =>
  750. referencedExports.push(stack.map((p) => p.id))
  751. );
  752. } else {
  753. // `NAME` is read as a value (not as the object of a static member
  754. // chain), so we have to assume the whole exports object is used.
  755. binding.referencedExports = null;
  756. if (binding.dep) {
  757. binding.dep.referencedExports = null;
  758. }
  759. }
  760. }
  761. return true;
  762. });
  763. parser.hooks.expressionMemberChain
  764. .for(REQUIRE_BINDING_TAG)
  765. .tap(PLUGIN_NAME, (_expr, members) => {
  766. const binding =
  767. /** @type {RequireBindingData} */
  768. (parser.currentTagData);
  769. if (binding && binding.referencedExports) {
  770. binding.referencedExports.push(members);
  771. }
  772. // Returning truthy suppresses the parser's fallback chain (which
  773. // would otherwise walk `NAME` as a bare expression and trigger our
  774. // `expression` hook above, marking the whole namespace as used).
  775. return true;
  776. });
  777. parser.hooks.callMemberChain
  778. .for(REQUIRE_BINDING_TAG)
  779. .tap(PLUGIN_NAME, (expr, members) => {
  780. const binding =
  781. /** @type {RequireBindingData} */
  782. (parser.currentTagData);
  783. if (binding && binding.referencedExports) {
  784. if (members.length === 0) {
  785. // `NAME(...)` — calling the require result directly; the
  786. // whole exports object is observable.
  787. binding.referencedExports = null;
  788. if (binding.dep) {
  789. binding.dep.referencedExports = null;
  790. }
  791. } else {
  792. binding.referencedExports.push(members);
  793. }
  794. }
  795. if (expr.arguments) parser.walkExpressions(expr.arguments);
  796. return true;
  797. });
  798. // Re-exporting the binding lets the require result escape, so the whole
  799. // exports object is observable. Stage -10 runs before the harmony bail tap.
  800. parser.hooks.exportSpecifier.tap(
  801. { name: PLUGIN_NAME, stage: -10 },
  802. (_statement, id) => {
  803. const binding =
  804. /** @type {RequireBindingData | undefined} */
  805. (parser.getTagData(id, REQUIRE_BINDING_TAG));
  806. if (binding) {
  807. binding.referencedExports = null;
  808. if (binding.dep) {
  809. binding.dep.referencedExports = null;
  810. }
  811. }
  812. }
  813. );
  814. // #endregion
  815. // #region Require.resolve
  816. /**
  817. * Processes the provided expr.
  818. * @param {CallExpression} expr call expression
  819. * @param {boolean} weak weak
  820. * @returns {boolean | void} true when handled
  821. */
  822. const processResolve = createProcessResolveHandler(
  823. parser,
  824. options,
  825. getContext
  826. );
  827. parser.hooks.call
  828. .for("require.resolve")
  829. .tap(PLUGIN_NAME, (expr) => processResolve(expr, false));
  830. parser.hooks.call
  831. .for("require.resolveWeak")
  832. .tap(PLUGIN_NAME, (expr) => processResolve(expr, true));
  833. // #endregion
  834. }
  835. }
  836. CommonJsImportsParserPlugin.createProcessResolveHandler =
  837. createProcessResolveHandler;
  838. CommonJsImportsParserPlugin.createRequireAsExpressionHandler =
  839. createRequireAsExpressionHandler;
  840. CommonJsImportsParserPlugin.createRequireCacheDependency =
  841. createRequireCacheDependency;
  842. CommonJsImportsParserPlugin.createRequireHandler = createRequireCallHandler;
  843. module.exports = CommonJsImportsParserPlugin;