CommonJsRequireDependency.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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 RuntimeGlobals = require("../RuntimeGlobals");
  8. const {
  9. getDependencyUsedByExportsCondition
  10. } = require("../optimize/InnerGraph");
  11. const makeSerializable = require("../util/makeSerializable");
  12. const {
  13. ESM_MODULE_EXPORTS_NAME,
  14. getRequireEsmModuleExportsAccess,
  15. isRequireEsmModuleExportsModule
  16. } = require("./CommonJsDependencyHelpers");
  17. const HarmonyImportGuard = require("./HarmonyImportGuard");
  18. const ModuleDependency = require("./ModuleDependency");
  19. /** @import { ReplaceSource } from "webpack-sources" */
  20. /**
  21. * @import {
  22. * GetConditionFn,
  23. * RawReferencedExports,
  24. * ReferencedExports
  25. * } from "../Dependency"
  26. */
  27. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  28. /** @import Module from "../Module" */
  29. /** @import ModuleGraph from "../ModuleGraph" */
  30. /** @import { Range } from "../javascript/JavascriptParser" */
  31. /** @import { UsedByExports } from "../optimize/InnerGraph" */
  32. /** @import { RuntimeSpec } from "../util/runtime" */
  33. /** @import { DependencyGuard } from "./HarmonyImportGuard" */
  34. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[RawReferencedExports | null, Range | undefined, DependencyGuard[] | undefined, boolean, boolean, UsedByExports | undefined]>} ObjectDeserializerContext */
  35. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[RawReferencedExports | null, Range | undefined, DependencyGuard[] | undefined, boolean, boolean, UsedByExports | undefined]>} ObjectSerializerContext */
  36. /**
  37. * Trims a member-access path at the first segment that is definitively not a
  38. * provided export of the module. Such a segment is a runtime property (e.g. an
  39. * array/object prototype method like `arr.includes`), so only the value up to
  40. * that point is observed; referencing the whole value keeps its data intact
  41. * instead of tree-shaking it away. Real named exports (`utils.foo`) are kept.
  42. * @param {import("../ExportsInfo")} exportsInfo module exports info
  43. * @param {string[]} name member-access path
  44. * @returns {string[]} the provided prefix of the path
  45. */
  46. const trimToProvidedPrefix = (exportsInfo, name) => {
  47. let current = exportsInfo;
  48. for (let i = 0; i < name.length; i++) {
  49. const exportInfo = current.getReadOnlyExportInfo(name[i]);
  50. if (exportInfo.provided === false) return name.slice(0, i);
  51. const nested = exportInfo.exportsInfo;
  52. if (!nested) return name;
  53. current = nested;
  54. }
  55. return name;
  56. };
  57. class CommonJsRequireDependency extends ModuleDependency {
  58. /**
  59. * Creates an instance of CommonJsRequireDependency.
  60. * @param {string} request request
  61. * @param {Range=} range location in source code of the string-literal argument (gets replaced by the module id)
  62. * @param {string=} context request context
  63. * @param {RawReferencedExports | null=} referencedExports list of referenced exports
  64. * @param {Range=} valueRange location in source code of the whole `require(...)` call (for `require(esm)` interop)
  65. */
  66. constructor(
  67. request,
  68. range,
  69. context,
  70. referencedExports = null,
  71. valueRange = undefined
  72. ) {
  73. super(request);
  74. this.range = range;
  75. /** @type {string | undefined} */
  76. this._context = context;
  77. /** @type {RawReferencedExports | null} */
  78. this.referencedExports = referencedExports;
  79. this.valueRange = valueRange;
  80. /** @type {DependencyGuard[] | undefined} */
  81. this.branchGuards = undefined;
  82. /** @type {UsedByExports | undefined} */
  83. this.usedByExports = undefined;
  84. /** @type {boolean} */
  85. this._canConcatenate = false;
  86. // `new require(...)`; see the template for the return-value rule
  87. /** @type {boolean} */
  88. this.callNew = false;
  89. }
  90. get type() {
  91. return "cjs require";
  92. }
  93. get category() {
  94. return "commonjs";
  95. }
  96. /**
  97. * Whether this require only evaluates the module (no exports read).
  98. * @returns {boolean} true when referencedExports is an empty list
  99. */
  100. isEvaluationOnly() {
  101. return (
  102. Array.isArray(this.referencedExports) &&
  103. this.referencedExports.length === 0
  104. );
  105. }
  106. /**
  107. * Returns function to determine if the connection is active.
  108. * @param {ModuleGraph} moduleGraph module graph
  109. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  110. */
  111. getCondition(moduleGraph) {
  112. const usedByExportsCondition =
  113. this.usedByExports !== undefined
  114. ? getDependencyUsedByExportsCondition(this, moduleGraph)
  115. : null;
  116. if (usedByExportsCondition === false) return false;
  117. const guards = this.branchGuards;
  118. const evaluationOnly = this.isEvaluationOnly();
  119. if (
  120. usedByExportsCondition === null &&
  121. guards === undefined &&
  122. !evaluationOnly
  123. ) {
  124. return null;
  125. }
  126. return (connection, runtime) => {
  127. if (
  128. usedByExportsCondition !== null &&
  129. usedByExportsCondition(connection, runtime) === false
  130. ) {
  131. return false;
  132. }
  133. if (
  134. guards !== undefined &&
  135. HarmonyImportGuard.isDeadByGuards(guards, moduleGraph, runtime)
  136. ) {
  137. return false;
  138. }
  139. if (evaluationOnly) {
  140. const refModule = connection.resolvedModule;
  141. if (!refModule) return true;
  142. return refModule.getSideEffectsConnectionState(moduleGraph);
  143. }
  144. return true;
  145. };
  146. }
  147. /**
  148. * Returns list of exports referenced by this dependency
  149. * @param {ModuleGraph} moduleGraph module graph
  150. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  151. * @returns {ReferencedExports} referenced exports
  152. */
  153. getReferencedExports(moduleGraph, runtime) {
  154. const importedModule = moduleGraph.getModule(this);
  155. if (this.isEvaluationOnly()) {
  156. // Active `require(esm)` with side effects still unwraps `module.exports`.
  157. if (
  158. importedModule &&
  159. isRequireEsmModuleExportsModule(importedModule, moduleGraph)
  160. ) {
  161. return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }];
  162. }
  163. return Dependency.NO_EXPORTS_REFERENCED;
  164. }
  165. if (
  166. importedModule &&
  167. isRequireEsmModuleExportsModule(importedModule, moduleGraph)
  168. ) {
  169. // `require(esm)` will unwrap the "module.exports" named export; only
  170. // that export is observable through this `require()` call.
  171. return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }];
  172. }
  173. if (!this.referencedExports) return Dependency.EXPORTS_OBJECT_REFERENCED;
  174. const exportsInfo = importedModule
  175. ? moduleGraph.getExportsInfo(importedModule)
  176. : undefined;
  177. return this.referencedExports.map((name) => ({
  178. name: exportsInfo ? trimToProvidedPrefix(exportsInfo, name) : name,
  179. canMangle: false,
  180. canInline: false
  181. }));
  182. }
  183. /**
  184. * Returns true if this dependency can be concatenated
  185. * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
  186. * @returns {boolean} true if this dependency can be concatenated
  187. */
  188. canConcatenate(concatenateCommonJsModules) {
  189. return concatenateCommonJsModules && this._canConcatenate;
  190. }
  191. /**
  192. * Serializes this instance into the provided serializer context.
  193. * @param {ObjectSerializerContext} context context
  194. */
  195. serialize(context) {
  196. context
  197. .write(this.referencedExports)
  198. .write(this.valueRange)
  199. .write(this.branchGuards)
  200. .write(this._canConcatenate)
  201. .write(this.callNew)
  202. .write(this.usedByExports);
  203. super.serialize(context);
  204. }
  205. /**
  206. * Restores this instance from the provided deserializer context.
  207. * @param {ObjectDeserializerContext} context context
  208. */
  209. deserialize(context) {
  210. this.referencedExports = context.read();
  211. const c1 = context.rest;
  212. this.valueRange = c1.read();
  213. const c2 = c1.rest;
  214. this.branchGuards = c2.read();
  215. const c3 = c2.rest;
  216. this._canConcatenate = c3.read();
  217. const c4 = c3.rest;
  218. this.callNew = c4.read();
  219. const c5 = c4.rest;
  220. this.usedByExports = c5.read();
  221. super.deserialize(c5.rest);
  222. }
  223. }
  224. CommonJsRequireDependency.Template = class CommonJsRequireDependencyTemplate extends (
  225. ModuleDependency.Template
  226. ) {
  227. /**
  228. * Applies the plugin by registering its hooks on the compiler.
  229. * @param {Dependency} dependency the dependency for which the template should be applied
  230. * @param {ReplaceSource} source the current replace source which can be modified
  231. * @param {DependencyTemplateContext} templateContext the context object
  232. * @returns {void}
  233. */
  234. apply(dependency, source, templateContext) {
  235. const {
  236. runtimeTemplate,
  237. moduleGraph,
  238. chunkGraph,
  239. runtime,
  240. concatenationScope
  241. } = templateContext;
  242. const dep = /** @type {CommonJsRequireDependency} */ (dependency);
  243. if (!dep.range) return;
  244. const connection = moduleGraph.getConnection(dep);
  245. if (connection && !connection.isTargetActive(runtime)) {
  246. const guards = dep.branchGuards;
  247. if (
  248. guards !== undefined &&
  249. HarmonyImportGuard.isDeadByGuards(guards, moduleGraph, runtime)
  250. ) {
  251. // Dead branch: module is excluded and has no id; code is never executed.
  252. source.replace(
  253. dep.range[0],
  254. dep.range[1] - 1,
  255. "null /* dead branch */"
  256. );
  257. return;
  258. }
  259. // Unused side-effect-free require: drop the whole call expression.
  260. if (!dep.valueRange) return;
  261. source.replace(dep.valueRange[0], dep.valueRange[1] - 1, "0");
  262. return;
  263. }
  264. if (
  265. connection &&
  266. concatenationScope !== undefined &&
  267. dep.valueRange !== undefined &&
  268. concatenationScope.isModuleInScope(connection.module)
  269. ) {
  270. let content = concatenationScope.createModuleReference(
  271. connection.module,
  272. {
  273. // `require(esm)` unwraps the "module.exports" export
  274. ids: isRequireEsmModuleExportsModule(connection.module, moduleGraph)
  275. ? [ESM_MODULE_EXPORTS_NAME]
  276. : undefined,
  277. asiSafe: true,
  278. moduleExportsAccess: true
  279. }
  280. );
  281. // replacing the call drops the `new`, so apply the `new` return-value rule
  282. // here: object or function passes through, anything else gets a fresh instance
  283. if (dep.callNew) {
  284. templateContext.runtimeRequirements.add(
  285. RuntimeGlobals.constructRequire
  286. );
  287. content = `${RuntimeGlobals.constructRequire}(${content})`;
  288. }
  289. source.replace(dep.valueRange[0], dep.valueRange[1] - 1, content);
  290. concatenationScope.registerReplacedRequire(dep.valueRange);
  291. return;
  292. }
  293. const importedModule = /** @type {Module} */ (moduleGraph.getModule(dep));
  294. const content = runtimeTemplate.moduleId({
  295. module: importedModule,
  296. chunkGraph,
  297. request: dep.request,
  298. weak: dep.weak
  299. });
  300. source.replace(dep.range[0], dep.range[1] - 1, content);
  301. if (dep.valueRange && importedModule) {
  302. const access = getRequireEsmModuleExportsAccess(
  303. importedModule,
  304. moduleGraph,
  305. runtime
  306. );
  307. if (access !== null) {
  308. source.insert(dep.valueRange[1], access);
  309. }
  310. }
  311. }
  312. };
  313. makeSerializable(
  314. CommonJsRequireDependency,
  315. "webpack/lib/dependencies/CommonJsRequireDependency"
  316. );
  317. module.exports = CommonJsRequireDependency;