ImportMetaContextDependencyParserPlugin.js 8.5 KB

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