URLPlugin.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const { pathToFileURL } = require("url");
  7. const {
  8. JAVASCRIPT_MODULE_TYPE_AUTO,
  9. JAVASCRIPT_MODULE_TYPE_ESM
  10. } = require("../ModuleTypeConstants");
  11. const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
  12. const { approve } = require("../javascript/JavascriptParserHelpers");
  13. const InnerGraph = require("../optimize/InnerGraph");
  14. const URLDependency = require("./URLDependency");
  15. /** @typedef {import("estree").NewExpression} NewExpressionNode */
  16. /** @typedef {import("../Compiler")} Compiler */
  17. /** @typedef {import("../NormalModule")} NormalModule */
  18. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  19. const PLUGIN_NAME = "URLPlugin";
  20. class URLPlugin {
  21. /**
  22. * @param {Compiler} compiler compiler
  23. */
  24. apply(compiler) {
  25. compiler.hooks.compilation.tap(
  26. PLUGIN_NAME,
  27. (compilation, { normalModuleFactory }) => {
  28. compilation.dependencyFactories.set(URLDependency, normalModuleFactory);
  29. compilation.dependencyTemplates.set(
  30. URLDependency,
  31. new URLDependency.Template()
  32. );
  33. /**
  34. * @param {NormalModule} module module
  35. * @returns {URL} file url
  36. */
  37. const getUrl = module => {
  38. return pathToFileURL(module.resource);
  39. };
  40. /**
  41. * @param {JavascriptParser} parser parser
  42. * @param {object} parserOptions options
  43. */
  44. const parserCallback = (parser, parserOptions) => {
  45. if (parserOptions.url === false) return;
  46. const relative = parserOptions.url === "relative";
  47. /**
  48. * @param {NewExpressionNode} expr expression
  49. * @returns {undefined | string} request
  50. */
  51. const getUrlRequest = expr => {
  52. if (expr.arguments.length !== 2) return;
  53. const [arg1, arg2] = expr.arguments;
  54. if (
  55. arg2.type !== "MemberExpression" ||
  56. arg1.type === "SpreadElement"
  57. )
  58. return;
  59. const chain = parser.extractMemberExpressionChain(arg2);
  60. if (
  61. chain.members.length !== 1 ||
  62. chain.object.type !== "MetaProperty" ||
  63. chain.object.meta.name !== "import" ||
  64. chain.object.property.name !== "meta" ||
  65. chain.members[0] !== "url"
  66. )
  67. return;
  68. const request = parser.evaluateExpression(arg1).asString();
  69. return request;
  70. };
  71. parser.hooks.canRename.for("URL").tap(PLUGIN_NAME, approve);
  72. parser.hooks.evaluateNewExpression
  73. .for("URL")
  74. .tap(PLUGIN_NAME, expr => {
  75. const request = getUrlRequest(expr);
  76. if (!request) return;
  77. const url = new URL(request, getUrl(parser.state.module));
  78. return new BasicEvaluatedExpression()
  79. .setString(url.toString())
  80. .setRange(expr.range);
  81. });
  82. parser.hooks.new.for("URL").tap(PLUGIN_NAME, _expr => {
  83. const expr = /** @type {NewExpressionNode} */ (_expr);
  84. const request = getUrlRequest(expr);
  85. if (!request) return;
  86. const [arg1, arg2] = expr.arguments;
  87. const dep = new URLDependency(
  88. request,
  89. [arg1.range[0], arg2.range[1]],
  90. expr.range,
  91. relative
  92. );
  93. dep.loc = expr.loc;
  94. parser.state.current.addDependency(dep);
  95. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  96. return true;
  97. });
  98. parser.hooks.isPure.for("NewExpression").tap(PLUGIN_NAME, _expr => {
  99. const expr = /** @type {NewExpressionNode} */ (_expr);
  100. const { callee } = expr;
  101. if (callee.type !== "Identifier") return;
  102. const calleeInfo = parser.getFreeInfoFromVariable(callee.name);
  103. if (!calleeInfo || calleeInfo.name !== "URL") return;
  104. const request = getUrlRequest(expr);
  105. if (request) return true;
  106. });
  107. };
  108. normalModuleFactory.hooks.parser
  109. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  110. .tap(PLUGIN_NAME, parserCallback);
  111. normalModuleFactory.hooks.parser
  112. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  113. .tap(PLUGIN_NAME, parserCallback);
  114. }
  115. );
  116. }
  117. }
  118. module.exports = URLPlugin;