HarmonyExportDependencyParserPlugin.js 8.1 KB

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