HarmonyImportDependencyParserPlugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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 HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
  12. const HarmonyExports = require("./HarmonyExports");
  13. const { ExportPresenceModes } = require("./HarmonyImportDependency");
  14. const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
  15. const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
  16. /** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */
  17. /** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */
  18. /** @typedef {import("estree").Identifier} Identifier */
  19. /** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
  20. /** @typedef {import("estree").ImportExpression} ImportExpression */
  21. /** @typedef {import("estree").MemberExpression} MemberExpression */
  22. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  23. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  24. /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
  25. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  26. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  27. /** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */
  28. /** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */
  29. /** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */
  30. const harmonySpecifierTag = Symbol("harmony import");
  31. /**
  32. * @typedef {Object} HarmonySettings
  33. * @property {string[]} ids
  34. * @property {string} source
  35. * @property {number} sourceOrder
  36. * @property {string} name
  37. * @property {boolean} await
  38. * @property {Record<string, any> | undefined} assertions
  39. */
  40. /**
  41. * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions
  42. * @returns {Record<string, any> | undefined} assertions
  43. */
  44. function getAssertions(node) {
  45. // TODO remove cast when @types/estree has been updated to import assertions
  46. const assertions = /** @type {{ assertions?: ImportAttributeNode[] }} */ (
  47. node
  48. ).assertions;
  49. if (assertions === undefined) {
  50. return undefined;
  51. }
  52. const result = {};
  53. for (const assertion of assertions) {
  54. const key =
  55. assertion.key.type === "Identifier"
  56. ? assertion.key.name
  57. : assertion.key.value;
  58. result[key] = assertion.value.value;
  59. }
  60. return result;
  61. }
  62. module.exports = class HarmonyImportDependencyParserPlugin {
  63. /**
  64. * @param {JavascriptParserOptions} options options
  65. */
  66. constructor(options) {
  67. this.exportPresenceMode =
  68. options.importExportsPresence !== undefined
  69. ? ExportPresenceModes.fromUserOption(options.importExportsPresence)
  70. : options.exportsPresence !== undefined
  71. ? ExportPresenceModes.fromUserOption(options.exportsPresence)
  72. : options.strictExportPresence
  73. ? ExportPresenceModes.ERROR
  74. : ExportPresenceModes.AUTO;
  75. this.strictThisContextOnImports = options.strictThisContextOnImports;
  76. }
  77. /**
  78. * @param {JavascriptParser} parser the parser
  79. * @returns {void}
  80. */
  81. apply(parser) {
  82. const { exportPresenceMode } = this;
  83. function getNonOptionalPart(members, membersOptionals) {
  84. let i = 0;
  85. while (i < members.length && membersOptionals[i] === false) i++;
  86. return i !== members.length ? members.slice(0, i) : members;
  87. }
  88. function getNonOptionalMemberChain(node, count) {
  89. while (count--) node = node.object;
  90. return node;
  91. }
  92. parser.hooks.isPure
  93. .for("Identifier")
  94. .tap("HarmonyImportDependencyParserPlugin", expression => {
  95. const expr = /** @type {Identifier} */ (expression);
  96. if (
  97. parser.isVariableDefined(expr.name) ||
  98. parser.getTagData(expr.name, harmonySpecifierTag)
  99. ) {
  100. return true;
  101. }
  102. });
  103. parser.hooks.import.tap(
  104. "HarmonyImportDependencyParserPlugin",
  105. (statement, source) => {
  106. parser.state.lastHarmonyImportOrder =
  107. (parser.state.lastHarmonyImportOrder || 0) + 1;
  108. const clearDep = new ConstDependency(
  109. parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
  110. ? ";"
  111. : "",
  112. /** @type {Range} */ (statement.range)
  113. );
  114. clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
  115. parser.state.module.addPresentationalDependency(clearDep);
  116. parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
  117. const assertions = getAssertions(statement);
  118. const sideEffectDep = new HarmonyImportSideEffectDependency(
  119. source,
  120. parser.state.lastHarmonyImportOrder,
  121. assertions
  122. );
  123. sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc);
  124. parser.state.module.addDependency(sideEffectDep);
  125. return true;
  126. }
  127. );
  128. parser.hooks.importSpecifier.tap(
  129. "HarmonyImportDependencyParserPlugin",
  130. (statement, source, id, name) => {
  131. const ids = id === null ? [] : [id];
  132. parser.tagVariable(name, harmonySpecifierTag, {
  133. name,
  134. source,
  135. ids,
  136. sourceOrder: parser.state.lastHarmonyImportOrder,
  137. assertions: getAssertions(statement)
  138. });
  139. return true;
  140. }
  141. );
  142. parser.hooks.binaryExpression.tap(
  143. "HarmonyImportDependencyParserPlugin",
  144. expression => {
  145. if (expression.operator !== "in") return;
  146. const leftPartEvaluated = parser.evaluateExpression(expression.left);
  147. if (leftPartEvaluated.couldHaveSideEffects()) return;
  148. const leftPart = leftPartEvaluated.asString();
  149. if (!leftPart) return;
  150. const rightPart = parser.evaluateExpression(expression.right);
  151. if (!rightPart.isIdentifier()) return;
  152. const rootInfo = rightPart.rootInfo;
  153. if (
  154. typeof rootInfo === "string" ||
  155. !rootInfo ||
  156. !rootInfo.tagInfo ||
  157. rootInfo.tagInfo.tag !== harmonySpecifierTag
  158. )
  159. return;
  160. const settings = rootInfo.tagInfo.data;
  161. const members = rightPart.getMembers();
  162. const dep = new HarmonyEvaluatedImportSpecifierDependency(
  163. settings.source,
  164. settings.sourceOrder,
  165. settings.ids.concat(members).concat([leftPart]),
  166. settings.name,
  167. /** @type {Range} */ (expression.range),
  168. settings.assertions,
  169. "in"
  170. );
  171. dep.directImport = members.length === 0;
  172. dep.asiSafe = !parser.isAsiPosition(
  173. /** @type {Range} */ (expression.range)[0]
  174. );
  175. dep.loc = /** @type {DependencyLocation} */ (expression.loc);
  176. parser.state.module.addDependency(dep);
  177. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  178. return true;
  179. }
  180. );
  181. parser.hooks.expression
  182. .for(harmonySpecifierTag)
  183. .tap("HarmonyImportDependencyParserPlugin", expr => {
  184. const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
  185. const dep = new HarmonyImportSpecifierDependency(
  186. settings.source,
  187. settings.sourceOrder,
  188. settings.ids,
  189. settings.name,
  190. /** @type {Range} */ (expr.range),
  191. exportPresenceMode,
  192. settings.assertions,
  193. []
  194. );
  195. dep.referencedPropertiesInDestructuring =
  196. parser.destructuringAssignmentPropertiesFor(expr);
  197. dep.shorthand = parser.scope.inShorthand;
  198. dep.directImport = true;
  199. dep.asiSafe = !parser.isAsiPosition(
  200. /** @type {Range} */ (expr.range)[0]
  201. );
  202. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  203. dep.call = parser.scope.inTaggedTemplateTag;
  204. parser.state.module.addDependency(dep);
  205. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  206. return true;
  207. });
  208. parser.hooks.expressionMemberChain
  209. .for(harmonySpecifierTag)
  210. .tap(
  211. "HarmonyImportDependencyParserPlugin",
  212. (expression, members, membersOptionals, memberRanges) => {
  213. const settings = /** @type {HarmonySettings} */ (
  214. parser.currentTagData
  215. );
  216. const nonOptionalMembers = getNonOptionalPart(
  217. members,
  218. membersOptionals
  219. );
  220. const ranges = memberRanges.slice(
  221. 0,
  222. memberRanges.length - (members.length - nonOptionalMembers.length)
  223. );
  224. const expr =
  225. nonOptionalMembers !== members
  226. ? getNonOptionalMemberChain(
  227. expression,
  228. members.length - nonOptionalMembers.length
  229. )
  230. : expression;
  231. const ids = settings.ids.concat(nonOptionalMembers);
  232. const dep = new HarmonyImportSpecifierDependency(
  233. settings.source,
  234. settings.sourceOrder,
  235. ids,
  236. settings.name,
  237. /** @type {Range} */ (expr.range),
  238. exportPresenceMode,
  239. settings.assertions,
  240. ranges
  241. );
  242. dep.referencedPropertiesInDestructuring =
  243. parser.destructuringAssignmentPropertiesFor(expr);
  244. dep.asiSafe = !parser.isAsiPosition(
  245. /** @type {Range} */ (expr.range)[0]
  246. );
  247. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  248. parser.state.module.addDependency(dep);
  249. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  250. return true;
  251. }
  252. );
  253. parser.hooks.callMemberChain
  254. .for(harmonySpecifierTag)
  255. .tap(
  256. "HarmonyImportDependencyParserPlugin",
  257. (expression, members, membersOptionals, memberRanges) => {
  258. const { arguments: args, callee } = expression;
  259. const settings = /** @type {HarmonySettings} */ (
  260. parser.currentTagData
  261. );
  262. const nonOptionalMembers = getNonOptionalPart(
  263. members,
  264. membersOptionals
  265. );
  266. const ranges = memberRanges.slice(
  267. 0,
  268. memberRanges.length - (members.length - nonOptionalMembers.length)
  269. );
  270. const expr =
  271. nonOptionalMembers !== members
  272. ? getNonOptionalMemberChain(
  273. callee,
  274. members.length - nonOptionalMembers.length
  275. )
  276. : callee;
  277. const ids = settings.ids.concat(nonOptionalMembers);
  278. const dep = new HarmonyImportSpecifierDependency(
  279. settings.source,
  280. settings.sourceOrder,
  281. ids,
  282. settings.name,
  283. /** @type {Range} */ (expr.range),
  284. exportPresenceMode,
  285. settings.assertions,
  286. ranges
  287. );
  288. dep.directImport = members.length === 0;
  289. dep.call = true;
  290. dep.asiSafe = !parser.isAsiPosition(
  291. /** @type {Range} */ (expr.range)[0]
  292. );
  293. // only in case when we strictly follow the spec we need a special case here
  294. dep.namespaceObjectAsContext =
  295. members.length > 0 && this.strictThisContextOnImports;
  296. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  297. parser.state.module.addDependency(dep);
  298. if (args) parser.walkExpressions(args);
  299. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  300. return true;
  301. }
  302. );
  303. const { hotAcceptCallback, hotAcceptWithoutCallback } =
  304. HotModuleReplacementPlugin.getParserHooks(parser);
  305. hotAcceptCallback.tap(
  306. "HarmonyImportDependencyParserPlugin",
  307. (expr, requests) => {
  308. if (!HarmonyExports.isEnabled(parser.state)) {
  309. // This is not a harmony module, skip it
  310. return;
  311. }
  312. const dependencies = requests.map(request => {
  313. const dep = new HarmonyAcceptImportDependency(request);
  314. dep.loc = expr.loc;
  315. parser.state.module.addDependency(dep);
  316. return dep;
  317. });
  318. if (dependencies.length > 0) {
  319. const dep = new HarmonyAcceptDependency(
  320. expr.range,
  321. dependencies,
  322. true
  323. );
  324. dep.loc = expr.loc;
  325. parser.state.module.addDependency(dep);
  326. }
  327. }
  328. );
  329. hotAcceptWithoutCallback.tap(
  330. "HarmonyImportDependencyParserPlugin",
  331. (expr, requests) => {
  332. if (!HarmonyExports.isEnabled(parser.state)) {
  333. // This is not a harmony module, skip it
  334. return;
  335. }
  336. const dependencies = requests.map(request => {
  337. const dep = new HarmonyAcceptImportDependency(request);
  338. dep.loc = expr.loc;
  339. parser.state.module.addDependency(dep);
  340. return dep;
  341. });
  342. if (dependencies.length > 0) {
  343. const dep = new HarmonyAcceptDependency(
  344. expr.range,
  345. dependencies,
  346. false
  347. );
  348. dep.loc = expr.loc;
  349. parser.state.module.addDependency(dep);
  350. }
  351. }
  352. );
  353. }
  354. };
  355. module.exports.harmonySpecifierTag = harmonySpecifierTag;
  356. module.exports.getAssertions = getAssertions;