URLPlugin.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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 CommentCompilationWarning = require("../CommentCompilationWarning");
  8. const {
  9. JAVASCRIPT_MODULE_TYPE_AUTO,
  10. JAVASCRIPT_MODULE_TYPE_ESM
  11. } = require("../ModuleTypeConstants");
  12. const RuntimeGlobals = require("../RuntimeGlobals");
  13. const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
  14. const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
  15. const { approve } = require("../javascript/JavascriptParserHelpers");
  16. const InnerGraph = require("../optimize/InnerGraph");
  17. const ConstDependency = require("./ConstDependency");
  18. const URLDependency = require("./URLDependency");
  19. /** @typedef {import("estree").MemberExpression} MemberExpression */
  20. /** @typedef {import("estree").NewExpression} NewExpressionNode */
  21. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  22. /** @typedef {import("../Compiler")} Compiler */
  23. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  24. /** @typedef {import("../NormalModule")} NormalModule */
  25. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  26. /** @typedef {import("../javascript/JavascriptParser")} Parser */
  27. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  28. const PLUGIN_NAME = "URLPlugin";
  29. class URLPlugin {
  30. /**
  31. * @param {Compiler} compiler compiler
  32. */
  33. apply(compiler) {
  34. compiler.hooks.compilation.tap(
  35. PLUGIN_NAME,
  36. (compilation, { normalModuleFactory }) => {
  37. compilation.dependencyFactories.set(URLDependency, normalModuleFactory);
  38. compilation.dependencyTemplates.set(
  39. URLDependency,
  40. new URLDependency.Template()
  41. );
  42. /**
  43. * @param {NormalModule} module module
  44. * @returns {URL} file url
  45. */
  46. const getUrl = module => pathToFileURL(module.resource);
  47. /**
  48. * @param {Parser} parser parser parser
  49. * @param {MemberExpression} arg arg
  50. * @returns {boolean} true when it is `meta.url`, otherwise false
  51. */
  52. const isMetaUrl = (parser, arg) => {
  53. const chain = parser.extractMemberExpressionChain(arg);
  54. if (
  55. chain.members.length !== 1 ||
  56. chain.object.type !== "MetaProperty" ||
  57. chain.object.meta.name !== "import" ||
  58. chain.object.property.name !== "meta" ||
  59. chain.members[0] !== "url"
  60. )
  61. return false;
  62. return true;
  63. };
  64. /**
  65. * @param {Parser} parser parser parser
  66. * @param {JavascriptParserOptions} parserOptions parserOptions
  67. * @returns {void}
  68. */
  69. const parserCallback = (parser, parserOptions) => {
  70. if (parserOptions.url === false) return;
  71. const relative = parserOptions.url === "relative";
  72. /**
  73. * @param {NewExpressionNode} expr expression
  74. * @returns {undefined | string} request
  75. */
  76. const getUrlRequest = expr => {
  77. if (expr.arguments.length !== 2) return;
  78. const [arg1, arg2] = expr.arguments;
  79. if (
  80. arg2.type !== "MemberExpression" ||
  81. arg1.type === "SpreadElement"
  82. )
  83. return;
  84. if (!isMetaUrl(parser, arg2)) return;
  85. return parser.evaluateExpression(arg1).asString();
  86. };
  87. parser.hooks.canRename.for("URL").tap(PLUGIN_NAME, approve);
  88. parser.hooks.evaluateNewExpression
  89. .for("URL")
  90. .tap(PLUGIN_NAME, expr => {
  91. const request = getUrlRequest(expr);
  92. if (!request) return;
  93. const url = new URL(request, getUrl(parser.state.module));
  94. return new BasicEvaluatedExpression()
  95. .setString(url.toString())
  96. .setRange(/** @type {Range} */ (expr.range));
  97. });
  98. parser.hooks.new.for("URL").tap(PLUGIN_NAME, _expr => {
  99. const expr = /** @type {NewExpressionNode} */ (_expr);
  100. const { options: importOptions, errors: commentErrors } =
  101. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  102. if (commentErrors) {
  103. for (const e of commentErrors) {
  104. const { comment } = e;
  105. parser.state.module.addWarning(
  106. new CommentCompilationWarning(
  107. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  108. /** @type {DependencyLocation} */ (comment.loc)
  109. )
  110. );
  111. }
  112. }
  113. if (importOptions && importOptions.webpackIgnore !== undefined) {
  114. if (typeof importOptions.webpackIgnore !== "boolean") {
  115. parser.state.module.addWarning(
  116. new UnsupportedFeatureWarning(
  117. `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`,
  118. /** @type {DependencyLocation} */ (expr.loc)
  119. )
  120. );
  121. return;
  122. } else if (importOptions.webpackIgnore) {
  123. if (expr.arguments.length !== 2) return;
  124. const [, arg2] = expr.arguments;
  125. if (
  126. arg2.type !== "MemberExpression" ||
  127. !isMetaUrl(parser, arg2)
  128. )
  129. return;
  130. const dep = new ConstDependency(
  131. RuntimeGlobals.baseURI,
  132. /** @type {Range} */ (arg2.range),
  133. [RuntimeGlobals.baseURI]
  134. );
  135. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  136. parser.state.module.addPresentationalDependency(dep);
  137. return true;
  138. }
  139. }
  140. const request = getUrlRequest(expr);
  141. if (!request) return;
  142. const [arg1, arg2] = expr.arguments;
  143. const dep = new URLDependency(
  144. request,
  145. [
  146. /** @type {Range} */ (arg1.range)[0],
  147. /** @type {Range} */ (arg2.range)[1]
  148. ],
  149. /** @type {Range} */ (expr.range),
  150. relative
  151. );
  152. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  153. parser.state.current.addDependency(dep);
  154. InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
  155. return true;
  156. });
  157. parser.hooks.isPure.for("NewExpression").tap(PLUGIN_NAME, _expr => {
  158. const expr = /** @type {NewExpressionNode} */ (_expr);
  159. const { callee } = expr;
  160. if (callee.type !== "Identifier") return;
  161. const calleeInfo = parser.getFreeInfoFromVariable(callee.name);
  162. if (!calleeInfo || calleeInfo.name !== "URL") return;
  163. const request = getUrlRequest(expr);
  164. if (request) return true;
  165. });
  166. };
  167. normalModuleFactory.hooks.parser
  168. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  169. .tap(PLUGIN_NAME, parserCallback);
  170. normalModuleFactory.hooks.parser
  171. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  172. .tap(PLUGIN_NAME, parserCallback);
  173. }
  174. );
  175. }
  176. }
  177. module.exports = URLPlugin;