HarmonyImportSpecifierDependency.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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 InitFragment = require("../InitFragment");
  8. const Template = require("../Template");
  9. const {
  10. getDependencyUsedByExportsCondition
  11. } = require("../optimize/InnerGraph");
  12. const { getTrimmedIdsAndRange } = require("../util/chainedImports");
  13. const makeSerializable = require("../util/makeSerializable");
  14. const propertyAccess = require("../util/propertyAccess");
  15. const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties");
  16. const HarmonyImportDependency = require("./HarmonyImportDependency");
  17. const { ImportPhaseUtils } = require("./ImportPhase");
  18. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  19. /** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */
  20. /** @typedef {import("../Dependency").RawReferencedExports} RawReferencedExports */
  21. /** @typedef {import("../Dependency").ReferencedExports} ReferencedExports */
  22. /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
  23. /** @typedef {import("../Module")} Module */
  24. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  25. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  26. /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
  27. /** @typedef {import("../WebpackError")} WebpackError */
  28. /** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperties} DestructuringAssignmentProperties */
  29. /** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */
  30. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  31. /** @typedef {import("../optimize/InnerGraph").UsedByExports} UsedByExports */
  32. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  33. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  34. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  35. /** @typedef {import("../util/chainedImports").IdRanges} IdRanges */
  36. /** @typedef {import("./HarmonyImportDependency").ExportPresenceMode} ExportPresenceMode */
  37. /** @typedef {HarmonyImportDependency.Ids} Ids */
  38. /** @typedef {import("./ImportPhase").ImportPhaseType} ImportPhaseType */
  39. const idsSymbol = Symbol("HarmonyImportSpecifierDependency.ids");
  40. const { ExportPresenceModes } = HarmonyImportDependency;
  41. class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
  42. /**
  43. * @param {string} request request
  44. * @param {number} sourceOrder source order
  45. * @param {Ids} ids ids
  46. * @param {string} name name
  47. * @param {Range} range range
  48. * @param {ExportPresenceMode} exportPresenceMode export presence mode
  49. * @param {ImportPhaseType} phase import phase
  50. * @param {ImportAttributes | undefined} attributes import attributes
  51. * @param {IdRanges | undefined} idRanges ranges for members of ids; the two arrays are right-aligned
  52. */
  53. constructor(
  54. request,
  55. sourceOrder,
  56. ids,
  57. name,
  58. range,
  59. exportPresenceMode,
  60. phase,
  61. attributes,
  62. idRanges // TODO webpack 6 make this non-optional. It must always be set to properly trim ids.
  63. ) {
  64. super(request, sourceOrder, phase, attributes);
  65. this.ids = ids;
  66. this.name = name;
  67. this.range = range;
  68. this.idRanges = idRanges;
  69. this.exportPresenceMode = exportPresenceMode;
  70. /** @type {undefined | boolean} */
  71. this.namespaceObjectAsContext = false;
  72. /** @type {undefined | boolean} */
  73. this.call = undefined;
  74. /** @type {undefined | boolean} */
  75. this.directImport = undefined;
  76. /** @type {undefined | boolean | string} */
  77. this.shorthand = undefined;
  78. /** @type {undefined | boolean} */
  79. this.asiSafe = undefined;
  80. /** @type {UsedByExports | undefined} */
  81. this.usedByExports = undefined;
  82. /** @type {DestructuringAssignmentProperties | undefined} */
  83. this.referencedPropertiesInDestructuring = undefined;
  84. }
  85. // TODO webpack 6 remove
  86. get id() {
  87. throw new Error("id was renamed to ids and type changed to string[]");
  88. }
  89. // TODO webpack 6 remove
  90. getId() {
  91. throw new Error("id was renamed to ids and type changed to string[]");
  92. }
  93. // TODO webpack 6 remove
  94. setId() {
  95. throw new Error("id was renamed to ids and type changed to string[]");
  96. }
  97. get type() {
  98. return "harmony import specifier";
  99. }
  100. /**
  101. * @param {ModuleGraph} moduleGraph the module graph
  102. * @returns {Ids} the imported ids
  103. */
  104. getIds(moduleGraph) {
  105. const meta = moduleGraph.getMetaIfExisting(this);
  106. if (meta === undefined) return this.ids;
  107. const ids = meta[idsSymbol];
  108. return ids !== undefined ? ids : this.ids;
  109. }
  110. /**
  111. * @param {ModuleGraph} moduleGraph the module graph
  112. * @param {Ids} ids the imported ids
  113. * @returns {void}
  114. */
  115. setIds(moduleGraph, ids) {
  116. moduleGraph.getMeta(this)[idsSymbol] = ids;
  117. }
  118. /**
  119. * @param {ModuleGraph} moduleGraph module graph
  120. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  121. */
  122. getCondition(moduleGraph) {
  123. return getDependencyUsedByExportsCondition(
  124. this,
  125. this.usedByExports,
  126. moduleGraph
  127. );
  128. }
  129. /**
  130. * @param {ModuleGraph} moduleGraph the module graph
  131. * @returns {ConnectionState} how this dependency connects the module to referencing modules
  132. */
  133. getModuleEvaluationSideEffectsState(moduleGraph) {
  134. return false;
  135. }
  136. /**
  137. * Returns list of exports referenced by this dependency
  138. * @param {ModuleGraph} moduleGraph module graph
  139. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  140. * @returns {ReferencedExports} referenced exports
  141. */
  142. getReferencedExports(moduleGraph, runtime) {
  143. let ids = this.getIds(moduleGraph);
  144. if (ids.length === 0) return this._getReferencedExportsInDestructuring();
  145. let namespaceObjectAsContext = this.namespaceObjectAsContext;
  146. if (ids[0] === "default") {
  147. const selfModule =
  148. /** @type {Module} */
  149. (moduleGraph.getParentModule(this));
  150. const importedModule =
  151. /** @type {Module} */
  152. (moduleGraph.getModule(this));
  153. switch (
  154. importedModule.getExportsType(
  155. moduleGraph,
  156. /** @type {BuildMeta} */
  157. (selfModule.buildMeta).strictHarmonyModule
  158. )
  159. ) {
  160. case "default-only":
  161. case "default-with-named":
  162. if (ids.length === 1) {
  163. return this._getReferencedExportsInDestructuring();
  164. }
  165. ids = ids.slice(1);
  166. namespaceObjectAsContext = true;
  167. break;
  168. case "dynamic":
  169. return Dependency.EXPORTS_OBJECT_REFERENCED;
  170. }
  171. }
  172. if (
  173. this.call &&
  174. !this.directImport &&
  175. (namespaceObjectAsContext || ids.length > 1)
  176. ) {
  177. if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED;
  178. ids = ids.slice(0, -1);
  179. }
  180. return this._getReferencedExportsInDestructuring(ids);
  181. }
  182. /**
  183. * @param {Ids=} ids ids
  184. * @returns {RawReferencedExports} referenced exports
  185. */
  186. _getReferencedExportsInDestructuring(ids) {
  187. if (this.referencedPropertiesInDestructuring) {
  188. /** @type {RawReferencedExports} */
  189. const refsInDestructuring = [];
  190. traverseDestructuringAssignmentProperties(
  191. this.referencedPropertiesInDestructuring,
  192. (stack) => refsInDestructuring.push(stack.map((p) => p.id))
  193. );
  194. /** @type {RawReferencedExports} */
  195. const refs = [];
  196. for (const idsInDestructuring of refsInDestructuring) {
  197. refs.push(ids ? [...ids, ...idsInDestructuring] : idsInDestructuring);
  198. }
  199. return refs;
  200. }
  201. return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED;
  202. }
  203. /**
  204. * @param {ModuleGraph} moduleGraph module graph
  205. * @returns {ExportPresenceMode} effective mode
  206. */
  207. _getEffectiveExportPresenceLevel(moduleGraph) {
  208. if (this.exportPresenceMode !== ExportPresenceModes.AUTO) {
  209. return this.exportPresenceMode;
  210. }
  211. const buildMeta =
  212. /** @type {BuildMeta} */
  213. (
  214. /** @type {Module} */
  215. (moduleGraph.getParentModule(this)).buildMeta
  216. );
  217. return buildMeta.strictHarmonyModule
  218. ? ExportPresenceModes.ERROR
  219. : ExportPresenceModes.WARN;
  220. }
  221. /**
  222. * Returns warnings
  223. * @param {ModuleGraph} moduleGraph module graph
  224. * @returns {WebpackError[] | null | undefined} warnings
  225. */
  226. getWarnings(moduleGraph) {
  227. const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
  228. if (exportsPresence === ExportPresenceModes.WARN) {
  229. return this._getErrors(moduleGraph);
  230. }
  231. return null;
  232. }
  233. /**
  234. * Returns errors
  235. * @param {ModuleGraph} moduleGraph module graph
  236. * @returns {WebpackError[] | null | undefined} errors
  237. */
  238. getErrors(moduleGraph) {
  239. const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
  240. if (exportsPresence === ExportPresenceModes.ERROR) {
  241. return this._getErrors(moduleGraph);
  242. }
  243. return null;
  244. }
  245. /**
  246. * @param {ModuleGraph} moduleGraph module graph
  247. * @returns {WebpackError[] | undefined} errors
  248. */
  249. _getErrors(moduleGraph) {
  250. const ids = this.getIds(moduleGraph);
  251. return this.getLinkingErrors(
  252. moduleGraph,
  253. ids,
  254. `(imported as '${this.name}')`
  255. );
  256. }
  257. /**
  258. * implement this method to allow the occurrence order plugin to count correctly
  259. * @returns {number} count how often the id is used in this dependency
  260. */
  261. getNumberOfIdOccurrences() {
  262. return 0;
  263. }
  264. /**
  265. * @param {ObjectSerializerContext} context context
  266. */
  267. serialize(context) {
  268. const { write } = context;
  269. write(this.ids);
  270. write(this.name);
  271. write(this.range);
  272. write(this.idRanges);
  273. write(this.exportPresenceMode);
  274. write(this.namespaceObjectAsContext);
  275. write(this.call);
  276. write(this.directImport);
  277. write(this.shorthand);
  278. write(this.asiSafe);
  279. write(this.usedByExports);
  280. write(this.referencedPropertiesInDestructuring);
  281. super.serialize(context);
  282. }
  283. /**
  284. * @param {ObjectDeserializerContext} context context
  285. */
  286. deserialize(context) {
  287. const { read } = context;
  288. this.ids = read();
  289. this.name = read();
  290. this.range = read();
  291. this.idRanges = read();
  292. this.exportPresenceMode = read();
  293. this.namespaceObjectAsContext = read();
  294. this.call = read();
  295. this.directImport = read();
  296. this.shorthand = read();
  297. this.asiSafe = read();
  298. this.usedByExports = read();
  299. this.referencedPropertiesInDestructuring = read();
  300. super.deserialize(context);
  301. }
  302. }
  303. makeSerializable(
  304. HarmonyImportSpecifierDependency,
  305. "webpack/lib/dependencies/HarmonyImportSpecifierDependency"
  306. );
  307. HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends (
  308. HarmonyImportDependency.Template
  309. ) {
  310. /**
  311. * @param {Dependency} dependency the dependency for which the template should be applied
  312. * @param {ReplaceSource} source the current replace source which can be modified
  313. * @param {DependencyTemplateContext} templateContext the context object
  314. * @returns {void}
  315. */
  316. apply(dependency, source, templateContext) {
  317. const dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency);
  318. const { moduleGraph, runtime, initFragments } = templateContext;
  319. const connection = moduleGraph.getConnection(dep);
  320. // Only render declaration for import specifier when the dependency is conditional
  321. if (connection && !connection.isTargetActive(runtime)) {
  322. initFragments.push(
  323. new InitFragment(
  324. `/* unused harmony import specifier */ var ${dep.name};\n`,
  325. InitFragment.STAGE_HARMONY_IMPORTS,
  326. 0,
  327. `unused import specifier ${dep.name}`
  328. )
  329. );
  330. return;
  331. }
  332. const ids = dep.getIds(moduleGraph);
  333. const {
  334. trimmedRange: [trimmedRangeStart, trimmedRangeEnd],
  335. trimmedIds
  336. } = getTrimmedIdsAndRange(ids, dep.range, dep.idRanges, moduleGraph, dep);
  337. const exportExpr = this._getCodeForIds(
  338. dep,
  339. source,
  340. templateContext,
  341. trimmedIds
  342. );
  343. if (dep.shorthand) {
  344. source.insert(trimmedRangeEnd, `: ${exportExpr}`);
  345. } else {
  346. source.replace(trimmedRangeStart, trimmedRangeEnd - 1, exportExpr);
  347. }
  348. if (dep.referencedPropertiesInDestructuring) {
  349. let prefixedIds = ids;
  350. if (ids[0] === "default") {
  351. const selfModule =
  352. /** @type {Module} */
  353. (moduleGraph.getParentModule(dep));
  354. const importedModule =
  355. /** @type {Module} */
  356. (moduleGraph.getModule(dep));
  357. const exportsType = importedModule.getExportsType(
  358. moduleGraph,
  359. /** @type {BuildMeta} */
  360. (selfModule.buildMeta).strictHarmonyModule
  361. );
  362. if (
  363. (exportsType === "default-only" ||
  364. exportsType === "default-with-named") &&
  365. ids.length >= 1
  366. ) {
  367. prefixedIds = ids.slice(1);
  368. }
  369. }
  370. /** @type {{ ids: Ids, range: Range, shorthand: boolean | string }[]} */
  371. const replacementsInDestructuring = [];
  372. traverseDestructuringAssignmentProperties(
  373. dep.referencedPropertiesInDestructuring,
  374. undefined,
  375. (stack) => {
  376. const property = stack[stack.length - 1];
  377. replacementsInDestructuring.push({
  378. ids: stack.map((p) => p.id),
  379. range: property.range,
  380. shorthand: property.shorthand
  381. });
  382. }
  383. );
  384. for (const { ids, shorthand, range } of replacementsInDestructuring) {
  385. /** @type {Ids} */
  386. const concatedIds = [...prefixedIds, ...ids];
  387. const module = /** @type {Module} */ (moduleGraph.getModule(dep));
  388. const used = moduleGraph
  389. .getExportsInfo(module)
  390. .getUsedName(concatedIds, runtime);
  391. if (!used) return;
  392. const newName = used[used.length - 1];
  393. const name = concatedIds[concatedIds.length - 1];
  394. if (newName === name) continue;
  395. const comment = `${Template.toNormalComment(name)} `;
  396. const key = comment + JSON.stringify(newName);
  397. source.replace(
  398. range[0],
  399. range[1] - 1,
  400. shorthand ? `${key}: ${name}` : `${key}`
  401. );
  402. }
  403. }
  404. }
  405. /**
  406. * @param {HarmonyImportSpecifierDependency} dep dependency
  407. * @param {ReplaceSource} source source
  408. * @param {DependencyTemplateContext} templateContext context
  409. * @param {Ids} ids ids
  410. * @returns {string} generated code
  411. */
  412. _getCodeForIds(dep, source, templateContext, ids) {
  413. const { moduleGraph, module, runtime, concatenationScope } =
  414. templateContext;
  415. const connection = moduleGraph.getConnection(dep);
  416. /** @type {string} */
  417. let exportExpr;
  418. if (
  419. connection &&
  420. concatenationScope &&
  421. concatenationScope.isModuleInScope(connection.module)
  422. ) {
  423. if (ids.length === 0) {
  424. exportExpr = concatenationScope.createModuleReference(
  425. connection.module,
  426. {
  427. asiSafe: dep.asiSafe,
  428. deferredImport: ImportPhaseUtils.isDefer(dep.phase)
  429. }
  430. );
  431. } else if (dep.namespaceObjectAsContext && ids.length === 1) {
  432. exportExpr =
  433. concatenationScope.createModuleReference(connection.module, {
  434. asiSafe: dep.asiSafe,
  435. deferredImport: ImportPhaseUtils.isDefer(dep.phase)
  436. }) + propertyAccess(ids);
  437. } else {
  438. exportExpr = concatenationScope.createModuleReference(
  439. connection.module,
  440. {
  441. ids,
  442. call: dep.call,
  443. directImport: dep.directImport,
  444. asiSafe: dep.asiSafe,
  445. deferredImport: ImportPhaseUtils.isDefer(dep.phase)
  446. }
  447. );
  448. }
  449. } else {
  450. super.apply(dep, source, templateContext);
  451. const { runtimeTemplate, initFragments, runtimeRequirements } =
  452. templateContext;
  453. exportExpr = runtimeTemplate.exportFromImport({
  454. moduleGraph,
  455. module: /** @type {Module} */ (moduleGraph.getModule(dep)),
  456. chunkGraph: templateContext.chunkGraph,
  457. request: dep.request,
  458. exportName: ids,
  459. originModule: module,
  460. asiSafe: dep.shorthand ? true : dep.asiSafe,
  461. isCall: dep.call,
  462. callContext: !dep.directImport,
  463. defaultInterop: true,
  464. importVar: dep.getImportVar(moduleGraph),
  465. initFragments,
  466. runtime,
  467. runtimeRequirements,
  468. dependency: dep
  469. });
  470. }
  471. return exportExpr;
  472. }
  473. };
  474. module.exports = HarmonyImportSpecifierDependency;
  475. module.exports.idsSymbol = idsSymbol;