RequireEnsureDependenciesBlockParserPlugin.js 5.0 KB

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