RequireEnsureDependenciesBlockParserPlugin.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RequireEnsureDependenciesBlock = require("./RequireEnsureDependenciesBlock");
  7. const RequireEnsureDependency = require("./RequireEnsureDependency");
  8. const RequireEnsureItemDependency = require("./RequireEnsureItemDependency");
  9. const getFunctionExpression = require("./getFunctionExpression");
  10. /** @typedef {import("estree").Expression} Expression */
  11. /** @typedef {import("estree").SpreadElement} SpreadElement */
  12. /** @typedef {import("../AsyncDependenciesBlock").GroupOptions} GroupOptions */
  13. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  14. /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
  15. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  16. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  17. /** @typedef {import("./getFunctionExpression").FunctionExpressionResult} FunctionExpressionResult */
  18. const PLUGIN_NAME = "RequireEnsureDependenciesBlockParserPlugin";
  19. module.exports = class RequireEnsureDependenciesBlockParserPlugin {
  20. /**
  21. * @param {JavascriptParser} parser the parser
  22. * @returns {void}
  23. */
  24. apply(parser) {
  25. parser.hooks.call.for("require.ensure").tap(PLUGIN_NAME, (expr) => {
  26. /** @type {string | GroupOptions | null} */
  27. let chunkName = null;
  28. /** @type {undefined | Expression | SpreadElement} */
  29. let errorExpressionArg;
  30. /** @type {undefined | FunctionExpressionResult} */
  31. let errorExpression;
  32. switch (expr.arguments.length) {
  33. case 4: {
  34. const chunkNameExpr = parser.evaluateExpression(expr.arguments[3]);
  35. if (!chunkNameExpr.isString()) return;
  36. chunkName =
  37. /** @type {string} */
  38. (chunkNameExpr.string);
  39. }
  40. // falls through
  41. case 3: {
  42. errorExpressionArg = expr.arguments[2];
  43. errorExpression = getFunctionExpression(errorExpressionArg);
  44. if (!errorExpression && !chunkName) {
  45. const chunkNameExpr = parser.evaluateExpression(expr.arguments[2]);
  46. if (!chunkNameExpr.isString()) return;
  47. chunkName =
  48. /** @type {string} */
  49. (chunkNameExpr.string);
  50. }
  51. }
  52. // falls through
  53. case 2: {
  54. const dependenciesExpr = parser.evaluateExpression(expr.arguments[0]);
  55. const dependenciesItems = /** @type {BasicEvaluatedExpression[]} */ (
  56. dependenciesExpr.isArray()
  57. ? dependenciesExpr.items
  58. : [dependenciesExpr]
  59. );
  60. const successExpressionArg = expr.arguments[1];
  61. const successExpression = getFunctionExpression(successExpressionArg);
  62. if (successExpression) {
  63. parser.walkExpressions(successExpression.expressions);
  64. }
  65. if (errorExpression) {
  66. parser.walkExpressions(errorExpression.expressions);
  67. }
  68. const depBlock = new RequireEnsureDependenciesBlock(
  69. chunkName,
  70. /** @type {DependencyLocation} */
  71. (expr.loc)
  72. );
  73. const errorCallbackExists =
  74. expr.arguments.length === 4 ||
  75. (!chunkName && expr.arguments.length === 3);
  76. const dep = new RequireEnsureDependency(
  77. /** @type {Range} */ (expr.range),
  78. /** @type {Range} */ (expr.arguments[1].range),
  79. errorCallbackExists &&
  80. /** @type {Range} */ (expr.arguments[2].range)
  81. );
  82. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  83. depBlock.addDependency(dep);
  84. const old = parser.state.current;
  85. parser.state.current = /** @type {EXPECTED_ANY} */ (depBlock);
  86. try {
  87. let failed = false;
  88. parser.inScope([], () => {
  89. for (const ee of dependenciesItems) {
  90. if (ee.isString()) {
  91. const ensureDependency = new RequireEnsureItemDependency(
  92. /** @type {string} */ (ee.string)
  93. );
  94. ensureDependency.loc =
  95. /** @type {DependencyLocation} */
  96. (expr.loc);
  97. depBlock.addDependency(ensureDependency);
  98. } else {
  99. failed = true;
  100. }
  101. }
  102. });
  103. if (failed) {
  104. return;
  105. }
  106. if (successExpression) {
  107. if (successExpression.fn.body.type === "BlockStatement") {
  108. // Opt-out of Dead Control Flow detection for this block
  109. const oldTerminated = parser.scope.terminated;
  110. parser.walkStatement(successExpression.fn.body);
  111. parser.scope.terminated = oldTerminated;
  112. } else {
  113. parser.walkExpression(successExpression.fn.body);
  114. }
  115. }
  116. old.addBlock(depBlock);
  117. } finally {
  118. parser.state.current = old;
  119. }
  120. if (!successExpression) {
  121. parser.walkExpression(successExpressionArg);
  122. }
  123. if (errorExpression) {
  124. if (errorExpression.fn.body.type === "BlockStatement") {
  125. parser.walkStatement(errorExpression.fn.body);
  126. } else {
  127. parser.walkExpression(errorExpression.fn.body);
  128. }
  129. } else if (errorExpressionArg) {
  130. parser.walkExpression(errorExpressionArg);
  131. }
  132. return true;
  133. }
  134. }
  135. });
  136. }
  137. };