HarmonyExportDependencyParserPlugin.js 8.2 KB

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