URLDependency.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const {
  7. ASSET_URL_TYPE,
  8. JAVASCRIPT_TYPE
  9. } = require("../ModuleSourceTypeConstants");
  10. const RuntimeGlobals = require("../RuntimeGlobals");
  11. const RawDataUrlModule = require("../asset/RawDataUrlModule");
  12. const {
  13. getDependencyUsedByExportsCondition
  14. } = require("../optimize/InnerGraph");
  15. const { toJsStringLiteral } = require("../util/identifier");
  16. const makeSerializable = require("../util/makeSerializable");
  17. const memoize = require("../util/memoize");
  18. const {
  19. PUBLIC_PATH_AUTO,
  20. PUBLIC_PATH_FULL_HASH
  21. } = require("../util/publicPathPlaceholder");
  22. const ModuleDependency = require("./ModuleDependency");
  23. /** @import { ReplaceSource } from "webpack-sources" */
  24. /**
  25. * @import Dependency, {
  26. * GetConditionFn,
  27. * UpdateHashContext
  28. * } from "../Dependency"
  29. */
  30. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  31. /** @import Module from "../Module" */
  32. /** @import ModuleGraph from "../ModuleGraph" */
  33. /** @import Hash from "../util/Hash" */
  34. /** @import { Range } from "../javascript/JavascriptParser" */
  35. /** @import { UsedByExports } from "../optimize/InnerGraph" */
  36. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range, boolean, UsedByExports | undefined, true | undefined, true | undefined, ("high" | "low" | "auto" | undefined), string | undefined, string | undefined, string | undefined]>} ObjectDeserializerContext */
  37. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range, boolean, UsedByExports | undefined, true | undefined, true | undefined, ("high" | "low" | "auto" | undefined), string | undefined, string | undefined, string | undefined]>} ObjectSerializerContext */
  38. const getIgnoredRawDataUrlModule = memoize(
  39. () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)")
  40. );
  41. /**
  42. * Resolves the static literal specifier (already quoted) for `new URL(<here>, import.meta.url)`,
  43. * or `null` when the asset url can't be determined statically (e.g. a runtime/dynamic publicPath).
  44. * @param {URLDependency} dep the dependency
  45. * @param {DependencyTemplateContext} templateContext the template context
  46. * @returns {string | null} a JS string literal, or `null` to fall back to the runtime form
  47. */
  48. const getAnalyzableUrlSpecifier = (dep, templateContext) => {
  49. const {
  50. module: consumingModule,
  51. moduleGraph,
  52. chunkGraph,
  53. runtime,
  54. codeGenerationResults,
  55. runtimeTemplate
  56. } = templateContext;
  57. const assetModule = moduleGraph.getModule(dep);
  58. if (!assetModule || !codeGenerationResults.has(assetModule, runtime)) {
  59. return null;
  60. }
  61. const urlData = codeGenerationResults.getData(assetModule, runtime, "url");
  62. const jsUrl = urlData && urlData[JAVASCRIPT_TYPE];
  63. if (
  64. typeof jsUrl === "string" &&
  65. !jsUrl.includes(RuntimeGlobals.require) &&
  66. // An analyzable wrapper is an expression, not a value — quoting it would nest
  67. // one `new URL(…)` inside another. Its own literal is rebuilt below instead.
  68. !jsUrl.includes(runtimeTemplate.outputOptions.importMetaName)
  69. ) {
  70. // An already-quoted literal (generator `publicPath` / data: url) is used as-is;
  71. // a raw value (external asset) is normalized to a quoted string.
  72. return jsUrl.startsWith('"') ? jsUrl : toJsStringLiteral(jsUrl);
  73. }
  74. if (urlData && jsUrl === undefined) {
  75. // Wrapper dropped for an `asset-url` consumer: an absolute public path resolves
  76. // the same url from every chunk, so it is already a literal.
  77. const assetUrl = urlData[ASSET_URL_TYPE];
  78. // A css or html consumer drops the wrapper too, and its value carries a
  79. // placeholder only those assets are rendered with — so use the name below.
  80. if (
  81. typeof assetUrl === "string" &&
  82. !assetUrl.includes(PUBLIC_PATH_AUTO) &&
  83. !assetUrl.includes(PUBLIC_PATH_FULL_HASH)
  84. ) {
  85. return toJsStringLiteral(assetUrl);
  86. }
  87. }
  88. // The wrapper concatenates the runtime public path, or was dropped as unread —
  89. // either way the name is what the literal is built from.
  90. const filename = codeGenerationResults.getData(
  91. assetModule,
  92. runtime,
  93. "filename"
  94. );
  95. if (typeof filename !== "string") return null;
  96. return runtimeTemplate.getAnalyzableAssetUrl(
  97. consumingModule,
  98. chunkGraph,
  99. filename,
  100. runtime
  101. );
  102. };
  103. class URLDependency extends ModuleDependency {
  104. /**
  105. * Creates an instance of URLDependency.
  106. * @param {string} request request
  107. * @param {Range} range range of the arguments of new URL( |> ... <| )
  108. * @param {Range} outerRange range of the full |> new URL(...) <|
  109. * @param {boolean=} relative use relative urls instead of absolute with base uri
  110. */
  111. constructor(request, range, outerRange, relative) {
  112. super(request);
  113. this.range = range;
  114. this.outerRange = outerRange;
  115. /** @type {boolean} */
  116. this.relative = relative || false;
  117. /** @type {UsedByExports | undefined} */
  118. this.usedByExports = undefined;
  119. /** @type {true | undefined} */
  120. this.prefetch = undefined;
  121. /** @type {true | undefined} */
  122. this.preload = undefined;
  123. /** @type {"high" | "low" | "auto" | undefined} */
  124. this.fetchPriority = undefined;
  125. /** @type {string | undefined} */
  126. this.asAttribute = undefined;
  127. /** @type {string | undefined} */
  128. this.typeAttribute = undefined;
  129. /** @type {string | undefined} */
  130. this.mediaAttribute = undefined;
  131. }
  132. /**
  133. * Updates the hash with the data contributed by this instance.
  134. * @param {Hash} hash hash to be updated
  135. * @param {UpdateHashContext} context context
  136. * @returns {void}
  137. */
  138. updateHash(hash, context) {
  139. super.updateHash(hash, context);
  140. const { runtimeTemplate, runtime } = context;
  141. // Only a baked url reads the base; the runtime form is the same text under every
  142. // base, so hashing one there would cost the code generation cache for nothing.
  143. if (
  144. runtimeTemplate === undefined ||
  145. this.relative ||
  146. !runtimeTemplate.analyzableUrlReadsBaseUri()
  147. ) {
  148. return;
  149. }
  150. const base = runtimeTemplate.entryBaseUri(runtime);
  151. // Disagreeing entries keep the runtime form, different code again from the
  152. // base-less literal `undefined` bakes; an empty base must differ from both.
  153. if (base === null) hash.update("|");
  154. // Left out entirely where no entry sets one, so ordinary builds hash as before.
  155. else if (typeof base === "string") hash.update(`=${base}`);
  156. }
  157. get type() {
  158. return "new URL()";
  159. }
  160. get category() {
  161. return "url";
  162. }
  163. /**
  164. * Returns function to determine if the connection is active.
  165. * @param {ModuleGraph} moduleGraph module graph
  166. * @returns {null | false | GetConditionFn} function to determine if the connection is active
  167. */
  168. getCondition(moduleGraph) {
  169. return getDependencyUsedByExportsCondition(this, moduleGraph);
  170. }
  171. /**
  172. * Creates an ignored module.
  173. * @param {string} context context directory
  174. * @returns {Module} ignored module
  175. */
  176. createIgnoredModule(context) {
  177. return getIgnoredRawDataUrlModule();
  178. }
  179. /**
  180. * Serializes this instance into the provided serializer context.
  181. * @param {ObjectSerializerContext} context context
  182. */
  183. serialize(context) {
  184. context
  185. .write(this.outerRange)
  186. .write(this.relative)
  187. .write(this.usedByExports)
  188. .write(this.prefetch)
  189. .write(this.preload)
  190. .write(this.fetchPriority)
  191. .write(this.asAttribute)
  192. .write(this.typeAttribute)
  193. .write(this.mediaAttribute);
  194. super.serialize(context);
  195. }
  196. /**
  197. * Restores this instance from the provided deserializer context.
  198. * @param {ObjectDeserializerContext} context context
  199. */
  200. deserialize(context) {
  201. this.outerRange = context.read();
  202. const c1 = context.rest;
  203. this.relative = c1.read();
  204. const c2 = c1.rest;
  205. this.usedByExports = c2.read();
  206. const c3 = c2.rest;
  207. this.prefetch = c3.read();
  208. const c4 = c3.rest;
  209. this.preload = c4.read();
  210. const c5 = c4.rest;
  211. this.fetchPriority = c5.read();
  212. const c6 = c5.rest;
  213. this.asAttribute = c6.read();
  214. const c7 = c6.rest;
  215. this.typeAttribute = c7.read();
  216. const c8 = c7.rest;
  217. this.mediaAttribute = c8.read();
  218. super.deserialize(c8.rest);
  219. }
  220. }
  221. URLDependency.Template = class URLDependencyTemplate extends (
  222. ModuleDependency.Template
  223. ) {
  224. /**
  225. * Applies the plugin by registering its hooks on the compiler.
  226. * @param {Dependency} dependency the dependency for which the template should be applied
  227. * @param {ReplaceSource} source the current replace source which can be modified
  228. * @param {DependencyTemplateContext} templateContext the context object
  229. * @returns {void}
  230. */
  231. apply(dependency, source, templateContext) {
  232. const {
  233. chunkGraph,
  234. moduleGraph,
  235. runtimeRequirements,
  236. runtimeTemplate,
  237. codeGenerationResults,
  238. runtime
  239. } = templateContext;
  240. const dep = /** @type {URLDependency} */ (dependency);
  241. const connection = moduleGraph.getConnection(dep);
  242. // Skip rendering depending when dependency is conditional
  243. if (connection && !connection.isTargetActive(runtime)) {
  244. source.replace(
  245. dep.outerRange[0],
  246. dep.outerRange[1] - 1,
  247. "/* unused asset import */ undefined"
  248. );
  249. return;
  250. }
  251. // For ESM module output, emit the analyzable `new URL("./asset", import.meta.url)`
  252. // form (literal specifier, no runtime helpers) so other bundlers and webpack itself
  253. // can statically follow the asset. `url: "relative"` keeps the runtime form.
  254. // A prefetch/preload hint doesn't force it: the `<link>` is emitted separately at
  255. // chunk startup (`StartupAssetHintRuntimeModule`), independent of the call site.
  256. if (
  257. !dep.relative &&
  258. runtimeTemplate.supportsAnalyzable(
  259. "url",
  260. chunkGraph,
  261. templateContext.module
  262. )
  263. ) {
  264. const specifier = getAnalyzableUrlSpecifier(dep, templateContext);
  265. if (specifier !== null) {
  266. source.replace(
  267. dep.range[0],
  268. dep.range[1] - 1,
  269. `/* asset import */ ${specifier}, ${runtimeTemplate.outputOptions.importMetaName}.url`
  270. );
  271. return;
  272. }
  273. }
  274. const module = moduleGraph.getModule(dep);
  275. // A wrapper-less asset has no exports to require, so concatenate what the wrapper
  276. // would have — `AssetModulesPlugin` emulates the same for build-time execution.
  277. const wrapperLess =
  278. module !== null && !module.getSourceTypes().has(JAVASCRIPT_TYPE);
  279. const filename =
  280. wrapperLess && codeGenerationResults.has(module, runtime)
  281. ? codeGenerationResults.getData(module, runtime, "filename")
  282. : undefined;
  283. let moduleRaw;
  284. if (wrapperLess && typeof filename === "string") {
  285. runtimeRequirements.add(RuntimeGlobals.publicPath);
  286. moduleRaw = `${RuntimeGlobals.publicPath} + ${toJsStringLiteral(filename)}`;
  287. } else {
  288. runtimeRequirements.add(RuntimeGlobals.require);
  289. if (wrapperLess) runtimeRequirements.add(RuntimeGlobals.publicPath);
  290. moduleRaw = runtimeTemplate.moduleRaw({
  291. chunkGraph,
  292. module,
  293. request: dep.request,
  294. runtimeRequirements,
  295. weak: false
  296. });
  297. }
  298. if (dep.relative) {
  299. runtimeRequirements.add(RuntimeGlobals.relativeUrl);
  300. source.replace(
  301. dep.outerRange[0],
  302. dep.outerRange[1] - 1,
  303. `/* asset import */ new ${RuntimeGlobals.relativeUrl}(${moduleRaw})`
  304. );
  305. } else {
  306. runtimeRequirements.add(RuntimeGlobals.baseURI);
  307. source.replace(
  308. dep.range[0],
  309. dep.range[1] - 1,
  310. `/* asset import */ ${moduleRaw}, ${RuntimeGlobals.baseURI}`
  311. );
  312. }
  313. }
  314. };
  315. makeSerializable(URLDependency, "webpack/lib/dependencies/URLDependency");
  316. module.exports = URLDependency;