RequireEnsureDependenciesBlockParserPlugin.js 4.6 KB

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