HarmonyImportDependencyParserPlugin.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
  7. const InnerGraph = require("../optimize/InnerGraph");
  8. const ConstDependency = require("./ConstDependency");
  9. const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
  10. const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
  11. const HarmonyExports = require("./HarmonyExports");
  12. const { ExportPresenceModes } = require("./HarmonyImportDependency");
  13. const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
  14. const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
  15. /** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */
  16. /** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */
  17. /** @typedef {import("estree").Identifier} Identifier */
  18. /** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
  19. /** @typedef {import("estree").ImportExpression} ImportExpression */
  20. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  21. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  22. /** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */
  23. /** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */
  24. /** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */
  25. const harmonySpecifierTag = Symbol("harmony import");
  26. /**
  27. * @typedef {Object} HarmonySettings
  28. * @property {string[]} ids
  29. * @property {string} source
  30. * @property {number} sourceOrder
  31. * @property {string} name
  32. * @property {boolean} await
  33. * @property {Record<string, any> | undefined} assertions
  34. */
  35. /**
  36. * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions
  37. * @returns {Record<string, any> | undefined} assertions
  38. */
  39. function getAssertions(node) {
  40. // TODO remove cast when @types/estree has been updated to import assertions
  41. const assertions = /** @type {{ assertions?: ImportAttributeNode[] }} */ (
  42. node
  43. ).assertions;
  44. if (assertions === undefined) {
  45. return undefined;
  46. }
  47. const result = {};
  48. for (const assertion of assertions) {
  49. const key =
  50. assertion.key.type === "Identifier"
  51. ? assertion.key.name
  52. : assertion.key.value;
  53. result[key] = assertion.value.value;
  54. }
  55. return result;
  56. }
  57. module.exports = class HarmonyImportDependencyParserPlugin {
  58. /**
  59. * @param {JavascriptParserOptions} options options
  60. */
  61. constructor(options) {
  62. this.exportPresenceMode =
  63. options.importExportsPresence !== undefined
  64. ? ExportPresenceModes.fromUserOption(options.importExportsPresence)
  65. : options.exportsPresence !== undefined
  66. ? ExportPresenceModes.fromUserOption(options.exportsPresence)
  67. : options.strictExportPresence
  68. ? ExportPresenceModes.ERROR
  69. : ExportPresenceModes.AUTO;
  70. this.strictThisContextOnImports = options.strictThisContextOnImports;
  71. }
  72. /**
  73. * @param {JavascriptParser} parser the parser
  74. * @returns {void}
  75. */
  76. apply(parser) {
  77. const { exportPresenceMode } = this;
  78. parser.hooks.isPure
  79. .for("Identifier")
  80. .tap("HarmonyImportDependencyParserPlugin", expression => {
  81. const expr = /** @type {Identifier} */ (expression);
  82. if (
  83. parser.isVariableDefined(expr.name) ||
  84. parser.getTagData(expr.name, harmonySpecifierTag)
  85. ) {
  86. return true;
  87. }
  88. });
  89. parser.hooks.import.tap(
  90. "HarmonyImportDependencyParserPlugin",
  91. (statement, source) => {
  92. parser.state.lastHarmonyImportOrder =
  93. (parser.state.lastHarmonyImportOrder || 0) + 1;
  94. const clearDep = new ConstDependency(
  95. parser.isAsiPosition(statement.range[0]) ? ";" : "",
  96. statement.range
  97. );
  98. clearDep.loc = statement.loc;
  99. parser.state.module.addPresentationalDependency(clearDep);
  100. parser.unsetAsiPosition(statement.range[1]);
  101. const assertions = getAssertions(statement);
  102. const sideEffectDep = new HarmonyImportSideEffectDependency(
  103. source,
  104. parser.state.lastHarmonyImportOrder,
  105. assertions
  106. );
  107. sideEffectDep.loc = statement.loc;
  108. parser.state.module.addDependency(sideEffectDep);
  109. return true;
  110. }
  111. );
  112. parser.hooks.importSpecifier.tap(
  113. "HarmonyImportDependencyParserPlugin",
  114. (statement, source, id, name) => {
  115. const ids = id === null ? [] : [id];
  116. parser.tagVariable(name, harmonySpecifierTag, {
  117. name,
  118. source,
  119. ids,
  120. sourceOrder: parser.state.lastHarmonyImportOrder,
  121. assertions: getAssertions(statement)
  122. });
  123. return true;
  124. }
  125. );
  126. parser.hooks.expression
  127. .for(harmonySpecifierTag)
  128. .tap("HarmonyImportDependencyParserPlugin", expr => {
  129. const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
  130. const dep = new HarmonyImportSpecifierDependency(
  131. settings.source,
  132. settings.sourceOrder,
  133. settings.ids,
  134. settings.name,
  135. expr.range,
  136. exportPresenceMode,
  137. settings.assertions
  138. );
  139. dep.shorthand = parser.scope.inShorthand;
  140. dep.directImport = true;
  141. dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
  142. dep.loc = expr.loc;
  143. parser.state.module.addDependency(dep);
  144. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  145. return true;
  146. });
  147. parser.hooks.expressionMemberChain
  148. .for(harmonySpecifierTag)
  149. .tap("HarmonyImportDependencyParserPlugin", (expr, members) => {
  150. const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
  151. const ids = settings.ids.concat(members);
  152. const dep = new HarmonyImportSpecifierDependency(
  153. settings.source,
  154. settings.sourceOrder,
  155. ids,
  156. settings.name,
  157. expr.range,
  158. exportPresenceMode,
  159. settings.assertions
  160. );
  161. dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
  162. dep.loc = expr.loc;
  163. parser.state.module.addDependency(dep);
  164. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  165. return true;
  166. });
  167. parser.hooks.callMemberChain
  168. .for(harmonySpecifierTag)
  169. .tap("HarmonyImportDependencyParserPlugin", (expr, members) => {
  170. const { arguments: args, callee } = expr;
  171. const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
  172. const ids = settings.ids.concat(members);
  173. const dep = new HarmonyImportSpecifierDependency(
  174. settings.source,
  175. settings.sourceOrder,
  176. ids,
  177. settings.name,
  178. callee.range,
  179. exportPresenceMode,
  180. settings.assertions
  181. );
  182. dep.directImport = members.length === 0;
  183. dep.call = true;
  184. dep.asiSafe = !parser.isAsiPosition(callee.range[0]);
  185. // only in case when we strictly follow the spec we need a special case here
  186. dep.namespaceObjectAsContext =
  187. members.length > 0 && this.strictThisContextOnImports;
  188. dep.loc = callee.loc;
  189. parser.state.module.addDependency(dep);
  190. if (args) parser.walkExpressions(args);
  191. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  192. return true;
  193. });
  194. const { hotAcceptCallback, hotAcceptWithoutCallback } =
  195. HotModuleReplacementPlugin.getParserHooks(parser);
  196. hotAcceptCallback.tap(
  197. "HarmonyImportDependencyParserPlugin",
  198. (expr, requests) => {
  199. if (!HarmonyExports.isEnabled(parser.state)) {
  200. // This is not a harmony module, skip it
  201. return;
  202. }
  203. const dependencies = requests.map(request => {
  204. const dep = new HarmonyAcceptImportDependency(request);
  205. dep.loc = expr.loc;
  206. parser.state.module.addDependency(dep);
  207. return dep;
  208. });
  209. if (dependencies.length > 0) {
  210. const dep = new HarmonyAcceptDependency(
  211. expr.range,
  212. dependencies,
  213. true
  214. );
  215. dep.loc = expr.loc;
  216. parser.state.module.addDependency(dep);
  217. }
  218. }
  219. );
  220. hotAcceptWithoutCallback.tap(
  221. "HarmonyImportDependencyParserPlugin",
  222. (expr, requests) => {
  223. if (!HarmonyExports.isEnabled(parser.state)) {
  224. // This is not a harmony module, skip it
  225. return;
  226. }
  227. const dependencies = requests.map(request => {
  228. const dep = new HarmonyAcceptImportDependency(request);
  229. dep.loc = expr.loc;
  230. parser.state.module.addDependency(dep);
  231. return dep;
  232. });
  233. if (dependencies.length > 0) {
  234. const dep = new HarmonyAcceptDependency(
  235. expr.range,
  236. dependencies,
  237. false
  238. );
  239. dep.loc = expr.loc;
  240. parser.state.module.addDependency(dep);
  241. }
  242. }
  243. );
  244. }
  245. };
  246. module.exports.harmonySpecifierTag = harmonySpecifierTag;
  247. module.exports.getAssertions = getAssertions;