HarmonyExportDependencyParserPlugin.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const CompatibilityPlugin = require("../CompatibilityPlugin");
  7. const WebpackError = require("../WebpackError");
  8. const { getImportAttributes } = require("../javascript/JavascriptParser");
  9. const InnerGraph = require("../optimize/InnerGraph");
  10. const ConstDependency = require("./ConstDependency");
  11. const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
  12. const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
  13. const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
  14. const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
  15. const { ExportPresenceModes } = require("./HarmonyImportDependency");
  16. const {
  17. harmonySpecifierTag
  18. } = require("./HarmonyImportDependencyParserPlugin");
  19. const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
  20. const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
  21. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  22. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  23. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  24. /** @typedef {import("../javascript/JavascriptParser").ClassDeclaration} ClassDeclaration */
  25. /** @typedef {import("../javascript/JavascriptParser").FunctionDeclaration} FunctionDeclaration */
  26. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  27. /** @typedef {import("./HarmonyImportDependencyParserPlugin").HarmonySettings} HarmonySettings */
  28. /** @typedef {import("../CompatibilityPlugin").CompatibilitySettings} CompatibilitySettings */
  29. const { HarmonyStarExportsList } = HarmonyExportImportedSpecifierDependency;
  30. const PLUGIN_NAME = "HarmonyExportDependencyParserPlugin";
  31. module.exports = class HarmonyExportDependencyParserPlugin {
  32. /**
  33. * Creates an instance of HarmonyExportDependencyParserPlugin.
  34. * @param {JavascriptParserOptions} options options
  35. */
  36. constructor(options) {
  37. this.options = options;
  38. this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
  39. options.reexportExportsPresence,
  40. options
  41. );
  42. }
  43. /**
  44. * Applies the plugin by registering its hooks on the compiler.
  45. * @param {JavascriptParser} parser the parser
  46. * @returns {void}
  47. */
  48. apply(parser) {
  49. const { exportPresenceMode } = this;
  50. const getImportPhase = createGetImportPhase(
  51. this.options.deferImport,
  52. false
  53. );
  54. parser.hooks.export.tap(PLUGIN_NAME, (statement) => {
  55. const dep = new HarmonyExportHeaderDependency(
  56. /** @type {Range | false} */ (
  57. statement.declaration && statement.declaration.range
  58. ),
  59. /** @type {Range} */ (statement.range)
  60. );
  61. dep.loc = Object.create(
  62. /** @type {DependencyLocation} */ (statement.loc)
  63. );
  64. dep.loc.index = -1;
  65. parser.state.module.addPresentationalDependency(dep);
  66. return true;
  67. });
  68. parser.hooks.exportImport.tap(PLUGIN_NAME, (statement, source) => {
  69. parser.state.lastHarmonyImportOrder =
  70. (parser.state.lastHarmonyImportOrder || 0) + 1;
  71. const clearDep = new ConstDependency(
  72. "",
  73. /** @type {Range} */ (statement.range)
  74. );
  75. clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
  76. clearDep.loc.index = -1;
  77. parser.state.module.addPresentationalDependency(clearDep);
  78. const phase = getImportPhase(parser, statement);
  79. if (phase && ImportPhaseUtils.isDefer(phase)) {
  80. const error = new WebpackError(
  81. "Deferred re-export (`export defer * as namespace from '...'`) is not a part of the Import Defer proposal.\nUse the following code instead:\n import defer * as namespace from '...';\n export { namespace };"
  82. );
  83. error.loc = statement.loc || undefined;
  84. parser.state.current.addError(error);
  85. }
  86. const sideEffectDep = new HarmonyImportSideEffectDependency(
  87. /** @type {string} */ (source),
  88. parser.state.lastHarmonyImportOrder,
  89. phase,
  90. getImportAttributes(statement)
  91. );
  92. sideEffectDep.loc = Object.create(
  93. /** @type {DependencyLocation} */ (statement.loc)
  94. );
  95. sideEffectDep.loc.index = -1;
  96. parser.state.current.addDependency(sideEffectDep);
  97. return true;
  98. });
  99. parser.hooks.exportExpression.tap(PLUGIN_NAME, (statement, node) => {
  100. const isFunctionDeclaration = node.type === "FunctionDeclaration";
  101. const exprRange = /** @type {Range} */ (node.range);
  102. const statementRange = /** @type {Range} */ (statement.range);
  103. const comments = parser.getComments([statementRange[0], exprRange[0]]);
  104. const dep = new HarmonyExportExpressionDependency(
  105. exprRange,
  106. statementRange,
  107. comments
  108. .map((c) => {
  109. switch (c.type) {
  110. case "Block":
  111. return `/*${c.value}*/`;
  112. case "Line":
  113. return `//${c.value}\n`;
  114. }
  115. return "";
  116. })
  117. .join(""),
  118. node.type.endsWith("Declaration") &&
  119. /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id
  120. ? /** @type {FunctionDeclaration | ClassDeclaration} */
  121. (node).id.name
  122. : isFunctionDeclaration
  123. ? {
  124. range: [
  125. exprRange[0],
  126. node.params.length > 0
  127. ? /** @type {Range} */ (node.params[0].range)[0]
  128. : /** @type {Range} */ (node.body.range)[0]
  129. ],
  130. prefix: `${node.async ? "async " : ""}function${
  131. node.generator ? "*" : ""
  132. } `,
  133. suffix: `(${node.params.length > 0 ? "" : ") "}`
  134. }
  135. : undefined
  136. );
  137. dep.isAnonymousDefault =
  138. node.type === "ArrowFunctionExpression" ||
  139. ((node.type === "FunctionDeclaration" ||
  140. node.type === "FunctionExpression" ||
  141. node.type === "ClassDeclaration" ||
  142. node.type === "ClassExpression") &&
  143. !node.id);
  144. dep.loc = Object.create(
  145. /** @type {DependencyLocation} */ (statement.loc)
  146. );
  147. dep.loc.index = -1;
  148. parser.state.current.addDependency(dep);
  149. InnerGraph.addVariableUsage(
  150. parser,
  151. node.type.endsWith("Declaration") &&
  152. /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id
  153. ? /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id.name
  154. : "*default*",
  155. "default"
  156. );
  157. return true;
  158. });
  159. parser.hooks.exportSpecifier.tap(
  160. PLUGIN_NAME,
  161. (statement, id, name, idx) => {
  162. // CompatibilityPlugin may change exports name
  163. // not handle re-export or import then export situation as current CompatibilityPlugin only
  164. // rename symbol in declaration module, not change exported symbol
  165. const variable = parser.getTagData(
  166. id,
  167. CompatibilityPlugin.nestedWebpackIdentifierTag
  168. );
  169. if (variable && /** @type {CompatibilitySettings} */ (variable).name) {
  170. // CompatibilityPlugin changes exports to a new name, should updates exports name
  171. id = /** @type {CompatibilitySettings} */ (variable).name;
  172. }
  173. const settings =
  174. /** @type {HarmonySettings} */
  175. (parser.getTagData(id, harmonySpecifierTag));
  176. const harmonyNamedExports = (parser.state.harmonyNamedExports =
  177. parser.state.harmonyNamedExports || new Set());
  178. harmonyNamedExports.add(name);
  179. InnerGraph.addVariableUsage(parser, id, name);
  180. const dep = settings
  181. ? new HarmonyExportImportedSpecifierDependency(
  182. settings.source,
  183. settings.sourceOrder,
  184. settings.ids,
  185. name,
  186. harmonyNamedExports,
  187. null,
  188. exportPresenceMode,
  189. null,
  190. settings.phase,
  191. settings.attributes
  192. )
  193. : new HarmonyExportSpecifierDependency(id, name);
  194. dep.loc = Object.create(
  195. /** @type {DependencyLocation} */ (statement.loc)
  196. );
  197. dep.loc.index = idx;
  198. const isAsiSafe = !parser.isAsiPosition(
  199. /** @type {Range} */
  200. (statement.range)[0]
  201. );
  202. if (!isAsiSafe) {
  203. parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]);
  204. }
  205. parser.state.current.addDependency(dep);
  206. return true;
  207. }
  208. );
  209. parser.hooks.exportImportSpecifier.tap(
  210. PLUGIN_NAME,
  211. (statement, source, id, name, idx) => {
  212. const harmonyNamedExports = (parser.state.harmonyNamedExports =
  213. parser.state.harmonyNamedExports || new Set());
  214. /** @type {InstanceType<HarmonyStarExportsList> | null} */
  215. let harmonyStarExports = null;
  216. if (name) {
  217. harmonyNamedExports.add(name);
  218. } else {
  219. harmonyStarExports = parser.state.harmonyStarExports =
  220. parser.state.harmonyStarExports || new HarmonyStarExportsList();
  221. }
  222. const attributes = getImportAttributes(statement);
  223. const dep = new HarmonyExportImportedSpecifierDependency(
  224. /** @type {string} */
  225. (source),
  226. /** @type {number} */
  227. (parser.state.lastHarmonyImportOrder),
  228. id ? [id] : [],
  229. name,
  230. harmonyNamedExports,
  231. // eslint-disable-next-line unicorn/prefer-spread
  232. harmonyStarExports && harmonyStarExports.slice(),
  233. exportPresenceMode,
  234. harmonyStarExports,
  235. getImportPhase(parser, statement),
  236. attributes
  237. );
  238. if (harmonyStarExports) {
  239. harmonyStarExports.push(dep);
  240. }
  241. dep.loc = Object.create(
  242. /** @type {DependencyLocation} */ (statement.loc)
  243. );
  244. dep.loc.index = idx;
  245. const isAsiSafe = !parser.isAsiPosition(
  246. /** @type {Range} */
  247. (statement.range)[0]
  248. );
  249. if (!isAsiSafe) {
  250. parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]);
  251. }
  252. parser.state.current.addDependency(dep);
  253. return true;
  254. }
  255. );
  256. }
  257. };