ImportParserPlugin.js 12 KB

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