ImportParserPlugin.js 12 KB

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