ImportParserPlugin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
  7. const CommentCompilationWarning = require("../CommentCompilationWarning");
  8. const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
  9. const { getImportAttributes } = require("../javascript/JavascriptParser");
  10. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  11. const ImportContextDependency = require("./ImportContextDependency");
  12. const ImportDependency = require("./ImportDependency");
  13. const ImportEagerDependency = require("./ImportEagerDependency");
  14. const ImportWeakDependency = require("./ImportWeakDependency");
  15. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  16. /** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
  17. /** @typedef {import("../ContextModule").ContextMode} ContextMode */
  18. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  19. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  20. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  21. /** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */
  22. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  23. class ImportParserPlugin {
  24. /**
  25. * @param {JavascriptParserOptions} options options
  26. */
  27. constructor(options) {
  28. this.options = options;
  29. }
  30. /**
  31. * @param {JavascriptParser} parser the parser
  32. * @returns {void}
  33. */
  34. apply(parser) {
  35. /**
  36. * @template T
  37. * @param {Iterable<T>} enumerable enumerable
  38. * @returns {T[][]} array of array
  39. */
  40. const exportsFromEnumerable = enumerable =>
  41. Array.from(enumerable, e => [e]);
  42. parser.hooks.importCall.tap("ImportParserPlugin", expr => {
  43. const param = parser.evaluateExpression(expr.source);
  44. let chunkName = null;
  45. let mode = /** @type {ContextMode} */ (this.options.dynamicImportMode);
  46. let include = null;
  47. let exclude = null;
  48. /** @type {string[][] | null} */
  49. let exports = null;
  50. /** @type {RawChunkGroupOptions} */
  51. const groupOptions = {};
  52. const {
  53. dynamicImportPreload,
  54. dynamicImportPrefetch,
  55. dynamicImportFetchPriority
  56. } = this.options;
  57. if (dynamicImportPreload !== undefined && dynamicImportPreload !== false)
  58. groupOptions.preloadOrder =
  59. dynamicImportPreload === true ? 0 : dynamicImportPreload;
  60. if (
  61. dynamicImportPrefetch !== undefined &&
  62. dynamicImportPrefetch !== false
  63. )
  64. groupOptions.prefetchOrder =
  65. dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch;
  66. if (
  67. dynamicImportFetchPriority !== undefined &&
  68. dynamicImportFetchPriority !== false
  69. )
  70. groupOptions.fetchPriority = dynamicImportFetchPriority;
  71. const { options: importOptions, errors: commentErrors } =
  72. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  73. if (commentErrors) {
  74. for (const e of commentErrors) {
  75. const { comment } = e;
  76. parser.state.module.addWarning(
  77. new CommentCompilationWarning(
  78. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  79. /** @type {DependencyLocation} */ (comment.loc)
  80. )
  81. );
  82. }
  83. }
  84. if (importOptions) {
  85. if (importOptions.webpackIgnore !== undefined) {
  86. if (typeof importOptions.webpackIgnore !== "boolean") {
  87. parser.state.module.addWarning(
  88. new UnsupportedFeatureWarning(
  89. `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`,
  90. /** @type {DependencyLocation} */ (expr.loc)
  91. )
  92. );
  93. } else if (importOptions.webpackIgnore) {
  94. // Do not instrument `import()` if `webpackIgnore` is `true`
  95. return false;
  96. }
  97. }
  98. if (importOptions.webpackChunkName !== undefined) {
  99. if (typeof importOptions.webpackChunkName !== "string") {
  100. parser.state.module.addWarning(
  101. new UnsupportedFeatureWarning(
  102. `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`,
  103. /** @type {DependencyLocation} */ (expr.loc)
  104. )
  105. );
  106. } else {
  107. chunkName = importOptions.webpackChunkName;
  108. }
  109. }
  110. if (importOptions.webpackMode !== undefined) {
  111. if (typeof importOptions.webpackMode !== "string") {
  112. parser.state.module.addWarning(
  113. new UnsupportedFeatureWarning(
  114. `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`,
  115. /** @type {DependencyLocation} */ (expr.loc)
  116. )
  117. );
  118. } else {
  119. mode = /** @type {ContextMode} */ (importOptions.webpackMode);
  120. }
  121. }
  122. if (importOptions.webpackPrefetch !== undefined) {
  123. if (importOptions.webpackPrefetch === true) {
  124. groupOptions.prefetchOrder = 0;
  125. } else if (typeof importOptions.webpackPrefetch === "number") {
  126. groupOptions.prefetchOrder = importOptions.webpackPrefetch;
  127. } else {
  128. parser.state.module.addWarning(
  129. new UnsupportedFeatureWarning(
  130. `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`,
  131. /** @type {DependencyLocation} */ (expr.loc)
  132. )
  133. );
  134. }
  135. }
  136. if (importOptions.webpackPreload !== undefined) {
  137. if (importOptions.webpackPreload === true) {
  138. groupOptions.preloadOrder = 0;
  139. } else if (typeof importOptions.webpackPreload === "number") {
  140. groupOptions.preloadOrder = importOptions.webpackPreload;
  141. } else {
  142. parser.state.module.addWarning(
  143. new UnsupportedFeatureWarning(
  144. `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`,
  145. /** @type {DependencyLocation} */ (expr.loc)
  146. )
  147. );
  148. }
  149. }
  150. if (importOptions.webpackFetchPriority !== undefined) {
  151. if (
  152. typeof importOptions.webpackFetchPriority === "string" &&
  153. ["high", "low", "auto"].includes(importOptions.webpackFetchPriority)
  154. ) {
  155. groupOptions.fetchPriority =
  156. /** @type {"low" | "high" | "auto"} */
  157. (importOptions.webpackFetchPriority);
  158. } else {
  159. parser.state.module.addWarning(
  160. new UnsupportedFeatureWarning(
  161. `\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
  162. /** @type {DependencyLocation} */ (expr.loc)
  163. )
  164. );
  165. }
  166. }
  167. if (importOptions.webpackInclude !== undefined) {
  168. if (
  169. !importOptions.webpackInclude ||
  170. !(importOptions.webpackInclude instanceof RegExp)
  171. ) {
  172. parser.state.module.addWarning(
  173. new UnsupportedFeatureWarning(
  174. `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`,
  175. /** @type {DependencyLocation} */ (expr.loc)
  176. )
  177. );
  178. } else {
  179. include = importOptions.webpackInclude;
  180. }
  181. }
  182. if (importOptions.webpackExclude !== undefined) {
  183. if (
  184. !importOptions.webpackExclude ||
  185. !(importOptions.webpackExclude instanceof RegExp)
  186. ) {
  187. parser.state.module.addWarning(
  188. new UnsupportedFeatureWarning(
  189. `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`,
  190. /** @type {DependencyLocation} */ (expr.loc)
  191. )
  192. );
  193. } else {
  194. exclude = importOptions.webpackExclude;
  195. }
  196. }
  197. if (importOptions.webpackExports !== undefined) {
  198. if (
  199. !(
  200. typeof importOptions.webpackExports === "string" ||
  201. (Array.isArray(importOptions.webpackExports) &&
  202. /** @type {string[]} */ (importOptions.webpackExports).every(
  203. item => typeof item === "string"
  204. ))
  205. )
  206. ) {
  207. parser.state.module.addWarning(
  208. new UnsupportedFeatureWarning(
  209. `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`,
  210. /** @type {DependencyLocation} */ (expr.loc)
  211. )
  212. );
  213. } else if (typeof importOptions.webpackExports === "string") {
  214. exports = [[importOptions.webpackExports]];
  215. } else {
  216. exports = exportsFromEnumerable(importOptions.webpackExports);
  217. }
  218. }
  219. }
  220. if (
  221. mode !== "lazy" &&
  222. mode !== "lazy-once" &&
  223. mode !== "eager" &&
  224. mode !== "weak"
  225. ) {
  226. parser.state.module.addWarning(
  227. new UnsupportedFeatureWarning(
  228. `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`,
  229. /** @type {DependencyLocation} */ (expr.loc)
  230. )
  231. );
  232. mode = "lazy";
  233. }
  234. const referencedPropertiesInDestructuring =
  235. parser.destructuringAssignmentPropertiesFor(expr);
  236. if (referencedPropertiesInDestructuring) {
  237. if (exports) {
  238. parser.state.module.addWarning(
  239. new UnsupportedFeatureWarning(
  240. "`webpackExports` could not be used with destructuring assignment.",
  241. /** @type {DependencyLocation} */ (expr.loc)
  242. )
  243. );
  244. }
  245. exports = exportsFromEnumerable(
  246. [...referencedPropertiesInDestructuring].map(({ id }) => id)
  247. );
  248. }
  249. if (param.isString()) {
  250. const attributes = getImportAttributes(expr);
  251. if (mode === "eager") {
  252. const dep = new ImportEagerDependency(
  253. /** @type {string} */ (param.string),
  254. /** @type {Range} */ (expr.range),
  255. exports,
  256. attributes
  257. );
  258. parser.state.current.addDependency(dep);
  259. } else if (mode === "weak") {
  260. const dep = new ImportWeakDependency(
  261. /** @type {string} */ (param.string),
  262. /** @type {Range} */ (expr.range),
  263. exports,
  264. attributes
  265. );
  266. parser.state.current.addDependency(dep);
  267. } else {
  268. const depBlock = new AsyncDependenciesBlock(
  269. {
  270. ...groupOptions,
  271. name: chunkName
  272. },
  273. /** @type {DependencyLocation} */ (expr.loc),
  274. param.string
  275. );
  276. const dep = new ImportDependency(
  277. /** @type {string} */ (param.string),
  278. /** @type {Range} */ (expr.range),
  279. exports,
  280. attributes
  281. );
  282. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  283. dep.optional = Boolean(parser.scope.inTry);
  284. depBlock.addDependency(dep);
  285. parser.state.current.addBlock(depBlock);
  286. }
  287. return true;
  288. }
  289. if (mode === "weak") {
  290. mode = "async-weak";
  291. }
  292. const dep = ContextDependencyHelpers.create(
  293. ImportContextDependency,
  294. /** @type {Range} */ (expr.range),
  295. param,
  296. expr,
  297. this.options,
  298. {
  299. chunkName,
  300. groupOptions,
  301. include,
  302. exclude,
  303. mode,
  304. namespaceObject: /** @type {BuildMeta} */ (
  305. parser.state.module.buildMeta
  306. ).strictHarmonyModule
  307. ? "strict"
  308. : true,
  309. typePrefix: "import()",
  310. category: "esm",
  311. referencedExports: exports,
  312. attributes: getImportAttributes(expr)
  313. },
  314. parser
  315. );
  316. if (!dep) return;
  317. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  318. dep.optional = Boolean(parser.scope.inTry);
  319. parser.state.current.addDependency(dep);
  320. return true;
  321. });
  322. }
  323. }
  324. module.exports = ImportParserPlugin;