CommonJsExportRequireDependency.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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/propertyAccess");
  12. const { handleDependencyBase } = require("./CommonJsDependencyHelpers");
  13. const ModuleDependency = require("./ModuleDependency");
  14. const processExportInfo = require("./processExportInfo");
  15. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  16. /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
  17. /** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
  18. /** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */
  19. /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
  20. /** @typedef {import("../Module")} Module */
  21. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  22. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  23. const idsSymbol = Symbol("CommonJsExportRequireDependency.ids");
  24. const EMPTY_OBJECT = {};
  25. class CommonJsExportRequireDependency extends ModuleDependency {
  26. constructor(range, valueRange, base, names, request, ids, resultUsed) {
  27. super(request);
  28. this.range = range;
  29. this.valueRange = valueRange;
  30. this.base = base;
  31. this.names = names;
  32. this.ids = ids;
  33. this.resultUsed = resultUsed;
  34. this.asiSafe = undefined;
  35. }
  36. get type() {
  37. return "cjs export require";
  38. }
  39. /**
  40. * @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
  41. */
  42. couldAffectReferencingModule() {
  43. return Dependency.TRANSITIVE;
  44. }
  45. /**
  46. * @param {ModuleGraph} moduleGraph the module graph
  47. * @returns {string[]} the imported id
  48. */
  49. getIds(moduleGraph) {
  50. return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
  51. }
  52. /**
  53. * @param {ModuleGraph} moduleGraph the module graph
  54. * @param {string[]} ids the imported ids
  55. * @returns {void}
  56. */
  57. setIds(moduleGraph, ids) {
  58. moduleGraph.getMeta(this)[idsSymbol] = ids;
  59. }
  60. /**
  61. * Returns list of exports referenced by this dependency
  62. * @param {ModuleGraph} moduleGraph module graph
  63. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  64. * @returns {(string[] | ReferencedExport)[]} referenced exports
  65. */
  66. getReferencedExports(moduleGraph, runtime) {
  67. const ids = this.getIds(moduleGraph);
  68. const getFullResult = () => {
  69. if (ids.length === 0) {
  70. return Dependency.EXPORTS_OBJECT_REFERENCED;
  71. } else {
  72. return [
  73. {
  74. name: ids,
  75. canMangle: false
  76. }
  77. ];
  78. }
  79. };
  80. if (this.resultUsed) return getFullResult();
  81. let exportsInfo = moduleGraph.getExportsInfo(
  82. moduleGraph.getParentModule(this)
  83. );
  84. for (const name of this.names) {
  85. const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
  86. const used = exportInfo.getUsed(runtime);
  87. if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED;
  88. if (used !== UsageState.OnlyPropertiesUsed) return getFullResult();
  89. exportsInfo = exportInfo.exportsInfo;
  90. if (!exportsInfo) return getFullResult();
  91. }
  92. if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
  93. return getFullResult();
  94. }
  95. /** @type {string[][]} */
  96. const referencedExports = [];
  97. for (const exportInfo of exportsInfo.orderedExports) {
  98. processExportInfo(
  99. runtime,
  100. referencedExports,
  101. ids.concat(exportInfo.name),
  102. exportInfo,
  103. false
  104. );
  105. }
  106. return referencedExports.map(name => ({
  107. name,
  108. canMangle: false
  109. }));
  110. }
  111. /**
  112. * Returns the exported names
  113. * @param {ModuleGraph} moduleGraph module graph
  114. * @returns {ExportsSpec | undefined} export names
  115. */
  116. getExports(moduleGraph) {
  117. const ids = this.getIds(moduleGraph);
  118. if (this.names.length === 1) {
  119. const name = this.names[0];
  120. const from = moduleGraph.getConnection(this);
  121. if (!from) return;
  122. return {
  123. exports: [
  124. {
  125. name,
  126. from,
  127. export: ids.length === 0 ? null : ids,
  128. // we can't mangle names that are in an empty object
  129. // because one could access the prototype property
  130. // when export isn't set yet
  131. canMangle: !(name in EMPTY_OBJECT) && false
  132. }
  133. ],
  134. dependencies: [from.module]
  135. };
  136. } else if (this.names.length > 0) {
  137. const name = this.names[0];
  138. return {
  139. exports: [
  140. {
  141. name,
  142. // we can't mangle names that are in an empty object
  143. // because one could access the prototype property
  144. // when export isn't set yet
  145. canMangle: !(name in EMPTY_OBJECT) && false
  146. }
  147. ],
  148. dependencies: undefined
  149. };
  150. } else {
  151. const from = moduleGraph.getConnection(this);
  152. if (!from) return;
  153. const reexportInfo = this.getStarReexports(
  154. moduleGraph,
  155. undefined,
  156. from.module
  157. );
  158. if (reexportInfo) {
  159. return {
  160. exports: Array.from(reexportInfo.exports, name => {
  161. return {
  162. name,
  163. from,
  164. export: ids.concat(name),
  165. canMangle: !(name in EMPTY_OBJECT) && false
  166. };
  167. }),
  168. // TODO handle deep reexports
  169. dependencies: [from.module]
  170. };
  171. } else {
  172. return {
  173. exports: true,
  174. from: ids.length === 0 ? from : undefined,
  175. canMangle: false,
  176. dependencies: [from.module]
  177. };
  178. }
  179. }
  180. }
  181. /**
  182. * @param {ModuleGraph} moduleGraph the module graph
  183. * @param {RuntimeSpec} runtime the runtime
  184. * @param {Module} importedModule the imported module (optional)
  185. * @returns {{exports?: Set<string>, checked?: Set<string>}} information
  186. */
  187. getStarReexports(
  188. moduleGraph,
  189. runtime,
  190. importedModule = moduleGraph.getModule(this)
  191. ) {
  192. let importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
  193. const ids = this.getIds(moduleGraph);
  194. if (ids.length > 0)
  195. importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids);
  196. let exportsInfo = moduleGraph.getExportsInfo(
  197. moduleGraph.getParentModule(this)
  198. );
  199. if (this.names.length > 0)
  200. exportsInfo = exportsInfo.getNestedExportsInfo(this.names);
  201. const noExtraExports =
  202. importedExportsInfo &&
  203. importedExportsInfo.otherExportsInfo.provided === false;
  204. const noExtraImports =
  205. exportsInfo &&
  206. exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused;
  207. if (!noExtraExports && !noExtraImports) {
  208. return;
  209. }
  210. const isNamespaceImport =
  211. importedModule.getExportsType(moduleGraph, false) === "namespace";
  212. /** @type {Set<string>} */
  213. const exports = new Set();
  214. /** @type {Set<string>} */
  215. const checked = new Set();
  216. if (noExtraImports) {
  217. for (const exportInfo of exportsInfo.orderedExports) {
  218. const name = exportInfo.name;
  219. if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
  220. if (name === "__esModule" && isNamespaceImport) {
  221. exports.add(name);
  222. } else if (importedExportsInfo) {
  223. const importedExportInfo =
  224. importedExportsInfo.getReadOnlyExportInfo(name);
  225. if (importedExportInfo.provided === false) continue;
  226. exports.add(name);
  227. if (importedExportInfo.provided === true) continue;
  228. checked.add(name);
  229. } else {
  230. exports.add(name);
  231. checked.add(name);
  232. }
  233. }
  234. } else if (noExtraExports) {
  235. for (const importedExportInfo of importedExportsInfo.orderedExports) {
  236. const name = importedExportInfo.name;
  237. if (importedExportInfo.provided === false) continue;
  238. if (exportsInfo) {
  239. const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
  240. if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
  241. }
  242. exports.add(name);
  243. if (importedExportInfo.provided === true) continue;
  244. checked.add(name);
  245. }
  246. if (isNamespaceImport) {
  247. exports.add("__esModule");
  248. checked.delete("__esModule");
  249. }
  250. }
  251. return { exports, checked };
  252. }
  253. serialize(context) {
  254. const { write } = context;
  255. write(this.asiSafe);
  256. write(this.range);
  257. write(this.valueRange);
  258. write(this.base);
  259. write(this.names);
  260. write(this.ids);
  261. write(this.resultUsed);
  262. super.serialize(context);
  263. }
  264. deserialize(context) {
  265. const { read } = context;
  266. this.asiSafe = read();
  267. this.range = read();
  268. this.valueRange = read();
  269. this.base = read();
  270. this.names = read();
  271. this.ids = read();
  272. this.resultUsed = read();
  273. super.deserialize(context);
  274. }
  275. }
  276. makeSerializable(
  277. CommonJsExportRequireDependency,
  278. "webpack/lib/dependencies/CommonJsExportRequireDependency"
  279. );
  280. CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends (
  281. ModuleDependency.Template
  282. ) {
  283. /**
  284. * @param {Dependency} dependency the dependency for which the template should be applied
  285. * @param {ReplaceSource} source the current replace source which can be modified
  286. * @param {DependencyTemplateContext} templateContext the context object
  287. * @returns {void}
  288. */
  289. apply(
  290. dependency,
  291. source,
  292. {
  293. module,
  294. runtimeTemplate,
  295. chunkGraph,
  296. moduleGraph,
  297. runtimeRequirements,
  298. runtime
  299. }
  300. ) {
  301. const dep = /** @type {CommonJsExportRequireDependency} */ (dependency);
  302. const used = moduleGraph
  303. .getExportsInfo(module)
  304. .getUsedName(dep.names, runtime);
  305. const [type, base] = handleDependencyBase(
  306. dep.base,
  307. module,
  308. runtimeRequirements
  309. );
  310. const importedModule = moduleGraph.getModule(dep);
  311. let requireExpr = runtimeTemplate.moduleExports({
  312. module: importedModule,
  313. chunkGraph,
  314. request: dep.request,
  315. weak: dep.weak,
  316. runtimeRequirements
  317. });
  318. if (importedModule) {
  319. const ids = dep.getIds(moduleGraph);
  320. const usedImported = moduleGraph
  321. .getExportsInfo(importedModule)
  322. .getUsedName(ids, runtime);
  323. if (usedImported) {
  324. const comment = equals(usedImported, ids)
  325. ? ""
  326. : Template.toNormalComment(propertyAccess(ids)) + " ";
  327. requireExpr += `${comment}${propertyAccess(usedImported)}`;
  328. }
  329. }
  330. switch (type) {
  331. case "expression":
  332. source.replace(
  333. dep.range[0],
  334. dep.range[1] - 1,
  335. used
  336. ? `${base}${propertyAccess(used)} = ${requireExpr}`
  337. : `/* unused reexport */ ${requireExpr}`
  338. );
  339. return;
  340. case "Object.defineProperty":
  341. throw new Error("TODO");
  342. default:
  343. throw new Error("Unexpected type");
  344. }
  345. }
  346. };
  347. module.exports = CommonJsExportRequireDependency;