CommonJsExportRequireDependency.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Dependency = require("../Dependency");
  7. const { UsageState } = require("../ExportsInfo");
  8. const Template = require("../Template");
  9. const { equals } = require("../util/ArrayHelpers");
  10. const makeSerializable = require("../util/makeSerializable");
  11. const { propertyAccess } = require("../util/property");
  12. const {
  13. ESM_MODULE_EXPORTS_NAME,
  14. getRequireEsmModuleExportsAccess,
  15. handleDependencyBase,
  16. isRequireEsmModuleExportsModule
  17. } = require("./CommonJsDependencyHelpers");
  18. const ModuleDependency = require("./ModuleDependency");
  19. const processExportInfo = require("./processExportInfo");
  20. /** @import { ReplaceSource } from "webpack-sources" */
  21. /**
  22. * @import {
  23. * ExportsSpec,
  24. * GetConditionFn,
  25. * RawReferencedExports,
  26. * ReferencedExports,
  27. * TRANSITIVE,
  28. * ExportInfoName
  29. * } from "../Dependency"
  30. */
  31. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  32. /** @import ExportsInfo, { ExportInfo } from "../ExportsInfo" */
  33. /** @import Module from "../Module" */
  34. /** @import ModuleGraph from "../ModuleGraph" */
  35. /** @import ModuleGraphConnection from "../ModuleGraphConnection" */
  36. /** @import { Range } from "../javascript/JavascriptParser" */
  37. /** @import { RuntimeSpec } from "../util/runtime" */
  38. /**
  39. * @import {
  40. * CommonJSDependencyBaseKeywords
  41. * } from "./CommonJsDependencyHelpers"
  42. */
  43. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectDeserializerContext */
  44. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectSerializerContext */
  45. const idsSymbol = /** @type {symbol} */ (
  46. Symbol("CommonJsExportRequireDependency.ids")
  47. );
  48. const EMPTY_OBJECT = {};
  49. /** @typedef {Set<string>} Exports */
  50. /** @typedef {Set<string>} Checked */
  51. class CommonJsExportRequireDependency extends ModuleDependency {
  52. /**
  53. * Creates an instance of CommonJsExportRequireDependency.
  54. * @param {Range} range range
  55. * @param {Range | null} valueRange value range
  56. * @param {CommonJSDependencyBaseKeywords} base base
  57. * @param {ExportInfoName[]} names names
  58. * @param {string} request request
  59. * @param {ExportInfoName[]} ids ids
  60. * @param {boolean} resultUsed true, when the result is used
  61. */
  62. constructor(range, valueRange, base, names, request, ids, resultUsed) {
  63. super(request);
  64. this.range = range;
  65. this.valueRange = valueRange;
  66. /** @type {CommonJSDependencyBaseKeywords} */
  67. this.base = base;
  68. /** @type {string[]} */
  69. this.names = names;
  70. /** @type {string[]} */
  71. this.ids = ids;
  72. /** @type {boolean} */
  73. this.resultUsed = resultUsed;
  74. /** @type {undefined | boolean} */
  75. this.asiSafe = undefined;
  76. // true when the reexport is a lazy `{ get: () => require(...) }` accessor
  77. // (as produced by `Object.defineProperty`) rather than an eager value.
  78. /** @type {boolean} */
  79. this.getter = false;
  80. }
  81. get type() {
  82. return "cjs export require";
  83. }
  84. get category() {
  85. return "commonjs";
  86. }
  87. /**
  88. * Could affect referencing module.
  89. * @returns {boolean | TRANSITIVE} true, when changes to the referenced module could affect the referencing module; TRANSITIVE, when changes to the referenced module could affect referencing modules of the referencing module
  90. */
  91. couldAffectReferencingModule() {
  92. return Dependency.TRANSITIVE;
  93. }
  94. /**
  95. * Returns true if this dependency can be concatenated
  96. * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
  97. * @returns {boolean} true if this dependency can be concatenated
  98. */
  99. canConcatenate(concatenateCommonJsModules) {
  100. return concatenateCommonJsModules;
  101. }
  102. /**
  103. * Returns function to determine if the connection is active.
  104. * @param {ModuleGraph} moduleGraph module graph
  105. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  106. */
  107. getCondition(moduleGraph) {
  108. // Conservative: keep `module.exports = require(...)` (names empty) active even
  109. // when the parent is evaluation-only and the target is side-effect-free.
  110. if (this.resultUsed || this.names.length === 0) return null;
  111. const names = this.names;
  112. const getter = this.getter;
  113. return (connection, runtime) => {
  114. const parentModule = moduleGraph.getParentModule(this);
  115. if (!parentModule) return true;
  116. const used = moduleGraph
  117. .getExportsInfo(parentModule)
  118. .getUsedName(names, runtime);
  119. if (used !== false) return true;
  120. if (getter) return false;
  121. const refModule = connection.resolvedModule;
  122. if (!refModule) return true;
  123. return refModule.getSideEffectsConnectionState(moduleGraph);
  124. };
  125. }
  126. /**
  127. * Returns the imported id.
  128. * @param {ModuleGraph} moduleGraph the module graph
  129. * @returns {ExportInfoName[]} the imported id
  130. */
  131. getIds(moduleGraph) {
  132. return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
  133. }
  134. /**
  135. * Updates ids using the provided module graph.
  136. * @param {ModuleGraph} moduleGraph the module graph
  137. * @param {ExportInfoName[]} ids the imported ids
  138. * @returns {void}
  139. */
  140. setIds(moduleGraph, ids) {
  141. moduleGraph.getMeta(this)[idsSymbol] = ids;
  142. }
  143. /**
  144. * Returns list of exports referenced by this dependency
  145. * @param {ModuleGraph} moduleGraph module graph
  146. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  147. * @returns {ReferencedExports} referenced exports
  148. */
  149. getReferencedExports(moduleGraph, runtime) {
  150. const ids = this.getIds(moduleGraph);
  151. const importedModule = moduleGraph.getModule(this);
  152. if (
  153. importedModule &&
  154. isRequireEsmModuleExportsModule(importedModule, moduleGraph)
  155. ) {
  156. // `require(esm)` unwraps the "module.exports" named export; any
  157. // further property access lands on that value (which webpack does
  158. // not model), so only the "module.exports" export is observable.
  159. return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }];
  160. }
  161. const getFullResult = () => {
  162. if (ids.length === 0) {
  163. return Dependency.EXPORTS_OBJECT_REFERENCED;
  164. }
  165. return [
  166. {
  167. name: ids,
  168. canMangle: false,
  169. canInline: false
  170. }
  171. ];
  172. };
  173. if (this.resultUsed) return getFullResult();
  174. /** @type {ExportsInfo | undefined} */
  175. let exportsInfo = moduleGraph.getExportsInfo(
  176. /** @type {Module} */ (moduleGraph.getParentModule(this))
  177. );
  178. for (const name of this.names) {
  179. const exportInfo =
  180. /** @type {InstanceType<ExportInfo>} */
  181. (exportsInfo.getReadOnlyExportInfo(name));
  182. const used = exportInfo.getUsed(runtime);
  183. if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED;
  184. if (used !== UsageState.OnlyPropertiesUsed) return getFullResult();
  185. exportsInfo = exportInfo.exportsInfo;
  186. if (!exportsInfo) return getFullResult();
  187. }
  188. if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
  189. return getFullResult();
  190. }
  191. /** @type {RawReferencedExports} */
  192. const referencedExports = [];
  193. for (const exportInfo of exportsInfo.orderedExports) {
  194. processExportInfo(
  195. runtime,
  196. referencedExports,
  197. [...ids, exportInfo.name],
  198. exportInfo,
  199. false
  200. );
  201. }
  202. return referencedExports.map((name) => ({
  203. name,
  204. canMangle: false,
  205. canInline: false
  206. }));
  207. }
  208. /**
  209. * Returns the exported names
  210. * @param {ModuleGraph} moduleGraph module graph
  211. * @returns {ExportsSpec | undefined} export names
  212. */
  213. getExports(moduleGraph) {
  214. const importedModule = moduleGraph.getModule(this);
  215. const esmUnwrap =
  216. importedModule &&
  217. isRequireEsmModuleExportsModule(importedModule, moduleGraph);
  218. if (this.names.length === 1) {
  219. const ids = this.getIds(moduleGraph);
  220. const name = this.names[0];
  221. const from = moduleGraph.getConnection(this);
  222. if (!from) return;
  223. const exportChain = esmUnwrap
  224. ? [ESM_MODULE_EXPORTS_NAME, ...ids]
  225. : ids.length === 0
  226. ? null
  227. : ids;
  228. return {
  229. exports: [
  230. {
  231. name,
  232. from,
  233. export: exportChain,
  234. // we can't mangle names that are in an empty object
  235. // because one could access the prototype property
  236. // when export isn't set yet
  237. canMangle: !(name in EMPTY_OBJECT) && false
  238. }
  239. ],
  240. dependencies: [from.module]
  241. };
  242. } else if (this.names.length > 0) {
  243. const name = this.names[0];
  244. return {
  245. exports: [
  246. {
  247. name,
  248. // we can't mangle names that are in an empty object
  249. // because one could access the prototype property
  250. // when export isn't set yet
  251. canMangle: !(name in EMPTY_OBJECT) && false
  252. }
  253. ],
  254. dependencies: undefined
  255. };
  256. }
  257. const from = moduleGraph.getConnection(this);
  258. if (!from) return;
  259. if (esmUnwrap) {
  260. // Full re-export `module.exports = require("./esm")` of a module
  261. // with a `"module.exports"` named export: the wrapping module's
  262. // `module.exports` becomes the unwrapped value, whose own
  263. // properties webpack cannot enumerate statically.
  264. return {
  265. exports: true,
  266. canMangle: false,
  267. dependencies: [from.module]
  268. };
  269. }
  270. // The imported namespace ESM hasn't been flagged by
  271. // FlagDependencyExportsPlugin yet (no exports determined), so its
  272. // `"module.exports"` unwrap eligibility is still unknown. Star-reexporting
  273. // now would add `__esModule` (and names) the monotonic merge can't retract
  274. // once the module turns out to unwrap, making the result order-dependent
  275. // across runtimes; defer — the `from.module` dependency re-queues us once
  276. // its exports (owned names, or dynamic `other`) become known.
  277. if (
  278. importedModule &&
  279. importedModule.getExportsType(moduleGraph, false) === "namespace"
  280. ) {
  281. const importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
  282. if (
  283. importedExportsInfo.otherExportsInfo.provided === false &&
  284. importedExportsInfo.ownedExports[Symbol.iterator]().next().done
  285. ) {
  286. return { exports: [], dependencies: [from.module] };
  287. }
  288. }
  289. const reexportInfo = this.getStarReexports(
  290. moduleGraph,
  291. undefined,
  292. from.module
  293. );
  294. const ids = this.getIds(moduleGraph);
  295. if (reexportInfo) {
  296. return {
  297. exports: Array.from(
  298. /** @type {Exports} */
  299. (reexportInfo.exports),
  300. (name) => ({
  301. name,
  302. from,
  303. export: [...ids, name],
  304. canMangle: !(name in EMPTY_OBJECT) && false
  305. })
  306. ),
  307. dependencies: [from.module]
  308. };
  309. }
  310. return {
  311. exports: true,
  312. from: ids.length === 0 ? from : undefined,
  313. canMangle: false,
  314. dependencies: [from.module]
  315. };
  316. }
  317. /**
  318. * Gets star reexports.
  319. * @param {ModuleGraph} moduleGraph the module graph
  320. * @param {RuntimeSpec} runtime the runtime
  321. * @param {Module} importedModule the imported module (optional)
  322. * @returns {{ exports?: Exports, checked?: Checked } | undefined} information
  323. */
  324. getStarReexports(
  325. moduleGraph,
  326. runtime,
  327. importedModule = /** @type {Module} */ (moduleGraph.getModule(this))
  328. ) {
  329. /** @type {ExportsInfo | undefined} */
  330. let importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
  331. const ids = this.getIds(moduleGraph);
  332. if (ids.length > 0) {
  333. importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids);
  334. }
  335. /** @type {ExportsInfo | undefined} */
  336. let exportsInfo = moduleGraph.getExportsInfo(
  337. /** @type {Module} */ (moduleGraph.getParentModule(this))
  338. );
  339. if (this.names.length > 0) {
  340. exportsInfo = exportsInfo.getNestedExportsInfo(this.names);
  341. }
  342. const noExtraExports =
  343. importedExportsInfo &&
  344. importedExportsInfo.otherExportsInfo.provided === false;
  345. const noExtraImports =
  346. exportsInfo &&
  347. exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused;
  348. if (!noExtraExports && !noExtraImports) {
  349. return;
  350. }
  351. const isNamespaceImport =
  352. importedModule.getExportsType(moduleGraph, false) === "namespace";
  353. /** @type {Exports} */
  354. const exports = new Set();
  355. /** @type {Checked} */
  356. const checked = new Set();
  357. if (noExtraImports) {
  358. for (const exportInfo of /** @type {ExportsInfo} */ (exportsInfo)
  359. .orderedExports) {
  360. const name = exportInfo.name;
  361. if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
  362. if (name === "__esModule" && isNamespaceImport) {
  363. exports.add(name);
  364. } else if (importedExportsInfo) {
  365. const importedExportInfo =
  366. importedExportsInfo.getReadOnlyExportInfo(name);
  367. if (importedExportInfo.provided === false) continue;
  368. exports.add(name);
  369. if (importedExportInfo.provided === true) continue;
  370. checked.add(name);
  371. } else {
  372. exports.add(name);
  373. checked.add(name);
  374. }
  375. }
  376. } else if (noExtraExports) {
  377. for (const importedExportInfo of /** @type {ExportsInfo} */ (
  378. importedExportsInfo
  379. ).orderedExports) {
  380. const name = importedExportInfo.name;
  381. if (importedExportInfo.provided === false) continue;
  382. if (exportsInfo) {
  383. const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
  384. if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
  385. }
  386. exports.add(name);
  387. if (importedExportInfo.provided === true) continue;
  388. checked.add(name);
  389. }
  390. if (isNamespaceImport) {
  391. exports.add("__esModule");
  392. checked.delete("__esModule");
  393. }
  394. }
  395. return { exports, checked };
  396. }
  397. /**
  398. * Serializes this instance into the provided serializer context.
  399. * @param {ObjectSerializerContext} context context
  400. */
  401. serialize(context) {
  402. context
  403. .write(this.asiSafe)
  404. .write(this.range)
  405. .write(this.valueRange)
  406. .write(this.base)
  407. .write(this.names)
  408. .write(this.ids)
  409. .write(this.resultUsed)
  410. .write(this.getter);
  411. super.serialize(context);
  412. }
  413. /**
  414. * Restores this instance from the provided deserializer context.
  415. * @param {ObjectDeserializerContext} context context
  416. */
  417. deserialize(context) {
  418. this.asiSafe = context.read();
  419. const c1 = context.rest;
  420. this.range = c1.read();
  421. const c2 = c1.rest;
  422. this.valueRange = c2.read();
  423. const c3 = c2.rest;
  424. this.base = c3.read();
  425. const c4 = c3.rest;
  426. this.names = c4.read();
  427. const c5 = c4.rest;
  428. this.ids = c5.read();
  429. const c6 = c5.rest;
  430. this.resultUsed = c6.read();
  431. const c7 = c6.rest;
  432. this.getter = c7.read();
  433. super.deserialize(c7.rest);
  434. }
  435. }
  436. makeSerializable(
  437. CommonJsExportRequireDependency,
  438. "webpack/lib/dependencies/CommonJsExportRequireDependency"
  439. );
  440. CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends (
  441. ModuleDependency.Template
  442. ) {
  443. /**
  444. * Applies the plugin by registering its hooks on the compiler.
  445. * @param {Dependency} dependency the dependency for which the template should be applied
  446. * @param {ReplaceSource} source the current replace source which can be modified
  447. * @param {DependencyTemplateContext} templateContext the context object
  448. * @returns {void}
  449. */
  450. apply(
  451. dependency,
  452. source,
  453. {
  454. module,
  455. runtimeTemplate,
  456. chunkGraph,
  457. moduleGraph,
  458. runtimeRequirements,
  459. runtime,
  460. concatenationScope
  461. }
  462. ) {
  463. const dep = /** @type {CommonJsExportRequireDependency} */ (dependency);
  464. // CJS exports are never inlined
  465. const used = /** @type {string | string[] | false} */ (
  466. moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime)
  467. );
  468. const connection = /** @type {ModuleGraphConnection | undefined} */ (
  469. moduleGraph.getConnection(dep)
  470. );
  471. // Inactive unused reexport: no module id; drop to a no-op.
  472. // Missing connection (e.g. IgnorePlugin) must keep the require so it throws.
  473. if (!used && connection && !connection.isTargetActive(runtime)) {
  474. source.replace(dep.range[0], dep.range[1] - 1, "/* unused reexport */ 0");
  475. return;
  476. }
  477. const [type, base] = handleDependencyBase(
  478. dep.base,
  479. module,
  480. runtimeRequirements
  481. );
  482. const importedModule = moduleGraph.getModule(dep);
  483. /** @type {string} */
  484. let requireExpr;
  485. if (
  486. concatenationScope &&
  487. importedModule &&
  488. concatenationScope.isModuleInScope(importedModule)
  489. ) {
  490. // The alias reads the target's exports value, so it binds like `require()`
  491. // rather than an ESM import: no interop.
  492. const ids = dep.getIds(moduleGraph);
  493. const requestedIds = isRequireEsmModuleExportsModule(
  494. importedModule,
  495. moduleGraph
  496. )
  497. ? [ESM_MODULE_EXPORTS_NAME, ...ids]
  498. : ids;
  499. requireExpr = concatenationScope.createModuleReference(importedModule, {
  500. ids: requestedIds.length > 0 ? requestedIds : undefined,
  501. asiSafe: true,
  502. moduleExportsAccess: true
  503. });
  504. } else {
  505. requireExpr = runtimeTemplate.moduleExports({
  506. module: importedModule,
  507. chunkGraph,
  508. request: dep.request,
  509. weak: dep.weak,
  510. runtimeRequirements
  511. });
  512. if (importedModule) {
  513. const ids = dep.getIds(moduleGraph);
  514. const esmRequireAccess = getRequireEsmModuleExportsAccess(
  515. importedModule,
  516. moduleGraph,
  517. runtime
  518. );
  519. if (esmRequireAccess !== null) {
  520. requireExpr += `${esmRequireAccess}${propertyAccess(ids)}`;
  521. } else {
  522. // CJS exports are never inlined
  523. const usedImported = /** @type {string | string[] | false} */ (
  524. moduleGraph.getExportsInfo(importedModule).getUsedName(ids, runtime)
  525. );
  526. if (usedImported) {
  527. const comment = equals(usedImported, ids)
  528. ? ""
  529. : `${Template.toNormalComment(propertyAccess(ids))} `;
  530. requireExpr += `${comment}${propertyAccess(/** @type {string[]} */ (usedImported))}`;
  531. }
  532. }
  533. }
  534. }
  535. switch (type) {
  536. case "expression":
  537. source.replace(
  538. dep.range[0],
  539. dep.range[1] - 1,
  540. used
  541. ? `${base}${propertyAccess(/** @type {string[]} */ (used))} = ${requireExpr}`
  542. : `/* unused reexport */ ${requireExpr}`
  543. );
  544. return;
  545. case "Object.defineProperty": {
  546. // `Object.defineProperty(exports, "name", { value: require("...") })`
  547. // or the lazy getter form `{ get: () => require("...") }` used by
  548. // barrel files (e.g. webpack's own `lib/index.js`).
  549. const valueRange = /** @type {Range} */ (dep.valueRange);
  550. if (!used) {
  551. // Active unused eager reexport: keep side effects. Unused getters
  552. // are inactive and already replaced with `0` above.
  553. source.replace(
  554. dep.range[0],
  555. dep.range[1] - 1,
  556. `/* unused reexport */ ${requireExpr}`
  557. );
  558. return;
  559. }
  560. const descriptor = dep.getter
  561. ? "enumerable: true, get: () => ("
  562. : "value: (";
  563. source.replace(
  564. dep.range[0],
  565. valueRange[0] - 1,
  566. `Object.defineProperty(${base}${propertyAccess(
  567. /** @type {string[]} */ (used).slice(0, -1)
  568. )}, ${JSON.stringify(used[used.length - 1])}, { ${descriptor}`
  569. );
  570. source.replace(valueRange[0], valueRange[1] - 1, requireExpr);
  571. source.replace(valueRange[1], dep.range[1] - 1, ") })");
  572. return;
  573. }
  574. default:
  575. throw new Error("Unexpected type");
  576. }
  577. }
  578. };
  579. CommonJsExportRequireDependency.idsSymbol = idsSymbol;
  580. module.exports = CommonJsExportRequireDependency;