HarmonyImportSpecifierDependency.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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 {
  8. getDependencyUsedByExportsCondition
  9. } = require("../optimize/InnerGraph");
  10. const makeSerializable = require("../util/makeSerializable");
  11. const propertyAccess = require("../util/propertyAccess");
  12. const HarmonyImportDependency = require("./HarmonyImportDependency");
  13. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  14. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  15. /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
  16. /** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
  17. /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
  18. /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
  19. /** @typedef {import("../Module")} Module */
  20. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  21. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  22. /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
  23. /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
  24. /** @typedef {import("../WebpackError")} WebpackError */
  25. /** @typedef {import("../javascript/JavascriptParser").Assertions} Assertions */
  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/Hash")} Hash */
  30. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  31. const idsSymbol = Symbol("HarmonyImportSpecifierDependency.ids");
  32. const { ExportPresenceModes } = HarmonyImportDependency;
  33. class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
  34. /**
  35. * @param {TODO} request request
  36. * @param {number} sourceOrder source order
  37. * @param {string[]} ids ids
  38. * @param {string} name name
  39. * @param {Range} range range
  40. * @param {TODO} exportPresenceMode export presence mode
  41. * @param {Assertions=} assertions assertions
  42. * @param {Range[]=} idRanges ranges for members of ids; the two arrays are right-aligned
  43. */
  44. constructor(
  45. request,
  46. sourceOrder,
  47. ids,
  48. name,
  49. range,
  50. exportPresenceMode,
  51. assertions,
  52. idRanges // TODO webpack 6 make this non-optional. It must always be set to properly trim ids.
  53. ) {
  54. super(request, sourceOrder, assertions);
  55. this.ids = ids;
  56. this.name = name;
  57. this.range = range;
  58. this.idRanges = idRanges;
  59. this.exportPresenceMode = exportPresenceMode;
  60. /** @type {boolean | undefined} */
  61. this.namespaceObjectAsContext = false;
  62. this.call = undefined;
  63. this.directImport = undefined;
  64. this.shorthand = undefined;
  65. this.asiSafe = undefined;
  66. /** @type {Set<string> | boolean | undefined} */
  67. this.usedByExports = undefined;
  68. /** @type {Set<string> | undefined} */
  69. this.referencedPropertiesInDestructuring = undefined;
  70. }
  71. // TODO webpack 6 remove
  72. get id() {
  73. throw new Error("id was renamed to ids and type changed to string[]");
  74. }
  75. // TODO webpack 6 remove
  76. getId() {
  77. throw new Error("id was renamed to ids and type changed to string[]");
  78. }
  79. // TODO webpack 6 remove
  80. setId() {
  81. throw new Error("id was renamed to ids and type changed to string[]");
  82. }
  83. get type() {
  84. return "harmony import specifier";
  85. }
  86. /**
  87. * @param {ModuleGraph} moduleGraph the module graph
  88. * @returns {string[]} the imported ids
  89. */
  90. getIds(moduleGraph) {
  91. const meta = moduleGraph.getMetaIfExisting(this);
  92. if (meta === undefined) return this.ids;
  93. const ids = meta[idsSymbol];
  94. return ids !== undefined ? ids : this.ids;
  95. }
  96. /**
  97. * @param {ModuleGraph} moduleGraph the module graph
  98. * @param {string[]} ids the imported ids
  99. * @returns {void}
  100. */
  101. setIds(moduleGraph, ids) {
  102. moduleGraph.getMeta(this)[idsSymbol] = ids;
  103. }
  104. /**
  105. * @param {ModuleGraph} moduleGraph module graph
  106. * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active
  107. */
  108. getCondition(moduleGraph) {
  109. return getDependencyUsedByExportsCondition(
  110. this,
  111. this.usedByExports,
  112. moduleGraph
  113. );
  114. }
  115. /**
  116. * @param {ModuleGraph} moduleGraph the module graph
  117. * @returns {ConnectionState} how this dependency connects the module to referencing modules
  118. */
  119. getModuleEvaluationSideEffectsState(moduleGraph) {
  120. return false;
  121. }
  122. /**
  123. * Returns list of exports referenced by this dependency
  124. * @param {ModuleGraph} moduleGraph module graph
  125. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  126. * @returns {(string[] | ReferencedExport)[]} referenced exports
  127. */
  128. getReferencedExports(moduleGraph, runtime) {
  129. let ids = this.getIds(moduleGraph);
  130. if (ids.length === 0) return this._getReferencedExportsInDestructuring();
  131. let namespaceObjectAsContext = this.namespaceObjectAsContext;
  132. if (ids[0] === "default") {
  133. const selfModule = moduleGraph.getParentModule(this);
  134. const importedModule =
  135. /** @type {Module} */
  136. (moduleGraph.getModule(this));
  137. switch (
  138. importedModule.getExportsType(
  139. moduleGraph,
  140. /** @type {BuildMeta} */
  141. (selfModule.buildMeta).strictHarmonyModule
  142. )
  143. ) {
  144. case "default-only":
  145. case "default-with-named":
  146. if (ids.length === 1)
  147. return this._getReferencedExportsInDestructuring();
  148. ids = ids.slice(1);
  149. namespaceObjectAsContext = true;
  150. break;
  151. case "dynamic":
  152. return Dependency.EXPORTS_OBJECT_REFERENCED;
  153. }
  154. }
  155. if (
  156. this.call &&
  157. !this.directImport &&
  158. (namespaceObjectAsContext || ids.length > 1)
  159. ) {
  160. if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED;
  161. ids = ids.slice(0, -1);
  162. }
  163. return this._getReferencedExportsInDestructuring(ids);
  164. }
  165. /**
  166. * @param {string[]=} ids ids
  167. * @returns {(string[] | ReferencedExport)[]} referenced exports
  168. */
  169. _getReferencedExportsInDestructuring(ids) {
  170. if (this.referencedPropertiesInDestructuring) {
  171. /** @type {ReferencedExport[]} */
  172. const refs = [];
  173. for (const key of this.referencedPropertiesInDestructuring) {
  174. refs.push({
  175. name: ids ? ids.concat([key]) : [key],
  176. canMangle: false
  177. });
  178. }
  179. return refs;
  180. } else {
  181. return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED;
  182. }
  183. }
  184. /**
  185. * @param {ModuleGraph} moduleGraph module graph
  186. * @returns {number} effective mode
  187. */
  188. _getEffectiveExportPresenceLevel(moduleGraph) {
  189. if (this.exportPresenceMode !== ExportPresenceModes.AUTO)
  190. return this.exportPresenceMode;
  191. const buildMeta = /** @type {BuildMeta} */ (
  192. moduleGraph.getParentModule(this).buildMeta
  193. );
  194. return buildMeta.strictHarmonyModule
  195. ? ExportPresenceModes.ERROR
  196. : ExportPresenceModes.WARN;
  197. }
  198. /**
  199. * Returns warnings
  200. * @param {ModuleGraph} moduleGraph module graph
  201. * @returns {WebpackError[] | null | undefined} warnings
  202. */
  203. getWarnings(moduleGraph) {
  204. const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
  205. if (exportsPresence === ExportPresenceModes.WARN) {
  206. return this._getErrors(moduleGraph);
  207. }
  208. return null;
  209. }
  210. /**
  211. * Returns errors
  212. * @param {ModuleGraph} moduleGraph module graph
  213. * @returns {WebpackError[] | null | undefined} errors
  214. */
  215. getErrors(moduleGraph) {
  216. const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
  217. if (exportsPresence === ExportPresenceModes.ERROR) {
  218. return this._getErrors(moduleGraph);
  219. }
  220. return null;
  221. }
  222. /**
  223. * @param {ModuleGraph} moduleGraph module graph
  224. * @returns {WebpackError[] | undefined} errors
  225. */
  226. _getErrors(moduleGraph) {
  227. const ids = this.getIds(moduleGraph);
  228. return this.getLinkingErrors(
  229. moduleGraph,
  230. ids,
  231. `(imported as '${this.name}')`
  232. );
  233. }
  234. /**
  235. * implement this method to allow the occurrence order plugin to count correctly
  236. * @returns {number} count how often the id is used in this dependency
  237. */
  238. getNumberOfIdOccurrences() {
  239. return 0;
  240. }
  241. /**
  242. * @param {ObjectSerializerContext} context context
  243. */
  244. serialize(context) {
  245. const { write } = context;
  246. write(this.ids);
  247. write(this.name);
  248. write(this.range);
  249. write(this.idRanges);
  250. write(this.exportPresenceMode);
  251. write(this.namespaceObjectAsContext);
  252. write(this.call);
  253. write(this.directImport);
  254. write(this.shorthand);
  255. write(this.asiSafe);
  256. write(this.usedByExports);
  257. write(this.referencedPropertiesInDestructuring);
  258. super.serialize(context);
  259. }
  260. /**
  261. * @param {ObjectDeserializerContext} context context
  262. */
  263. deserialize(context) {
  264. const { read } = context;
  265. this.ids = read();
  266. this.name = read();
  267. this.range = read();
  268. this.idRanges = read();
  269. this.exportPresenceMode = read();
  270. this.namespaceObjectAsContext = read();
  271. this.call = read();
  272. this.directImport = read();
  273. this.shorthand = read();
  274. this.asiSafe = read();
  275. this.usedByExports = read();
  276. this.referencedPropertiesInDestructuring = read();
  277. super.deserialize(context);
  278. }
  279. }
  280. makeSerializable(
  281. HarmonyImportSpecifierDependency,
  282. "webpack/lib/dependencies/HarmonyImportSpecifierDependency"
  283. );
  284. HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends (
  285. HarmonyImportDependency.Template
  286. ) {
  287. /**
  288. * @param {Dependency} dependency the dependency for which the template should be applied
  289. * @param {ReplaceSource} source the current replace source which can be modified
  290. * @param {DependencyTemplateContext} templateContext the context object
  291. * @returns {void}
  292. */
  293. apply(dependency, source, templateContext) {
  294. const dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency);
  295. const { moduleGraph, runtime } = templateContext;
  296. const connection = moduleGraph.getConnection(dep);
  297. // Skip rendering depending when dependency is conditional
  298. if (connection && !connection.isTargetActive(runtime)) return;
  299. const ids = dep.getIds(moduleGraph); // determine minimal set of IDs.
  300. let trimmedIds = this._trimIdsToThoseImported(ids, moduleGraph, dep);
  301. let [rangeStart, rangeEnd] = dep.range;
  302. if (trimmedIds.length !== ids.length) {
  303. // The array returned from dep.idRanges is right-aligned with the array returned from dep.getIds.
  304. // Meaning, the two arrays may not always have the same number of elements, but the last element of
  305. // dep.idRanges corresponds to [the expression fragment to the left of] the last element of dep.getIds.
  306. // Use this to find the correct replacement range based on the number of ids that were trimmed.
  307. const idx =
  308. dep.idRanges === undefined
  309. ? -1 /* trigger failure case below */
  310. : dep.idRanges.length + (trimmedIds.length - ids.length);
  311. if (idx < 0 || idx >= dep.idRanges.length) {
  312. // cspell:ignore minifiers
  313. // Should not happen but we can't throw an error here because of backward compatibility with
  314. // external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers.
  315. trimmedIds = ids;
  316. // TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead.
  317. // throw new Error("Missing range starts data for id replacement trimming.");
  318. } else {
  319. [rangeStart, rangeEnd] = dep.idRanges[idx];
  320. }
  321. }
  322. const exportExpr = this._getCodeForIds(
  323. dep,
  324. source,
  325. templateContext,
  326. trimmedIds
  327. );
  328. if (dep.shorthand) {
  329. source.insert(rangeEnd, `: ${exportExpr}`);
  330. } else {
  331. source.replace(rangeStart, rangeEnd - 1, exportExpr);
  332. }
  333. }
  334. /**
  335. * @summary Determine which IDs in the id chain are actually referring to namespaces or imports,
  336. * and which are deeper member accessors on the imported object. Only the former should be re-rendered.
  337. * @param {string[]} ids ids
  338. * @param {ModuleGraph} moduleGraph moduleGraph
  339. * @param {HarmonyImportSpecifierDependency} dependency dependency
  340. * @returns {string[]} generated code
  341. */
  342. _trimIdsToThoseImported(ids, moduleGraph, dependency) {
  343. /** @type {string[]} */
  344. let trimmedIds = [];
  345. const exportsInfo = moduleGraph.getExportsInfo(
  346. /** @type {Module} */ (moduleGraph.getModule(dependency))
  347. );
  348. let currentExportsInfo = /** @type {ExportsInfo=} */ exportsInfo;
  349. for (let i = 0; i < ids.length; i++) {
  350. if (i === 0 && ids[i] === "default") {
  351. continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo
  352. }
  353. const exportInfo = currentExportsInfo.getExportInfo(ids[i]);
  354. if (exportInfo.provided === false) {
  355. // json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided
  356. trimmedIds = ids.slice(0, i);
  357. break;
  358. }
  359. const nestedInfo = exportInfo.getNestedExportsInfo();
  360. if (!nestedInfo) {
  361. // once all nested exports are traversed, the next item is the actual import so stop there
  362. trimmedIds = ids.slice(0, i + 1);
  363. break;
  364. }
  365. currentExportsInfo = nestedInfo;
  366. }
  367. // Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule")
  368. return trimmedIds.length ? trimmedIds : ids;
  369. }
  370. /**
  371. * @param {HarmonyImportSpecifierDependency} dep dependency
  372. * @param {ReplaceSource} source source
  373. * @param {DependencyTemplateContext} templateContext context
  374. * @param {string[]} ids ids
  375. * @returns {string} generated code
  376. */
  377. _getCodeForIds(dep, source, templateContext, ids) {
  378. const { moduleGraph, module, runtime, concatenationScope } =
  379. templateContext;
  380. const connection = moduleGraph.getConnection(dep);
  381. let exportExpr;
  382. if (
  383. connection &&
  384. concatenationScope &&
  385. concatenationScope.isModuleInScope(connection.module)
  386. ) {
  387. if (ids.length === 0) {
  388. exportExpr = concatenationScope.createModuleReference(
  389. connection.module,
  390. {
  391. asiSafe: dep.asiSafe
  392. }
  393. );
  394. } else if (dep.namespaceObjectAsContext && ids.length === 1) {
  395. exportExpr =
  396. concatenationScope.createModuleReference(connection.module, {
  397. asiSafe: dep.asiSafe
  398. }) + propertyAccess(ids);
  399. } else {
  400. exportExpr = concatenationScope.createModuleReference(
  401. connection.module,
  402. {
  403. ids,
  404. call: dep.call,
  405. directImport: dep.directImport,
  406. asiSafe: dep.asiSafe
  407. }
  408. );
  409. }
  410. } else {
  411. super.apply(dep, source, templateContext);
  412. const { runtimeTemplate, initFragments, runtimeRequirements } =
  413. templateContext;
  414. exportExpr = runtimeTemplate.exportFromImport({
  415. moduleGraph,
  416. module: /** @type {Module} */ (moduleGraph.getModule(dep)),
  417. request: dep.request,
  418. exportName: ids,
  419. originModule: module,
  420. asiSafe: dep.shorthand ? true : dep.asiSafe,
  421. isCall: dep.call,
  422. callContext: !dep.directImport,
  423. defaultInterop: true,
  424. importVar: dep.getImportVar(moduleGraph),
  425. initFragments,
  426. runtime,
  427. runtimeRequirements
  428. });
  429. }
  430. return exportExpr;
  431. }
  432. };
  433. module.exports = HarmonyImportSpecifierDependency;