URLPlugin.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  17. /** @typedef {import("../Compiler")} Compiler */
  18. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  19. /** @typedef {import("../NormalModule")} NormalModule */
  20. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  21. /** @typedef {import("../javascript/JavascriptParser")} Parser */
  22. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  23. const PLUGIN_NAME = "URLPlugin";
  24. class URLPlugin {
  25. /**
  26. * @param {Compiler} compiler compiler
  27. */
  28. apply(compiler) {
  29. compiler.hooks.compilation.tap(
  30. PLUGIN_NAME,
  31. (compilation, { normalModuleFactory }) => {
  32. compilation.dependencyFactories.set(URLDependency, normalModuleFactory);
  33. compilation.dependencyTemplates.set(
  34. URLDependency,
  35. new URLDependency.Template()
  36. );
  37. /**
  38. * @param {NormalModule} module module
  39. * @returns {URL} file url
  40. */
  41. const getUrl = module => {
  42. return pathToFileURL(module.resource);
  43. };
  44. /**
  45. * @param {Parser} parser parser parser
  46. * @param {JavascriptParserOptions} parserOptions parserOptions
  47. * @returns {void}
  48. */
  49. const parserCallback = (parser, parserOptions) => {
  50. if (parserOptions.url === false) return;
  51. const relative = parserOptions.url === "relative";
  52. /**
  53. * @param {NewExpressionNode} expr expression
  54. * @returns {undefined | string} request
  55. */
  56. const getUrlRequest = expr => {
  57. if (expr.arguments.length !== 2) return;
  58. const [arg1, arg2] = expr.arguments;
  59. if (
  60. arg2.type !== "MemberExpression" ||
  61. arg1.type === "SpreadElement"
  62. )
  63. return;
  64. const chain = parser.extractMemberExpressionChain(arg2);
  65. if (
  66. chain.members.length !== 1 ||
  67. chain.object.type !== "MetaProperty" ||
  68. chain.object.meta.name !== "import" ||
  69. chain.object.property.name !== "meta" ||
  70. chain.members[0] !== "url"
  71. )
  72. return;
  73. return parser.evaluateExpression(arg1).asString();
  74. };
  75. parser.hooks.canRename.for("URL").tap(PLUGIN_NAME, approve);
  76. parser.hooks.evaluateNewExpression
  77. .for("URL")
  78. .tap(PLUGIN_NAME, expr => {
  79. const request = getUrlRequest(expr);
  80. if (!request) return;
  81. const url = new URL(request, getUrl(parser.state.module));
  82. return new BasicEvaluatedExpression()
  83. .setString(url.toString())
  84. .setRange(/** @type {Range} */ (expr.range));
  85. });
  86. parser.hooks.new.for("URL").tap(PLUGIN_NAME, _expr => {
  87. const expr = /** @type {NewExpressionNode} */ (_expr);
  88. const request = getUrlRequest(expr);
  89. if (!request) return;
  90. const [arg1, arg2] = expr.arguments;
  91. const dep = new URLDependency(
  92. request,
  93. [
  94. /** @type {Range} */ (arg1.range)[0],
  95. /** @type {Range} */ (arg2.range)[1]
  96. ],
  97. /** @type {Range} */ (expr.range),
  98. relative
  99. );
  100. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  101. parser.state.current.addDependency(dep);
  102. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  103. return true;
  104. });
  105. parser.hooks.isPure.for("NewExpression").tap(PLUGIN_NAME, _expr => {
  106. const expr = /** @type {NewExpressionNode} */ (_expr);
  107. const { callee } = expr;
  108. if (callee.type !== "Identifier") return;
  109. const calleeInfo = parser.getFreeInfoFromVariable(callee.name);
  110. if (!calleeInfo || calleeInfo.name !== "URL") return;
  111. const request = getUrlRequest(expr);
  112. if (request) return true;
  113. });
  114. };
  115. normalModuleFactory.hooks.parser
  116. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  117. .tap(PLUGIN_NAME, parserCallback);
  118. normalModuleFactory.hooks.parser
  119. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  120. .tap(PLUGIN_NAME, parserCallback);
  121. }
  122. );
  123. }
  124. }
  125. module.exports = URLPlugin;