ImportMetaContextDependencyParserPlugin.js 9.5 KB

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