HarmonyExportDependencyParserPlugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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("../errors/WebpackError");
  8. const { getImportAttributes } = require("../javascript/JavascriptParser");
  9. const { CONST_BINDING_TAG } = require("../optimize/ConstExportsPlugin");
  10. const { toInlinedValue } = require("../optimize/InlineExports");
  11. const { getInnerGraphUtils } = require("../optimize/InnerGraph");
  12. const ConstDependency = require("./ConstDependency");
  13. const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
  14. const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
  15. const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
  16. const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
  17. const { ExportPresenceModes } = require("./HarmonyImportDependency");
  18. const {
  19. harmonySpecifierTag
  20. } = require("./HarmonyImportDependencyParserPlugin");
  21. const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
  22. const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
  23. /**
  24. * @import {
  25. * Expression,
  26. * ClassDeclaration,
  27. * FunctionDeclaration
  28. * } from "estree"
  29. */
  30. /**
  31. * @import {
  32. * JavascriptParserOptions
  33. * } from "../../declarations/WebpackOptions"
  34. */
  35. /** @import JavascriptParser, { Range } from "../javascript/JavascriptParser" */
  36. /** @import { HarmonySettings } from "./HarmonyImportDependencyParserPlugin" */
  37. /** @import { CompatibilitySettings } from "../CompatibilityPlugin" */
  38. const { HarmonyStarExportsList } = HarmonyExportImportedSpecifierDependency;
  39. const PLUGIN_NAME = "HarmonyExportDependencyParserPlugin";
  40. module.exports = class HarmonyExportDependencyParserPlugin {
  41. /**
  42. * Creates an instance of HarmonyExportDependencyParserPlugin.
  43. * @param {JavascriptParserOptions} options options
  44. */
  45. constructor(options) {
  46. /** @type {JavascriptParserOptions} */
  47. this.options = options;
  48. this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
  49. options.reexportExportsPresence,
  50. options
  51. );
  52. }
  53. /**
  54. * Applies the plugin by registering its hooks on the compiler.
  55. * @param {JavascriptParser} parser the parser
  56. * @returns {void}
  57. */
  58. apply(parser) {
  59. const { exportPresenceMode } = this;
  60. const getImportPhase = createGetImportPhase(
  61. this.options.deferImport,
  62. false
  63. );
  64. parser.hooks.export.tap(PLUGIN_NAME, (statement) => {
  65. const dep = new HarmonyExportHeaderDependency(
  66. statement.declaration && statement.declaration.range,
  67. /** @type {Range} */ (statement.range)
  68. );
  69. dep.setLocWithIndex(parser.getLocation(statement), -1);
  70. parser.state.module.addPresentationalDependency(dep);
  71. return true;
  72. });
  73. parser.hooks.exportImport.tap(PLUGIN_NAME, (statement, source) => {
  74. parser.state.lastHarmonyImportOrder =
  75. (parser.state.lastHarmonyImportOrder || 0) + 1;
  76. const clearDep = new ConstDependency(
  77. "",
  78. /** @type {Range} */ (statement.range)
  79. );
  80. clearDep.setLocWithIndex(parser.getLocation(statement), -1);
  81. parser.state.module.addPresentationalDependency(clearDep);
  82. const phase = getImportPhase(parser, statement);
  83. if (phase && ImportPhaseUtils.isDefer(phase)) {
  84. const error = new WebpackError(
  85. "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 };"
  86. );
  87. error.loc = parser.getLocation(statement) || undefined;
  88. parser.state.current.addError(error);
  89. }
  90. const sideEffectDep = new HarmonyImportSideEffectDependency(
  91. /** @type {string} */ (source),
  92. parser.state.lastHarmonyImportOrder,
  93. phase,
  94. getImportAttributes(statement)
  95. );
  96. sideEffectDep.setLocWithIndex(parser.getLocation(statement), -1);
  97. // lazy barrel: defer at parse time when the importing module is side-effect-free
  98. const moduleFactoryMeta = parser.state.module.factoryMeta;
  99. if (moduleFactoryMeta !== undefined && moduleFactoryMeta.sideEffectFree) {
  100. sideEffectDep.setLazy(true);
  101. }
  102. parser.state.current.addDependency(sideEffectDep);
  103. return true;
  104. });
  105. // `export default <namespace binding>` is the same export as
  106. // `export { ns as default }`, so it is claimed here and rewritten into the
  107. // same re-export. Stage -10 runs before the inner-graph tap, which would
  108. // otherwise wrap the identifier in a pure-expression guard whose insertions
  109. // outlive the statement this removes.
  110. parser.hooks.statement.tap(
  111. { name: PLUGIN_NAME, stage: -10 },
  112. (statement) => {
  113. if (
  114. statement.type !== "ExportDefaultDeclaration" ||
  115. statement.declaration.type !== "Identifier"
  116. ) {
  117. return;
  118. }
  119. const name = statement.declaration.name;
  120. const settings =
  121. /** @type {HarmonySettings | undefined} */
  122. (parser.getTagData(name, harmonySpecifierTag));
  123. if (!settings || settings.ids.length !== 0) return;
  124. if (ImportPhaseUtils.isDefer(settings.phase)) return;
  125. const harmonyNamedExports = (parser.state.harmonyNamedExports =
  126. parser.state.harmonyNamedExports || new Set());
  127. harmonyNamedExports.add("default");
  128. getInnerGraphUtils(parser.state.compilation).addVariableUsage(
  129. parser,
  130. name,
  131. "default"
  132. );
  133. const dep = new HarmonyExportImportedSpecifierDependency(
  134. settings.source,
  135. settings.sourceOrder,
  136. settings.ids,
  137. "default",
  138. harmonyNamedExports,
  139. null,
  140. exportPresenceMode,
  141. null,
  142. settings.phase,
  143. settings.attributes
  144. );
  145. // lazy barrel: defer the re-export at parse time when the importing module is side-effect-free
  146. const moduleFactoryMeta = parser.state.module.factoryMeta;
  147. if (
  148. moduleFactoryMeta !== undefined &&
  149. moduleFactoryMeta.sideEffectFree
  150. ) {
  151. dep.setLazy(true);
  152. }
  153. dep.setLocWithIndex(parser.getLocation(statement), -1);
  154. parser.state.current.addDependency(dep);
  155. // no range erases the whole statement, as for `export { ns as default }`
  156. const headerDep = new HarmonyExportHeaderDependency(
  157. undefined,
  158. /** @type {Range} */ (statement.range)
  159. );
  160. headerDep.setLocWithIndex(parser.getLocation(statement), -1);
  161. parser.state.module.addPresentationalDependency(headerDep);
  162. return true;
  163. }
  164. );
  165. parser.hooks.exportExpression.tap(PLUGIN_NAME, (statement, node) => {
  166. const isFunctionDeclaration = node.type === "FunctionDeclaration";
  167. const exprRange = /** @type {Range} */ (node.range);
  168. const statementRange = /** @type {Range} */ (statement.range);
  169. const comments = parser.getComments([statementRange[0], exprRange[0]]);
  170. const constValue = node.type.endsWith("Declaration")
  171. ? undefined
  172. : toInlinedValue(
  173. parser.evaluateExpression(/** @type {Expression} */ (node))
  174. );
  175. const dep = new HarmonyExportExpressionDependency(
  176. exprRange,
  177. statementRange,
  178. comments
  179. .map((c) => {
  180. switch (c.type) {
  181. case "Block":
  182. return `/*${c.value}*/`;
  183. case "Line":
  184. return `//${c.value}\n`;
  185. }
  186. return "";
  187. })
  188. .join(""),
  189. node.type.endsWith("Declaration") &&
  190. /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id
  191. ? /** @type {FunctionDeclaration | ClassDeclaration} */
  192. (node).id.name
  193. : isFunctionDeclaration
  194. ? {
  195. range: [
  196. exprRange[0],
  197. node.params.length > 0
  198. ? /** @type {Range} */ (node.params[0].range)[0]
  199. : /** @type {Range} */ (node.body.range)[0]
  200. ],
  201. prefix: `${node.async ? "async " : ""}function${
  202. node.generator ? "*" : ""
  203. } `,
  204. suffix: `(${node.params.length > 0 ? "" : ") "}`
  205. }
  206. : undefined,
  207. constValue
  208. );
  209. dep.isAnonymousDefault =
  210. this.options.anonymousDefaultExportName !== false &&
  211. (node.type === "ArrowFunctionExpression" ||
  212. ((node.type === "FunctionDeclaration" ||
  213. node.type === "FunctionExpression" ||
  214. node.type === "ClassDeclaration" ||
  215. node.type === "ClassExpression") &&
  216. !node.id));
  217. dep.setLocWithIndex(parser.getLocation(statement), -1);
  218. parser.state.current.addDependency(dep);
  219. getInnerGraphUtils(parser.state.compilation).addVariableUsage(
  220. parser,
  221. node.type.endsWith("Declaration") &&
  222. /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id
  223. ? /** @type {FunctionDeclaration | ClassDeclaration} */ (node).id.name
  224. : "*default*",
  225. "default"
  226. );
  227. return true;
  228. });
  229. parser.hooks.exportSpecifier.tap(
  230. PLUGIN_NAME,
  231. (statement, id, name, idx) => {
  232. // CompatibilityPlugin may change exports name
  233. // not handle re-export or import then export situation as current CompatibilityPlugin only
  234. // rename symbol in declaration module, not change exported symbol
  235. const variable = parser.getTagData(
  236. id,
  237. CompatibilityPlugin.nestedWebpackIdentifierTag
  238. );
  239. if (variable && /** @type {CompatibilitySettings} */ (variable).name) {
  240. // CompatibilityPlugin changes exports to a new name, should updates exports name
  241. id = /** @type {CompatibilitySettings} */ (variable).name;
  242. }
  243. const settings =
  244. /** @type {HarmonySettings} */
  245. (parser.getTagData(id, harmonySpecifierTag));
  246. const harmonyNamedExports = (parser.state.harmonyNamedExports =
  247. parser.state.harmonyNamedExports || new Set());
  248. harmonyNamedExports.add(name);
  249. getInnerGraphUtils(parser.state.compilation).addVariableUsage(
  250. parser,
  251. id,
  252. name
  253. );
  254. const tagData = settings
  255. ? undefined
  256. : /** @type {{ value?: import("../optimize/InlineExports").InlinedValue } | undefined} */
  257. (parser.getTagData(id, CONST_BINDING_TAG));
  258. const dep = settings
  259. ? new HarmonyExportImportedSpecifierDependency(
  260. settings.source,
  261. settings.sourceOrder,
  262. settings.ids,
  263. name,
  264. harmonyNamedExports,
  265. null,
  266. exportPresenceMode,
  267. null,
  268. settings.phase,
  269. settings.attributes
  270. )
  271. : new HarmonyExportSpecifierDependency(
  272. id,
  273. name,
  274. tagData ? tagData.value || null : undefined
  275. );
  276. // lazy barrel: defer the re-export at parse time when the importing module is side-effect-free
  277. const moduleFactoryMeta = parser.state.module.factoryMeta;
  278. if (
  279. settings &&
  280. moduleFactoryMeta !== undefined &&
  281. moduleFactoryMeta.sideEffectFree
  282. ) {
  283. dep.setLazy(true);
  284. }
  285. dep.setLocWithIndex(
  286. parser.getLocation(statement),
  287. /** @type {number} */ (idx)
  288. );
  289. const isAsiSafe = !parser.isAsiPosition(
  290. /** @type {Range} */
  291. (statement.range)[0]
  292. );
  293. if (!isAsiSafe) {
  294. parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]);
  295. }
  296. parser.state.current.addDependency(dep);
  297. return true;
  298. }
  299. );
  300. parser.hooks.exportImportSpecifier.tap(
  301. PLUGIN_NAME,
  302. (statement, source, id, name, idx) => {
  303. const harmonyNamedExports = (parser.state.harmonyNamedExports =
  304. parser.state.harmonyNamedExports || new Set());
  305. /** @type {InstanceType<HarmonyStarExportsList> | null} */
  306. let harmonyStarExports = null;
  307. if (name) {
  308. harmonyNamedExports.add(name);
  309. } else {
  310. harmonyStarExports = parser.state.harmonyStarExports =
  311. parser.state.harmonyStarExports || new HarmonyStarExportsList();
  312. }
  313. const attributes = getImportAttributes(statement);
  314. const dep = new HarmonyExportImportedSpecifierDependency(
  315. /** @type {string} */
  316. (source),
  317. /** @type {number} */
  318. (parser.state.lastHarmonyImportOrder),
  319. id ? [id] : [],
  320. name,
  321. harmonyNamedExports,
  322. // eslint-disable-next-line unicorn/prefer-spread
  323. harmonyStarExports && harmonyStarExports.slice(),
  324. exportPresenceMode,
  325. harmonyStarExports,
  326. getImportPhase(parser, statement),
  327. attributes
  328. );
  329. if (harmonyStarExports) {
  330. harmonyStarExports.push(dep);
  331. }
  332. // lazy barrel: defer the re-export at parse time when the importing module is side-effect-free
  333. const moduleFactoryMeta = parser.state.module.factoryMeta;
  334. if (
  335. moduleFactoryMeta !== undefined &&
  336. moduleFactoryMeta.sideEffectFree
  337. ) {
  338. dep.setLazy(true);
  339. }
  340. dep.setLocWithIndex(
  341. parser.getLocation(statement),
  342. /** @type {number} */ (idx)
  343. );
  344. const isAsiSafe = !parser.isAsiPosition(
  345. /** @type {Range} */
  346. (statement.range)[0]
  347. );
  348. if (!isAsiSafe) {
  349. parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]);
  350. }
  351. parser.state.current.addDependency(dep);
  352. return true;
  353. }
  354. );
  355. }
  356. };