CommonJsDependencyHelpers.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Entrypoint = require("../Entrypoint");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const { propertyAccess } = require("../util/property");
  9. /** @import ChunkGraph from "../ChunkGraph" */
  10. /** @import Module, { RuntimeRequirements } from "../Module" */
  11. /** @import RuntimeTemplate from "../RuntimeTemplate" */
  12. /** @import ModuleGraph from "../ModuleGraph" */
  13. /** @import { RuntimeSpec } from "../util/runtime" */
  14. /** @typedef {"exports" | "module.exports" | "this" | "Object.defineProperty(exports)" | "Object.defineProperty(module.exports)" | "Object.defineProperty(this)"} CommonJSDependencyBaseKeywords */
  15. /**
  16. * The well-known name of the ESM named export that, when present, is unwrapped
  17. * by CommonJS `require()` to match Node.js v23+ `require(esm)` semantics:
  18. * https://nodejs.org/docs/latest/api/modules.html#loading-ecmascript-modules-using-require
  19. */
  20. const ESM_MODULE_EXPORTS_NAME = "module.exports";
  21. /**
  22. * Whether `require()` of `importedModule` would trigger Node.js's
  23. * `require(esm)` `"module.exports"` named-export unwrap. This is the
  24. * usage-independent eligibility check: it only looks at module type and
  25. * whether the export is declared, so it can be safely used from
  26. * `getReferencedExports` before usage info is finalized (otherwise the
  27. * check would be circular — we'd need `"module.exports"` to already be
  28. * marked used in order to ask whether to mark it used).
  29. * @param {Module} importedModule the imported module
  30. * @param {ModuleGraph} moduleGraph the module graph
  31. * @returns {boolean} true if `require()` should unwrap `"module.exports"`
  32. */
  33. const isRequireEsmModuleExportsModule = (importedModule, moduleGraph) => {
  34. if (importedModule.getExportsType(moduleGraph, false) !== "namespace") {
  35. return false;
  36. }
  37. const exportsInfo = moduleGraph.getExportsInfo(importedModule);
  38. const exportInfo = exportsInfo.getReadOnlyExportInfo(ESM_MODULE_EXPORTS_NAME);
  39. return exportInfo.provided === true;
  40. };
  41. /**
  42. * When CommonJS `require()` resolves to an ES module that has a named export
  43. * with the literal string name `"module.exports"`, Node.js returns the value of
  44. * that export instead of the namespace object. Returns the property-access
  45. * expression to apply to the require result for that unwrapping, or `null` if
  46. * the imported module is not eligible (not strictly ESM, or no such export,
  47. * or the export was tree-shaken away).
  48. * @param {Module} importedModule the imported module
  49. * @param {ModuleGraph} moduleGraph the module graph
  50. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  51. * @returns {string | null} property-access expression (e.g. `["module.exports"]`), or `null`
  52. */
  53. const getRequireEsmModuleExportsAccess = (
  54. importedModule,
  55. moduleGraph,
  56. runtime
  57. ) => {
  58. if (!isRequireEsmModuleExportsModule(importedModule, moduleGraph)) {
  59. return null;
  60. }
  61. const exportsInfo = moduleGraph.getExportsInfo(importedModule);
  62. // CJS exports are never inlined
  63. const usedName = exportsInfo.getUsedName([ESM_MODULE_EXPORTS_NAME], runtime);
  64. if (usedName === false) return null;
  65. return propertyAccess(/** @type {readonly string[]} */ (usedName));
  66. };
  67. module.exports.ESM_MODULE_EXPORTS_NAME = ESM_MODULE_EXPORTS_NAME;
  68. module.exports.getRequireEsmModuleExportsAccess =
  69. getRequireEsmModuleExportsAccess;
  70. /**
  71. * Whether the base keyword reads the module's top-level `this`.
  72. * @param {CommonJSDependencyBaseKeywords} depBase commonjs dependency base
  73. * @returns {boolean} true when the base is `this`
  74. */
  75. const isThisBase = (depBase) =>
  76. depBase === "this" || depBase === "Object.defineProperty(this)";
  77. /**
  78. * A classic worker script's top-level `this` is its global scope, not the
  79. * module's exports. True only for a runtime every entry of which is a worker,
  80. * so a module imported elsewhere keeps exports semantics in that runtime.
  81. * @param {Module} module the module
  82. * @param {ChunkGraph} chunkGraph the chunk graph
  83. * @param {RuntimeSpec} runtime the runtime code is generated for
  84. * @param {RuntimeTemplate=} runtimeTemplate the runtime template
  85. * @returns {boolean} true when `this` is the worker's global scope
  86. */
  87. const isWorkerEntryThis = (module, chunkGraph, runtime, runtimeTemplate) => {
  88. // a module worker or worklet is an ES module, where `this` is undefined and
  89. // there is nothing to resolve it to
  90. if (
  91. typeof runtime !== "string" ||
  92. runtimeTemplate === undefined ||
  93. runtimeTemplate.isModule()
  94. ) {
  95. return false;
  96. }
  97. let workerEntry = false;
  98. for (const chunk of chunkGraph.getModuleChunks(module)) {
  99. if (chunk.runtime !== runtime) continue;
  100. for (const group of chunk.groupsIterable) {
  101. if (!(group instanceof Entrypoint)) continue;
  102. const { options } = group;
  103. // an entry may pin a worker to a non-worker entry's runtime, leaving one
  104. // code generation for both roles: keep exports, the only safe answer
  105. if (!options.worker || options.worklet) return false;
  106. workerEntry = true;
  107. }
  108. }
  109. return workerEntry;
  110. };
  111. /**
  112. * Returns type and base.
  113. * @param {CommonJSDependencyBaseKeywords} depBase commonjs dependency base
  114. * @param {Module} module module
  115. * @param {RuntimeRequirements} runtimeRequirements runtime requirements
  116. * @param {boolean=} isWorkerEntry whether `this` is a classic worker's global scope
  117. * @returns {[string, string]} type and base
  118. */
  119. module.exports.handleDependencyBase = (
  120. depBase,
  121. module,
  122. runtimeRequirements,
  123. isWorkerEntry
  124. ) => {
  125. /** @type {string} */
  126. let base;
  127. /** @type {string} */
  128. let type;
  129. switch (depBase) {
  130. case "exports":
  131. runtimeRequirements.add(RuntimeGlobals.exports);
  132. base = module.exportsArgument;
  133. type = "expression";
  134. break;
  135. case "module.exports":
  136. runtimeRequirements.add(RuntimeGlobals.module);
  137. base = `${module.moduleArgument}.exports`;
  138. type = "expression";
  139. break;
  140. case "this":
  141. if (isWorkerEntry) {
  142. runtimeRequirements.add(RuntimeGlobals.global);
  143. base = RuntimeGlobals.global;
  144. } else {
  145. runtimeRequirements.add(RuntimeGlobals.thisAsExports);
  146. base = "this";
  147. }
  148. type = "expression";
  149. break;
  150. case "Object.defineProperty(exports)":
  151. runtimeRequirements.add(RuntimeGlobals.exports);
  152. base = module.exportsArgument;
  153. type = "Object.defineProperty";
  154. break;
  155. case "Object.defineProperty(module.exports)":
  156. runtimeRequirements.add(RuntimeGlobals.module);
  157. base = `${module.moduleArgument}.exports`;
  158. type = "Object.defineProperty";
  159. break;
  160. case "Object.defineProperty(this)":
  161. if (isWorkerEntry) {
  162. runtimeRequirements.add(RuntimeGlobals.global);
  163. base = RuntimeGlobals.global;
  164. } else {
  165. runtimeRequirements.add(RuntimeGlobals.thisAsExports);
  166. base = "this";
  167. }
  168. type = "Object.defineProperty";
  169. break;
  170. default:
  171. throw new Error(`Unsupported base ${depBase}`);
  172. }
  173. return [type, base];
  174. };
  175. module.exports.isRequireEsmModuleExportsModule =
  176. isRequireEsmModuleExportsModule;
  177. module.exports.isThisBase = isThisBase;
  178. module.exports.isWorkerEntryThis = isWorkerEntryThis;