ImportParserPlugin.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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 {
  10. VariableInfo,
  11. getImportAttributes
  12. } = require("../javascript/JavascriptParser");
  13. const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties");
  14. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  15. const { getNonOptionalPart } = require("./HarmonyImportDependency");
  16. const ImportContextDependency = require("./ImportContextDependency");
  17. const ImportDependency = require("./ImportDependency");
  18. const ImportEagerDependency = require("./ImportEagerDependency");
  19. const { createGetImportPhase } = require("./ImportPhase");
  20. const ImportWeakDependency = require("./ImportWeakDependency");
  21. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  22. /** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
  23. /** @typedef {import("../ContextModule").ContextMode} ContextMode */
  24. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  25. /** @typedef {import("../Dependency").RawReferencedExports} RawReferencedExports */
  26. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  27. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  28. /** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */
  29. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  30. /** @typedef {import("../javascript/JavascriptParser").JavascriptParserState} JavascriptParserState */
  31. /** @typedef {import("../javascript/JavascriptParser").Members} Members */
  32. /** @typedef {import("../javascript/JavascriptParser").MembersOptionals} MembersOptionals */
  33. /** @typedef {import("../javascript/JavascriptParser").ArrowFunctionExpression} ArrowFunctionExpression */
  34. /** @typedef {import("../javascript/JavascriptParser").FunctionExpression} FunctionExpression */
  35. /** @typedef {import("../javascript/JavascriptParser").Identifier} Identifier */
  36. /** @typedef {import("../javascript/JavascriptParser").ObjectPattern} ObjectPattern */
  37. /** @typedef {import("../javascript/JavascriptParser").CallExpression} CallExpression */
  38. /** @typedef {{ references: RawReferencedExports, expression: ImportExpression }} ImportSettings */
  39. /** @typedef {WeakMap<ImportExpression, RawReferencedExports>} State */
  40. /** @type {WeakMap<JavascriptParserState, State>} */
  41. const parserStateMap = new WeakMap();
  42. const dynamicImportTag = Symbol("import()");
  43. /**
  44. * @param {JavascriptParser} parser javascript parser
  45. * @returns {State} import parser plugin state
  46. */
  47. function getState(parser) {
  48. if (!parserStateMap.has(parser.state)) {
  49. parserStateMap.set(parser.state, new WeakMap());
  50. }
  51. return /** @type {State} */ (parserStateMap.get(parser.state));
  52. }
  53. /**
  54. * @param {JavascriptParser} parser javascript parser
  55. * @param {ImportExpression} importCall import expression
  56. * @param {string} variableName variable name
  57. */
  58. function tagDynamicImportReferenced(parser, importCall, variableName) {
  59. const state = getState(parser);
  60. /** @type {RawReferencedExports} */
  61. const references = state.get(importCall) || [];
  62. state.set(importCall, references);
  63. parser.tagVariable(
  64. variableName,
  65. dynamicImportTag,
  66. /** @type {ImportSettings} */ ({
  67. references,
  68. expression: importCall
  69. })
  70. );
  71. }
  72. /**
  73. * @param {CallExpression} importThen import().then() call
  74. * @returns {Identifier | ObjectPattern | undefined} the dynamic imported namespace obj
  75. */
  76. function getFulfilledCallbackNamespaceObj(importThen) {
  77. const fulfilledCallback = importThen.arguments[0];
  78. if (
  79. fulfilledCallback &&
  80. (fulfilledCallback.type === "ArrowFunctionExpression" ||
  81. fulfilledCallback.type === "FunctionExpression") &&
  82. fulfilledCallback.params[0] &&
  83. (fulfilledCallback.params[0].type === "Identifier" ||
  84. fulfilledCallback.params[0].type === "ObjectPattern")
  85. ) {
  86. return fulfilledCallback.params[0];
  87. }
  88. }
  89. /**
  90. * @param {JavascriptParser} parser javascript parser
  91. * @param {ImportExpression} importCall import expression
  92. * @param {ArrowFunctionExpression | FunctionExpression} fulfilledCallback the fulfilled callback
  93. * @param {Identifier | ObjectPattern} namespaceObjArg the argument of namespace object=
  94. */
  95. function walkImportThenFulfilledCallback(
  96. parser,
  97. importCall,
  98. fulfilledCallback,
  99. namespaceObjArg
  100. ) {
  101. const arrow = fulfilledCallback.type === "ArrowFunctionExpression";
  102. const wasTopLevel = parser.scope.topLevelScope;
  103. parser.scope.topLevelScope = arrow ? (wasTopLevel ? "arrow" : false) : false;
  104. const scopeParams = [...fulfilledCallback.params];
  105. // Add function name in scope for recursive calls
  106. if (!arrow && fulfilledCallback.id) {
  107. scopeParams.push(fulfilledCallback.id);
  108. }
  109. parser.inFunctionScope(!arrow, scopeParams, () => {
  110. if (namespaceObjArg.type === "Identifier") {
  111. tagDynamicImportReferenced(parser, importCall, namespaceObjArg.name);
  112. } else {
  113. parser.enterDestructuringAssignment(namespaceObjArg, importCall);
  114. const referencedPropertiesInDestructuring =
  115. parser.destructuringAssignmentPropertiesFor(importCall);
  116. if (referencedPropertiesInDestructuring) {
  117. const state = getState(parser);
  118. const references = /** @type {RawReferencedExports} */ (
  119. state.get(importCall)
  120. );
  121. /** @type {RawReferencedExports} */
  122. const refsInDestructuring = [];
  123. traverseDestructuringAssignmentProperties(
  124. referencedPropertiesInDestructuring,
  125. (stack) => refsInDestructuring.push(stack.map((p) => p.id))
  126. );
  127. for (const ids of refsInDestructuring) {
  128. references.push(ids);
  129. }
  130. }
  131. }
  132. for (const param of fulfilledCallback.params) {
  133. parser.walkPattern(param);
  134. }
  135. if (fulfilledCallback.body.type === "BlockStatement") {
  136. parser.detectMode(fulfilledCallback.body.body);
  137. const prev = parser.prevStatement;
  138. parser.preWalkStatement(fulfilledCallback.body);
  139. parser.prevStatement = prev;
  140. parser.walkStatement(fulfilledCallback.body);
  141. } else {
  142. parser.walkExpression(fulfilledCallback.body);
  143. }
  144. });
  145. parser.scope.topLevelScope = wasTopLevel;
  146. }
  147. /**
  148. * @template T
  149. * @param {Iterable<T>} enumerable enumerable
  150. * @returns {T[][]} array of array
  151. */
  152. const exportsFromEnumerable = (enumerable) =>
  153. Array.from(enumerable, (e) => [e]);
  154. const PLUGIN_NAME = "ImportParserPlugin";
  155. class ImportParserPlugin {
  156. /**
  157. * @param {JavascriptParserOptions} options options
  158. */
  159. constructor(options) {
  160. this.options = options;
  161. }
  162. /**
  163. * @param {JavascriptParser} parser the parser
  164. * @returns {void}
  165. */
  166. apply(parser) {
  167. parser.hooks.collectDestructuringAssignmentProperties.tap(
  168. PLUGIN_NAME,
  169. (expr) => {
  170. if (expr.type === "ImportExpression") return true;
  171. const nameInfo = parser.getNameForExpression(expr);
  172. if (
  173. nameInfo &&
  174. nameInfo.rootInfo instanceof VariableInfo &&
  175. nameInfo.rootInfo.name &&
  176. parser.getTagData(nameInfo.rootInfo.name, dynamicImportTag)
  177. ) {
  178. return true;
  179. }
  180. }
  181. );
  182. parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl) => {
  183. if (
  184. decl.init &&
  185. decl.init.type === "AwaitExpression" &&
  186. decl.init.argument.type === "ImportExpression" &&
  187. decl.id.type === "Identifier"
  188. ) {
  189. parser.defineVariable(decl.id.name);
  190. tagDynamicImportReferenced(parser, decl.init.argument, decl.id.name);
  191. }
  192. });
  193. parser.hooks.expression.for(dynamicImportTag).tap(PLUGIN_NAME, (expr) => {
  194. const settings = /** @type {ImportSettings} */ (parser.currentTagData);
  195. const referencedPropertiesInDestructuring =
  196. parser.destructuringAssignmentPropertiesFor(expr);
  197. if (referencedPropertiesInDestructuring) {
  198. /** @type {RawReferencedExports} */
  199. const refsInDestructuring = [];
  200. traverseDestructuringAssignmentProperties(
  201. referencedPropertiesInDestructuring,
  202. (stack) => refsInDestructuring.push(stack.map((p) => p.id))
  203. );
  204. for (const ids of refsInDestructuring) {
  205. settings.references.push(ids);
  206. }
  207. } else {
  208. settings.references.push([]);
  209. }
  210. return true;
  211. });
  212. parser.hooks.expressionMemberChain
  213. .for(dynamicImportTag)
  214. .tap(PLUGIN_NAME, (_expression, members, membersOptionals) => {
  215. const settings = /** @type {ImportSettings} */ (parser.currentTagData);
  216. const ids = getNonOptionalPart(members, membersOptionals);
  217. settings.references.push(ids);
  218. return true;
  219. });
  220. parser.hooks.callMemberChain
  221. .for(dynamicImportTag)
  222. .tap(PLUGIN_NAME, (expression, members, membersOptionals) => {
  223. const { arguments: args } = expression;
  224. const settings = /** @type {ImportSettings} */ (parser.currentTagData);
  225. let ids = getNonOptionalPart(members, membersOptionals);
  226. const directImport = members.length === 0;
  227. if (
  228. !directImport &&
  229. (this.options.strictThisContextOnImports || ids.length > 1)
  230. ) {
  231. ids = ids.slice(0, -1);
  232. }
  233. settings.references.push(ids);
  234. if (args) parser.walkExpressions(args);
  235. return true;
  236. });
  237. parser.hooks.importCall.tap(PLUGIN_NAME, (expr, importThen) => {
  238. const param = parser.evaluateExpression(expr.source);
  239. /** @type {null | string} */
  240. let chunkName = null;
  241. let mode = /** @type {ContextMode} */ (this.options.dynamicImportMode);
  242. /** @type {null | RegExp} */
  243. let include = null;
  244. /** @type {null | RegExp} */
  245. let exclude = null;
  246. /** @type {null | RawReferencedExports} */
  247. let exports = null;
  248. /** @type {RawChunkGroupOptions} */
  249. const groupOptions = {};
  250. const {
  251. dynamicImportPreload,
  252. dynamicImportPrefetch,
  253. dynamicImportFetchPriority
  254. } = this.options;
  255. if (
  256. dynamicImportPreload !== undefined &&
  257. dynamicImportPreload !== false
  258. ) {
  259. groupOptions.preloadOrder =
  260. dynamicImportPreload === true ? 0 : dynamicImportPreload;
  261. }
  262. if (
  263. dynamicImportPrefetch !== undefined &&
  264. dynamicImportPrefetch !== false
  265. ) {
  266. groupOptions.prefetchOrder =
  267. dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch;
  268. }
  269. if (
  270. dynamicImportFetchPriority !== undefined &&
  271. dynamicImportFetchPriority !== false
  272. ) {
  273. groupOptions.fetchPriority = dynamicImportFetchPriority;
  274. }
  275. const { options: importOptions, errors: commentErrors } =
  276. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  277. if (commentErrors) {
  278. for (const e of commentErrors) {
  279. const { comment } = e;
  280. parser.state.module.addWarning(
  281. new CommentCompilationWarning(
  282. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  283. /** @type {DependencyLocation} */ (comment.loc)
  284. )
  285. );
  286. }
  287. }
  288. const phase = createGetImportPhase(this.options.deferImport)(
  289. parser,
  290. expr,
  291. () => importOptions
  292. );
  293. if (importOptions) {
  294. if (importOptions.webpackIgnore !== undefined) {
  295. if (typeof importOptions.webpackIgnore !== "boolean") {
  296. parser.state.module.addWarning(
  297. new UnsupportedFeatureWarning(
  298. `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`,
  299. /** @type {DependencyLocation} */ (expr.loc)
  300. )
  301. );
  302. } else if (importOptions.webpackIgnore) {
  303. // Do not instrument `import()` if `webpackIgnore` is `true`
  304. return false;
  305. }
  306. }
  307. if (importOptions.webpackChunkName !== undefined) {
  308. if (typeof importOptions.webpackChunkName !== "string") {
  309. parser.state.module.addWarning(
  310. new UnsupportedFeatureWarning(
  311. `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`,
  312. /** @type {DependencyLocation} */ (expr.loc)
  313. )
  314. );
  315. } else {
  316. chunkName = importOptions.webpackChunkName;
  317. }
  318. }
  319. if (importOptions.webpackMode !== undefined) {
  320. if (typeof importOptions.webpackMode !== "string") {
  321. parser.state.module.addWarning(
  322. new UnsupportedFeatureWarning(
  323. `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`,
  324. /** @type {DependencyLocation} */ (expr.loc)
  325. )
  326. );
  327. } else {
  328. mode = /** @type {ContextMode} */ (importOptions.webpackMode);
  329. }
  330. }
  331. if (importOptions.webpackPrefetch !== undefined) {
  332. if (importOptions.webpackPrefetch === true) {
  333. groupOptions.prefetchOrder = 0;
  334. } else if (typeof importOptions.webpackPrefetch === "number") {
  335. groupOptions.prefetchOrder = importOptions.webpackPrefetch;
  336. } else {
  337. parser.state.module.addWarning(
  338. new UnsupportedFeatureWarning(
  339. `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`,
  340. /** @type {DependencyLocation} */ (expr.loc)
  341. )
  342. );
  343. }
  344. }
  345. if (importOptions.webpackPreload !== undefined) {
  346. if (importOptions.webpackPreload === true) {
  347. groupOptions.preloadOrder = 0;
  348. } else if (typeof importOptions.webpackPreload === "number") {
  349. groupOptions.preloadOrder = importOptions.webpackPreload;
  350. } else {
  351. parser.state.module.addWarning(
  352. new UnsupportedFeatureWarning(
  353. `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`,
  354. /** @type {DependencyLocation} */ (expr.loc)
  355. )
  356. );
  357. }
  358. }
  359. if (importOptions.webpackFetchPriority !== undefined) {
  360. if (
  361. typeof importOptions.webpackFetchPriority === "string" &&
  362. ["high", "low", "auto"].includes(importOptions.webpackFetchPriority)
  363. ) {
  364. groupOptions.fetchPriority =
  365. /** @type {"low" | "high" | "auto"} */
  366. (importOptions.webpackFetchPriority);
  367. } else {
  368. parser.state.module.addWarning(
  369. new UnsupportedFeatureWarning(
  370. `\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
  371. /** @type {DependencyLocation} */ (expr.loc)
  372. )
  373. );
  374. }
  375. }
  376. if (importOptions.webpackInclude !== undefined) {
  377. if (
  378. !importOptions.webpackInclude ||
  379. !(importOptions.webpackInclude instanceof RegExp)
  380. ) {
  381. parser.state.module.addWarning(
  382. new UnsupportedFeatureWarning(
  383. `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`,
  384. /** @type {DependencyLocation} */ (expr.loc)
  385. )
  386. );
  387. } else {
  388. include = importOptions.webpackInclude;
  389. }
  390. }
  391. if (importOptions.webpackExclude !== undefined) {
  392. if (
  393. !importOptions.webpackExclude ||
  394. !(importOptions.webpackExclude instanceof RegExp)
  395. ) {
  396. parser.state.module.addWarning(
  397. new UnsupportedFeatureWarning(
  398. `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`,
  399. /** @type {DependencyLocation} */ (expr.loc)
  400. )
  401. );
  402. } else {
  403. exclude = importOptions.webpackExclude;
  404. }
  405. }
  406. if (importOptions.webpackExports !== undefined) {
  407. if (
  408. !(
  409. typeof importOptions.webpackExports === "string" ||
  410. (Array.isArray(importOptions.webpackExports) &&
  411. importOptions.webpackExports.every(
  412. (item) => typeof item === "string"
  413. ))
  414. )
  415. ) {
  416. parser.state.module.addWarning(
  417. new UnsupportedFeatureWarning(
  418. `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`,
  419. /** @type {DependencyLocation} */ (expr.loc)
  420. )
  421. );
  422. } else if (typeof importOptions.webpackExports === "string") {
  423. exports = [[importOptions.webpackExports]];
  424. } else {
  425. exports = exportsFromEnumerable(importOptions.webpackExports);
  426. }
  427. }
  428. }
  429. if (
  430. mode !== "lazy" &&
  431. mode !== "lazy-once" &&
  432. mode !== "eager" &&
  433. mode !== "weak"
  434. ) {
  435. parser.state.module.addWarning(
  436. new UnsupportedFeatureWarning(
  437. `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`,
  438. /** @type {DependencyLocation} */ (expr.loc)
  439. )
  440. );
  441. mode = "lazy";
  442. }
  443. const referencedPropertiesInDestructuring =
  444. parser.destructuringAssignmentPropertiesFor(expr);
  445. const state = getState(parser);
  446. const referencedPropertiesInMember = state.get(expr);
  447. const fulfilledNamespaceObj =
  448. importThen && getFulfilledCallbackNamespaceObj(importThen);
  449. if (
  450. referencedPropertiesInDestructuring ||
  451. referencedPropertiesInMember ||
  452. fulfilledNamespaceObj
  453. ) {
  454. if (exports) {
  455. parser.state.module.addWarning(
  456. new UnsupportedFeatureWarning(
  457. "You don't need `webpackExports` if the usage of dynamic import is statically analyse-able. You can safely remove the `webpackExports` magic comment.",
  458. /** @type {DependencyLocation} */ (expr.loc)
  459. )
  460. );
  461. }
  462. if (referencedPropertiesInDestructuring) {
  463. /** @type {RawReferencedExports} */
  464. const refsInDestructuring = [];
  465. traverseDestructuringAssignmentProperties(
  466. referencedPropertiesInDestructuring,
  467. (stack) => refsInDestructuring.push(stack.map((p) => p.id))
  468. );
  469. exports = refsInDestructuring;
  470. } else if (referencedPropertiesInMember) {
  471. exports = referencedPropertiesInMember;
  472. } else {
  473. /** @type {RawReferencedExports} */
  474. const references = [];
  475. state.set(expr, references);
  476. exports = references;
  477. }
  478. }
  479. if (param.isString()) {
  480. const attributes = getImportAttributes(expr);
  481. if (mode === "eager") {
  482. const dep = new ImportEagerDependency(
  483. /** @type {string} */ (param.string),
  484. /** @type {Range} */ (expr.range),
  485. exports,
  486. phase,
  487. attributes
  488. );
  489. parser.state.current.addDependency(dep);
  490. } else if (mode === "weak") {
  491. const dep = new ImportWeakDependency(
  492. /** @type {string} */ (param.string),
  493. /** @type {Range} */ (expr.range),
  494. exports,
  495. phase,
  496. attributes
  497. );
  498. parser.state.current.addDependency(dep);
  499. } else {
  500. const depBlock = new AsyncDependenciesBlock(
  501. {
  502. ...groupOptions,
  503. name: chunkName
  504. },
  505. /** @type {DependencyLocation} */ (expr.loc),
  506. param.string
  507. );
  508. const dep = new ImportDependency(
  509. /** @type {string} */ (param.string),
  510. /** @type {Range} */ (expr.range),
  511. exports,
  512. phase,
  513. attributes
  514. );
  515. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  516. dep.optional = Boolean(parser.scope.inTry);
  517. depBlock.addDependency(dep);
  518. parser.state.current.addBlock(depBlock);
  519. }
  520. } else {
  521. if (mode === "weak") {
  522. mode = "async-weak";
  523. }
  524. const dep = ContextDependencyHelpers.create(
  525. ImportContextDependency,
  526. /** @type {Range} */ (expr.range),
  527. param,
  528. expr,
  529. this.options,
  530. {
  531. chunkName,
  532. groupOptions,
  533. include,
  534. exclude,
  535. mode,
  536. namespaceObject:
  537. /** @type {BuildMeta} */
  538. (parser.state.module.buildMeta).strictHarmonyModule
  539. ? "strict"
  540. : true,
  541. typePrefix: "import()",
  542. category: "esm",
  543. referencedExports: exports,
  544. attributes: getImportAttributes(expr),
  545. phase
  546. },
  547. parser
  548. );
  549. if (!dep) return;
  550. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  551. dep.optional = Boolean(parser.scope.inTry);
  552. parser.state.current.addDependency(dep);
  553. }
  554. if (fulfilledNamespaceObj) {
  555. walkImportThenFulfilledCallback(
  556. parser,
  557. expr,
  558. /** @type {ArrowFunctionExpression | FunctionExpression} */
  559. (importThen.arguments[0]),
  560. fulfilledNamespaceObj
  561. );
  562. parser.walkExpressions(importThen.arguments.slice(1));
  563. } else if (importThen) {
  564. parser.walkExpressions(importThen.arguments);
  565. }
  566. return true;
  567. });
  568. }
  569. }
  570. module.exports = ImportParserPlugin;