ImportMetaContextDependencyParserPlugin.js 7.5 KB

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