ImportMetaContextDependencyParserPlugin.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const WebpackError = require("../WebpackError");
  7. const {
  8. evaluateToIdentifier
  9. } = require("../javascript/JavascriptParserHelpers");
  10. const ImportMetaContextDependency = require("./ImportMetaContextDependency");
  11. /** @typedef {import("estree").Expression} Expression */
  12. /** @typedef {import("estree").ObjectExpression} ObjectExpression */
  13. /** @typedef {import("estree").Property} Property */
  14. /** @typedef {import("estree").Identifier} Identifier */
  15. /** @typedef {import("estree").SourceLocation} SourceLocation */
  16. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  17. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  18. /** @typedef {import("../ContextModule").ContextModuleOptions} ContextModuleOptions */
  19. /** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
  20. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  21. /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
  22. /** @typedef {Pick<ContextModuleOptions, 'mode'|'recursive'|'regExp'|'include'|'exclude'|'chunkName'>&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */
  23. /**
  24. * @param {Property} prop property
  25. * @param {string} expect except message
  26. * @returns {WebpackError} error
  27. */
  28. function createPropertyParseError(prop, expect) {
  29. return createError(
  30. `Parsing import.meta.webpackContext options failed. Unknown value for property ${JSON.stringify(
  31. /** @type {Identifier} */
  32. (prop.key).name
  33. )}, expected type ${expect}.`,
  34. /** @type {DependencyLocation} */
  35. (prop.value.loc)
  36. );
  37. }
  38. /**
  39. * @param {string} msg message
  40. * @param {DependencyLocation} loc location
  41. * @returns {WebpackError} error
  42. */
  43. function createError(msg, loc) {
  44. const error = new WebpackError(msg);
  45. error.name = "ImportMetaContextError";
  46. error.loc = loc;
  47. return error;
  48. }
  49. const PLUGIN_NAME = "ImportMetaContextDependencyParserPlugin";
  50. module.exports = class ImportMetaContextDependencyParserPlugin {
  51. /**
  52. * @param {JavascriptParser} parser the parser
  53. * @returns {void}
  54. */
  55. apply(parser) {
  56. parser.hooks.evaluateIdentifier
  57. .for("import.meta.webpackContext")
  58. .tap(PLUGIN_NAME, expr =>
  59. evaluateToIdentifier(
  60. "import.meta.webpackContext",
  61. "import.meta",
  62. () => ["webpackContext"],
  63. true
  64. )(expr)
  65. );
  66. parser.hooks.call
  67. .for("import.meta.webpackContext")
  68. .tap(PLUGIN_NAME, expr => {
  69. if (expr.arguments.length < 1 || expr.arguments.length > 2) return;
  70. const [directoryNode, optionsNode] = expr.arguments;
  71. if (optionsNode && optionsNode.type !== "ObjectExpression") return;
  72. const requestExpr = parser.evaluateExpression(
  73. /** @type {Expression} */ (directoryNode)
  74. );
  75. if (!requestExpr.isString()) return;
  76. const request = /** @type {string} */ (requestExpr.string);
  77. const errors = [];
  78. let regExp = /^\.\/.*$/;
  79. let recursive = true;
  80. /** @type {ContextModuleOptions["mode"]} */
  81. let mode = "sync";
  82. /** @type {ContextModuleOptions["include"]} */
  83. let include;
  84. /** @type {ContextModuleOptions["exclude"]} */
  85. let exclude;
  86. /** @type {RawChunkGroupOptions} */
  87. const groupOptions = {};
  88. /** @type {ContextModuleOptions["chunkName"]} */
  89. let chunkName;
  90. /** @type {ContextModuleOptions["referencedExports"]} */
  91. let exports;
  92. if (optionsNode) {
  93. for (const prop of /** @type {ObjectExpression} */ (optionsNode)
  94. .properties) {
  95. if (prop.type !== "Property" || prop.key.type !== "Identifier") {
  96. errors.push(
  97. createError(
  98. "Parsing import.meta.webpackContext options failed.",
  99. /** @type {DependencyLocation} */
  100. (optionsNode.loc)
  101. )
  102. );
  103. break;
  104. }
  105. switch (prop.key.name) {
  106. case "regExp": {
  107. const regExpExpr = parser.evaluateExpression(
  108. /** @type {Expression} */ (prop.value)
  109. );
  110. if (!regExpExpr.isRegExp()) {
  111. errors.push(createPropertyParseError(prop, "RegExp"));
  112. } else {
  113. regExp = /** @type {RegExp} */ (regExpExpr.regExp);
  114. }
  115. break;
  116. }
  117. case "include": {
  118. const regExpExpr = parser.evaluateExpression(
  119. /** @type {Expression} */ (prop.value)
  120. );
  121. if (!regExpExpr.isRegExp()) {
  122. errors.push(createPropertyParseError(prop, "RegExp"));
  123. } else {
  124. include = regExpExpr.regExp;
  125. }
  126. break;
  127. }
  128. case "exclude": {
  129. const regExpExpr = parser.evaluateExpression(
  130. /** @type {Expression} */ (prop.value)
  131. );
  132. if (!regExpExpr.isRegExp()) {
  133. errors.push(createPropertyParseError(prop, "RegExp"));
  134. } else {
  135. exclude = regExpExpr.regExp;
  136. }
  137. break;
  138. }
  139. case "mode": {
  140. const modeExpr = parser.evaluateExpression(
  141. /** @type {Expression} */ (prop.value)
  142. );
  143. if (!modeExpr.isString()) {
  144. errors.push(createPropertyParseError(prop, "string"));
  145. } else {
  146. mode = /** @type {ContextModuleOptions["mode"]} */ (
  147. modeExpr.string
  148. );
  149. }
  150. break;
  151. }
  152. case "chunkName": {
  153. const expr = parser.evaluateExpression(
  154. /** @type {Expression} */ (prop.value)
  155. );
  156. if (!expr.isString()) {
  157. errors.push(createPropertyParseError(prop, "string"));
  158. } else {
  159. chunkName = expr.string;
  160. }
  161. break;
  162. }
  163. case "exports": {
  164. const expr = parser.evaluateExpression(
  165. /** @type {Expression} */ (prop.value)
  166. );
  167. if (expr.isString()) {
  168. exports = [[/** @type {string} */ (expr.string)]];
  169. } else if (expr.isArray()) {
  170. const items =
  171. /** @type {BasicEvaluatedExpression[]} */
  172. (expr.items);
  173. if (
  174. items.every(i => {
  175. if (!i.isArray()) return false;
  176. const innerItems =
  177. /** @type {BasicEvaluatedExpression[]} */ (i.items);
  178. return innerItems.every(i => i.isString());
  179. })
  180. ) {
  181. exports = [];
  182. for (const i1 of items) {
  183. /** @type {string[]} */
  184. const export_ = [];
  185. for (const i2 of /** @type {BasicEvaluatedExpression[]} */ (
  186. i1.items
  187. )) {
  188. export_.push(/** @type {string} */ (i2.string));
  189. }
  190. exports.push(export_);
  191. }
  192. } else {
  193. errors.push(
  194. createPropertyParseError(prop, "string|string[][]")
  195. );
  196. }
  197. } else {
  198. errors.push(
  199. createPropertyParseError(prop, "string|string[][]")
  200. );
  201. }
  202. break;
  203. }
  204. case "prefetch": {
  205. const expr = parser.evaluateExpression(
  206. /** @type {Expression} */ (prop.value)
  207. );
  208. if (expr.isBoolean()) {
  209. groupOptions.prefetchOrder = 0;
  210. } else if (expr.isNumber()) {
  211. groupOptions.prefetchOrder = expr.number;
  212. } else {
  213. errors.push(createPropertyParseError(prop, "boolean|number"));
  214. }
  215. break;
  216. }
  217. case "preload": {
  218. const expr = parser.evaluateExpression(
  219. /** @type {Expression} */ (prop.value)
  220. );
  221. if (expr.isBoolean()) {
  222. groupOptions.preloadOrder = 0;
  223. } else if (expr.isNumber()) {
  224. groupOptions.preloadOrder = expr.number;
  225. } else {
  226. errors.push(createPropertyParseError(prop, "boolean|number"));
  227. }
  228. break;
  229. }
  230. case "fetchPriority": {
  231. const expr = parser.evaluateExpression(
  232. /** @type {Expression} */ (prop.value)
  233. );
  234. if (
  235. expr.isString() &&
  236. ["high", "low", "auto"].includes(
  237. /** @type {string} */ (expr.string)
  238. )
  239. ) {
  240. groupOptions.fetchPriority =
  241. /** @type {RawChunkGroupOptions["fetchPriority"]} */ (
  242. expr.string
  243. );
  244. } else {
  245. errors.push(
  246. createPropertyParseError(prop, '"high"|"low"|"auto"')
  247. );
  248. }
  249. break;
  250. }
  251. case "recursive": {
  252. const recursiveExpr = parser.evaluateExpression(
  253. /** @type {Expression} */ (prop.value)
  254. );
  255. if (!recursiveExpr.isBoolean()) {
  256. errors.push(createPropertyParseError(prop, "boolean"));
  257. } else {
  258. recursive = /** @type {boolean} */ (recursiveExpr.bool);
  259. }
  260. break;
  261. }
  262. default:
  263. errors.push(
  264. createError(
  265. `Parsing import.meta.webpackContext options failed. Unknown property ${JSON.stringify(
  266. prop.key.name
  267. )}.`,
  268. /** @type {DependencyLocation} */ (optionsNode.loc)
  269. )
  270. );
  271. }
  272. }
  273. }
  274. if (errors.length) {
  275. for (const error of errors) parser.state.current.addError(error);
  276. return;
  277. }
  278. const dep = new ImportMetaContextDependency(
  279. {
  280. request,
  281. include,
  282. exclude,
  283. recursive,
  284. regExp,
  285. groupOptions,
  286. chunkName,
  287. referencedExports: exports,
  288. mode,
  289. category: "esm"
  290. },
  291. /** @type {Range} */ (expr.range)
  292. );
  293. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  294. dep.optional = Boolean(parser.scope.inTry);
  295. parser.state.current.addDependency(dep);
  296. return true;
  297. });
  298. }
  299. };