ImportMetaContextDependencyParserPlugin.js 9.3 KB

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