HarmonyImportSpecifierDependency.js 16 KB

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