CommonJsExportRequireDependency.js 12 KB

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