CommonJsExportRequireDependency.js 12 KB

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