chainedImports.js 4.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /** @typedef {import("../Dependency")} Dependency */
  7. /** @typedef {import("../Module")} Module */
  8. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  9. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  10. /**
  11. * @summary Get the subset of ids and their corresponding range in an id chain that should be re-rendered by webpack.
  12. * Only those in the chain that are actually referring to namespaces or imports should be re-rendered.
  13. * Deeper member accessors on the imported object should not be re-rendered. If deeper member accessors are re-rendered,
  14. * there is a potential loss of meaning with rendering a quoted accessor as an unquoted accessor, or vice versa,
  15. * because minifiers treat quoted accessors differently. e.g. import { a } from "./module"; a["b"] vs a.b
  16. * @param {string[]} untrimmedIds chained ids
  17. * @param {Range} untrimmedRange range encompassing allIds
  18. * @param {Range[] | undefined} ranges cumulative range of ids for each of allIds
  19. * @param {ModuleGraph} moduleGraph moduleGraph
  20. * @param {Dependency} dependency dependency
  21. * @returns {{trimmedIds: string[], trimmedRange: Range}} computed trimmed ids and cumulative range of those ids
  22. */
  23. exports.getTrimmedIdsAndRange = (
  24. untrimmedIds,
  25. untrimmedRange,
  26. ranges,
  27. moduleGraph,
  28. dependency
  29. ) => {
  30. let trimmedIds = trimIdsToThoseImported(
  31. untrimmedIds,
  32. moduleGraph,
  33. dependency
  34. );
  35. let trimmedRange = untrimmedRange;
  36. if (trimmedIds.length !== untrimmedIds.length) {
  37. // The array returned from dep.idRanges is right-aligned with the array returned from dep.names.
  38. // Meaning, the two arrays may not always have the same number of elements, but the last element of
  39. // dep.idRanges corresponds to [the expression fragment to the left of] the last element of dep.names.
  40. // Use this to find the correct replacement range based on the number of ids that were trimmed.
  41. const idx =
  42. ranges === undefined
  43. ? -1 /* trigger failure case below */
  44. : ranges.length + (trimmedIds.length - untrimmedIds.length);
  45. if (idx < 0 || idx >= /** @type {Range[]} */ (ranges).length) {
  46. // cspell:ignore minifiers
  47. // Should not happen but we can't throw an error here because of backward compatibility with
  48. // external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers.
  49. trimmedIds = untrimmedIds;
  50. // TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead.
  51. // throw new Error("Missing range starts data for id replacement trimming.");
  52. } else {
  53. trimmedRange = /** @type {Range[]} */ (ranges)[idx];
  54. }
  55. }
  56. return { trimmedIds, trimmedRange };
  57. };
  58. /**
  59. * @summary Determine which IDs in the id chain are actually referring to namespaces or imports,
  60. * and which are deeper member accessors on the imported object.
  61. * @param {string[]} ids untrimmed ids
  62. * @param {ModuleGraph} moduleGraph moduleGraph
  63. * @param {Dependency} dependency dependency
  64. * @returns {string[]} trimmed ids
  65. */
  66. function trimIdsToThoseImported(ids, moduleGraph, dependency) {
  67. /** @type {string[]} */
  68. let trimmedIds = [];
  69. let currentExportsInfo = moduleGraph.getExportsInfo(
  70. /** @type {Module} */ (moduleGraph.getModule(dependency))
  71. );
  72. for (let i = 0; i < ids.length; i++) {
  73. if (i === 0 && ids[i] === "default") {
  74. continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo
  75. }
  76. const exportInfo = currentExportsInfo.getExportInfo(ids[i]);
  77. if (exportInfo.provided === false) {
  78. // json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided
  79. trimmedIds = ids.slice(0, i);
  80. break;
  81. }
  82. const nestedInfo = exportInfo.getNestedExportsInfo();
  83. if (!nestedInfo) {
  84. // once all nested exports are traversed, the next item is the actual import so stop there
  85. trimmedIds = ids.slice(0, i + 1);
  86. break;
  87. }
  88. currentExportsInfo = nestedInfo;
  89. }
  90. // Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule")
  91. return trimmedIds.length ? trimmedIds : ids;
  92. }