CommonJsFullRequireDependency.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Template = require("../Template");
  7. const {
  8. getDependencyUsedByExportsCondition
  9. } = require("../optimize/InnerGraph");
  10. const { equals } = require("../util/ArrayHelpers");
  11. const { getTrimmedIdsAndRange } = require("../util/chainedImports");
  12. const makeSerializable = require("../util/makeSerializable");
  13. const { propertyAccess } = require("../util/property");
  14. const {
  15. ESM_MODULE_EXPORTS_NAME,
  16. getRequireEsmModuleExportsAccess,
  17. isRequireEsmModuleExportsModule
  18. } = require("./CommonJsDependencyHelpers");
  19. const HarmonyImportGuard = require("./HarmonyImportGuard");
  20. const ModuleDependency = require("./ModuleDependency");
  21. /** @import { ReplaceSource } from "webpack-sources" */
  22. /**
  23. * @import Dependency, {
  24. * GetConditionFn,
  25. * ReferencedExports,
  26. * ExportInfoName
  27. * } from "../Dependency"
  28. */
  29. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  30. /** @import ModuleGraph from "../ModuleGraph" */
  31. /** @import { Range } from "../javascript/JavascriptParser" */
  32. /** @import { UsedByExports } from "../optimize/InnerGraph" */
  33. /** @import { RuntimeSpec } from "../util/runtime" */
  34. /** @import { IdRanges } from "../util/chainedImports" */
  35. /** @import { DependencyGuard } from "./HarmonyImportGuard" */
  36. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean, DependencyGuard[] | undefined, boolean, boolean, UsedByExports | undefined]>} ObjectDeserializerContext */
  37. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean, DependencyGuard[] | undefined, boolean, boolean, UsedByExports | undefined]>} ObjectSerializerContext */
  38. class CommonJsFullRequireDependency extends ModuleDependency {
  39. /**
  40. * Creates an instance of CommonJsFullRequireDependency.
  41. * @param {string} request the request string
  42. * @param {Range} range location in source code
  43. * @param {ExportInfoName[]} names accessed properties on module
  44. * @param {IdRanges=} idRanges ranges for members of ids; the two arrays are right-aligned
  45. */
  46. constructor(
  47. request,
  48. range,
  49. names,
  50. idRanges /* TODO webpack 6 make this non-optional. It must always be set to properly trim ids. */
  51. ) {
  52. super(request);
  53. this.range = range;
  54. /** @type {string[]} */
  55. this.names = names;
  56. /** @type {IdRanges | undefined} */
  57. this.idRanges = idRanges;
  58. /** @type {boolean} */
  59. this.call = false;
  60. /** @type {boolean} */
  61. this.namespaceObjectAsContext = false;
  62. /** @type {undefined | boolean} */
  63. this.asiSafe = undefined;
  64. /** @type {DependencyGuard[] | undefined} */
  65. this.branchGuards = undefined;
  66. /** @type {UsedByExports | undefined} */
  67. this.usedByExports = undefined;
  68. /** @type {boolean} */
  69. this._canConcatenate = false;
  70. }
  71. /**
  72. * Returns function to determine if the connection is active.
  73. * @param {ModuleGraph} moduleGraph module graph
  74. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  75. */
  76. getCondition(moduleGraph) {
  77. const usedByExportsCondition =
  78. this.usedByExports !== undefined
  79. ? getDependencyUsedByExportsCondition(this, moduleGraph)
  80. : null;
  81. if (usedByExportsCondition === false) return false;
  82. const guards = this.branchGuards;
  83. if (usedByExportsCondition === null && guards === undefined) return null;
  84. return (connection, runtime) => {
  85. if (
  86. usedByExportsCondition !== null &&
  87. usedByExportsCondition(connection, runtime) === false
  88. ) {
  89. return false;
  90. }
  91. return !(
  92. guards !== undefined &&
  93. HarmonyImportGuard.isDeadByGuards(guards, moduleGraph, runtime)
  94. );
  95. };
  96. }
  97. /**
  98. * Returns true if this dependency can be concatenated
  99. * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
  100. * @returns {boolean} true if this dependency can be concatenated
  101. */
  102. canConcatenate(concatenateCommonJsModules) {
  103. return concatenateCommonJsModules && this._canConcatenate;
  104. }
  105. /**
  106. * Returns list of exports referenced by this dependency
  107. * @param {ModuleGraph} moduleGraph module graph
  108. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  109. * @returns {ReferencedExports} referenced exports
  110. */
  111. getReferencedExports(moduleGraph, runtime) {
  112. const importedModule = moduleGraph.getModule(this);
  113. // CommonJS property access is never rewritten to a literal, so it can't inline
  114. if (
  115. importedModule &&
  116. isRequireEsmModuleExportsModule(importedModule, moduleGraph)
  117. ) {
  118. // When `require(esm)` unwraps a `"module.exports"` named export, the
  119. // user's property access lands on that value (which webpack does not
  120. // model), so only the "module.exports" export itself is referenced.
  121. return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }];
  122. }
  123. if (
  124. this.call &&
  125. (!importedModule ||
  126. // the namespace object of a required ESM module is the call receiver
  127. this.namespaceObjectAsContext ||
  128. importedModule.getExportsType(moduleGraph, false) !== "namespace")
  129. ) {
  130. return [{ name: this.names.slice(0, -1), canInline: false }];
  131. }
  132. return [{ name: this.names, canInline: false }];
  133. }
  134. /**
  135. * Serializes this instance into the provided serializer context.
  136. * @param {ObjectSerializerContext} context context
  137. */
  138. serialize(context) {
  139. context
  140. .write(this.names)
  141. .write(this.idRanges)
  142. .write(this.call)
  143. .write(this.asiSafe)
  144. .write(this.branchGuards)
  145. .write(this._canConcatenate)
  146. .write(this.namespaceObjectAsContext)
  147. .write(this.usedByExports);
  148. super.serialize(context);
  149. }
  150. /**
  151. * Restores this instance from the provided deserializer context.
  152. * @param {ObjectDeserializerContext} context context
  153. */
  154. deserialize(context) {
  155. this.names = context.read();
  156. const c1 = context.rest;
  157. this.idRanges = c1.read();
  158. const c2 = c1.rest;
  159. this.call = c2.read();
  160. const c3 = c2.rest;
  161. this.asiSafe = c3.read();
  162. const c4 = c3.rest;
  163. this.branchGuards = c4.read();
  164. const c5 = c4.rest;
  165. this._canConcatenate = c5.read();
  166. const c6 = c5.rest;
  167. this.namespaceObjectAsContext = c6.read();
  168. const c7 = c6.rest;
  169. this.usedByExports = c7.read();
  170. super.deserialize(c7.rest);
  171. }
  172. get type() {
  173. return "cjs full require";
  174. }
  175. get category() {
  176. return "commonjs";
  177. }
  178. }
  179. CommonJsFullRequireDependency.Template = class CommonJsFullRequireDependencyTemplate extends (
  180. ModuleDependency.Template
  181. ) {
  182. /**
  183. * Applies the plugin by registering its hooks on the compiler.
  184. * @param {Dependency} dependency the dependency for which the template should be applied
  185. * @param {ReplaceSource} source the current replace source which can be modified
  186. * @param {DependencyTemplateContext} templateContext the context object
  187. * @returns {void}
  188. */
  189. apply(
  190. dependency,
  191. source,
  192. {
  193. runtimeTemplate,
  194. moduleGraph,
  195. chunkGraph,
  196. runtimeRequirements,
  197. runtime,
  198. concatenationScope
  199. }
  200. ) {
  201. const dep = /** @type {CommonJsFullRequireDependency} */ (dependency);
  202. if (!dep.range) return;
  203. const connection = moduleGraph.getConnection(dep);
  204. // Dead branch: module is excluded and has no id; code is never executed.
  205. if (connection && !connection.isTargetActive(runtime)) {
  206. // Replaces the whole member chain, so no property access is left dangling
  207. source.replace(dep.range[0], dep.range[1] - 1, "null /* dead branch */");
  208. return;
  209. }
  210. const importedModule = moduleGraph.getModule(dep);
  211. const {
  212. trimmedRange: [trimmedRangeStart, trimmedRangeEnd],
  213. trimmedIds
  214. } = getTrimmedIdsAndRange(
  215. dep.names,
  216. dep.range,
  217. dep.idRanges,
  218. moduleGraph,
  219. dep
  220. );
  221. if (concatenationScope) {
  222. const connection = moduleGraph.getConnection(dep);
  223. if (
  224. connection &&
  225. importedModule &&
  226. concatenationScope.isModuleInScope(importedModule)
  227. ) {
  228. // `require(esm)` unwraps the "module.exports" export and the member
  229. // chain hangs off it; without that prefix the ids name exports the
  230. // module never declares.
  231. const ids = isRequireEsmModuleExportsModule(
  232. connection.module,
  233. moduleGraph
  234. )
  235. ? [ESM_MODULE_EXPORTS_NAME, ...trimmedIds]
  236. : trimmedIds;
  237. source.replace(
  238. trimmedRangeStart,
  239. trimmedRangeEnd - 1,
  240. concatenationScope.createModuleReference(connection.module, {
  241. ids: ids.length > 0 ? ids : undefined,
  242. call: dep.call,
  243. asiSafe: dep.asiSafe,
  244. moduleExportsAccess: true
  245. })
  246. );
  247. return;
  248. }
  249. }
  250. let requireExpr = runtimeTemplate.moduleExports({
  251. module: importedModule,
  252. chunkGraph,
  253. request: dep.request,
  254. weak: dep.weak,
  255. runtimeRequirements
  256. });
  257. const esmRequireAccess = importedModule
  258. ? getRequireEsmModuleExportsAccess(importedModule, moduleGraph, runtime)
  259. : null;
  260. if (esmRequireAccess !== null) {
  261. const access = `${esmRequireAccess}${propertyAccess(trimmedIds)}`;
  262. requireExpr =
  263. dep.asiSafe === true
  264. ? `(${requireExpr}${access})`
  265. : `${requireExpr}${access}`;
  266. } else if (importedModule) {
  267. // CJS required are never inlined
  268. const usedImported = /** @type {string | string[] | false} */ (
  269. moduleGraph
  270. .getExportsInfo(importedModule)
  271. .getUsedName(trimmedIds, runtime)
  272. );
  273. if (usedImported) {
  274. const comment = equals(usedImported, trimmedIds)
  275. ? ""
  276. : `${Template.toNormalComment(propertyAccess(trimmedIds))} `;
  277. const access = `${comment}${propertyAccess(/** @type {string[]} */ (usedImported))}`;
  278. requireExpr =
  279. dep.asiSafe === true
  280. ? `(${requireExpr}${access})`
  281. : `${requireExpr}${access}`;
  282. }
  283. }
  284. source.replace(trimmedRangeStart, trimmedRangeEnd - 1, requireExpr);
  285. }
  286. };
  287. makeSerializable(
  288. CommonJsFullRequireDependency,
  289. "webpack/lib/dependencies/CommonJsFullRequireDependency"
  290. );
  291. module.exports = CommonJsFullRequireDependency;