ImportMetaContextDependencyParserPlugin.js 9.2 KB

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