HarmonyImportDependencyParserPlugin.js 13 KB

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