AssetGenerator.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const { RawSource } = require("webpack-sources");
  8. const ConcatenationScope = require("../ConcatenationScope");
  9. const Generator = require("../Generator");
  10. const {
  11. ASSET_AND_ASSET_URL_TYPES,
  12. ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES,
  13. ASSET_AND_JAVASCRIPT_TYPES,
  14. ASSET_TYPES,
  15. ASSET_URL_TYPE,
  16. ASSET_URL_TYPES,
  17. JAVASCRIPT_AND_ASSET_URL_TYPES,
  18. JAVASCRIPT_TYPE,
  19. JAVASCRIPT_TYPES,
  20. NO_TYPES
  21. } = require("../ModuleSourceTypeConstants");
  22. const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
  23. const RuntimeGlobals = require("../RuntimeGlobals");
  24. const { toTemplateSourceFileName } = require("../TemplatedPathPlugin");
  25. const URLDependency = require("../dependencies/URLDependency");
  26. const { getScheme } = require("../util/URLAbsoluteSpecifier");
  27. const createHash = require("../util/createHash");
  28. const { languageOfMediaType } = require("../util/dataURL");
  29. const { makePathsRelative } = require("../util/identifier");
  30. const memoize = require("../util/memoize");
  31. const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
  32. const {
  33. PUBLIC_PATH_AUTO,
  34. PUBLIC_PATH_FULL_HASH
  35. } = require("../util/publicPathPlaceholder");
  36. const { updateHashFromSource } = require("../util/source");
  37. const getMimeTypes = memoize(() => require("../util/mimeTypes"));
  38. /** @import { Source } from "webpack-sources" */
  39. /**
  40. * @import {
  41. * AssetGeneratorDataUrlOptions,
  42. * AssetGeneratorOptions,
  43. * AssetModuleFilename,
  44. * AssetModuleOutputPath,
  45. * AssetResourceGeneratorOptions,
  46. * RawPublicPath
  47. * } from "../../declarations/WebpackOptions"
  48. */
  49. /** @import ChunkGraph from "../ChunkGraph" */
  50. /** @import Compilation, { AssetInfo } from "../Compilation" */
  51. /** @import { GenerateContext, UpdateHashContext } from "../Generator" */
  52. /**
  53. * @import Module, {
  54. * NameForCondition,
  55. * ConcatenationBailoutReasonContext,
  56. * SourceType,
  57. * SourceTypes
  58. * } from "../Module"
  59. */
  60. /** @import { AssetModuleBuildInfo } from "./AssetModule" */
  61. /** @import ModuleGraph from "../ModuleGraph" */
  62. /** @import NormalModule from "../NormalModule" */
  63. /** @import RuntimeTemplate from "../RuntimeTemplate" */
  64. /** @import Hash from "../util/Hash" */
  65. /** @import { RuntimeSpec } from "../util/runtime" */
  66. /** @typedef {(source: string | Buffer, context: { filename: string, module: Module }) => string} DataUrlFunction */
  67. /**
  68. * Merges maybe arrays.
  69. * @template T
  70. * @template U
  71. * @param {null | string | T[] | Set<T> | undefined} a a
  72. * @param {null | string | U[] | Set<U> | undefined} b b
  73. * @returns {T[] & U[]} array
  74. */
  75. const mergeMaybeArrays = (a, b) => {
  76. /** @type {Set<T | U | null | undefined | string | Set<T> | Set<U>>} */
  77. const set = new Set();
  78. if (Array.isArray(a)) for (const item of a) set.add(item);
  79. else set.add(a);
  80. if (Array.isArray(b)) for (const item of b) set.add(item);
  81. else set.add(b);
  82. return /** @type {T[] & U[]} */ ([.../** @type {Set<T | U>} */ (set)]);
  83. };
  84. /**
  85. * Merges the provided values into a single result.
  86. * @param {AssetInfo} a a
  87. * @param {AssetInfo} b b
  88. * @returns {AssetInfo} object
  89. */
  90. const mergeAssetInfo = (a, b) => {
  91. /** @type {AssetInfo} */
  92. const result = { ...a, ...b };
  93. for (const key of Object.keys(a)) {
  94. if (key in b) {
  95. if (a[key] === b[key]) continue;
  96. switch (key) {
  97. case "fullhash":
  98. case "chunkhash":
  99. case "modulehash":
  100. case "contenthash":
  101. result[key] = mergeMaybeArrays(a[key], b[key]);
  102. break;
  103. case "immutable":
  104. case "development":
  105. case "hotModuleReplacement":
  106. case "javascriptModule":
  107. result[key] = a[key] || b[key];
  108. break;
  109. case "related":
  110. result[key] = mergeRelatedInfo(
  111. /** @type {NonNullable<AssetInfo["related"]>} */
  112. (a[key]),
  113. /** @type {NonNullable<AssetInfo["related"]>} */
  114. (b[key])
  115. );
  116. break;
  117. default:
  118. throw new Error(`Can't handle conflicting asset info for ${key}`);
  119. }
  120. }
  121. }
  122. return result;
  123. };
  124. /**
  125. * Merges related info.
  126. * @param {NonNullable<AssetInfo["related"]>} a a
  127. * @param {NonNullable<AssetInfo["related"]>} b b
  128. * @returns {NonNullable<AssetInfo["related"]>} object
  129. */
  130. const mergeRelatedInfo = (a, b) => {
  131. const result = { ...a, ...b };
  132. for (const key of Object.keys(a)) {
  133. if (key in b) {
  134. if (a[key] === b[key]) continue;
  135. result[key] = mergeMaybeArrays(a[key], b[key]);
  136. }
  137. }
  138. return result;
  139. };
  140. /**
  141. * Encodes the provided encoding.
  142. * @param {"base64" | false} encoding encoding
  143. * @param {Source} source source
  144. * @returns {string} encoded data
  145. */
  146. const encodeDataUri = (encoding, source) => {
  147. /** @type {string | undefined} */
  148. let encodedContent;
  149. switch (encoding) {
  150. case "base64": {
  151. encodedContent = source.buffer().toString("base64");
  152. break;
  153. }
  154. case false: {
  155. const content = source.source();
  156. encodedContent =
  157. typeof content === "string" ? content : content.toString("utf8");
  158. encodedContent = encodeURIComponent(encodedContent).replace(
  159. /[!'()*]/g,
  160. (character) =>
  161. `%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`
  162. );
  163. break;
  164. }
  165. default:
  166. throw new Error(`Unsupported encoding '${encoding}'`);
  167. }
  168. return encodedContent;
  169. };
  170. /**
  171. * Decodes data uri content.
  172. * @param {"base64" | false} encoding encoding
  173. * @param {string} content content
  174. * @returns {Buffer} decoded content
  175. */
  176. const decodeDataUriContent = (encoding, content) => {
  177. const isBase64 = encoding === "base64";
  178. if (isBase64) {
  179. return Buffer.from(content, "base64");
  180. }
  181. // If we can't decode return the original body
  182. try {
  183. return Buffer.from(decodeURIComponent(content), "utf8");
  184. } catch (_) {
  185. return Buffer.from(content, "utf8");
  186. }
  187. };
  188. const DEFAULT_ENCODING = "base64";
  189. class AssetGenerator extends Generator {
  190. /**
  191. * Creates an instance of AssetGenerator.
  192. * @param {ModuleGraph} moduleGraph the module graph
  193. * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
  194. * @param {AssetModuleFilename=} filename override for output.assetModuleFilename
  195. * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
  196. * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
  197. * @param {boolean=} emit generate output asset
  198. * @param {Compilation=} compilation the compilation (`getTypes` has no generate context, so the analyzable-output decision reads `output.module`/`output.publicPath` from here)
  199. */
  200. constructor(
  201. moduleGraph,
  202. dataUrlOptions,
  203. filename,
  204. publicPath,
  205. outputPath,
  206. emit,
  207. compilation
  208. ) {
  209. super();
  210. /** @type {AssetGeneratorOptions["dataUrl"] | undefined} */
  211. this.dataUrlOptions = dataUrlOptions;
  212. /** @type {AssetModuleFilename | undefined} */
  213. this.filename = filename;
  214. /** @type {RawPublicPath | undefined} */
  215. this.publicPath = publicPath;
  216. /** @type {AssetModuleOutputPath | undefined} */
  217. this.outputPath = outputPath;
  218. /** @type {boolean | undefined} */
  219. this.emit = emit;
  220. /** @type {Compilation | undefined} */
  221. this._compilation = compilation;
  222. /** @type {ModuleGraph} */
  223. this._moduleGraph = moduleGraph;
  224. }
  225. /**
  226. * Gets source file name.
  227. * @param {NormalModule} module module
  228. * @param {RuntimeTemplate} runtimeTemplate runtime template
  229. * @returns {string} source file name
  230. */
  231. static getSourceFileName(module, runtimeTemplate) {
  232. return makePathsRelative(
  233. runtimeTemplate.compilation.compiler.context,
  234. /** @type {string} */
  235. (module.getResource()),
  236. runtimeTemplate.compilation.compiler.root
  237. ).replace(/^\.\//, "");
  238. }
  239. /**
  240. * Gets the source file name as seen by a filename template. With
  241. * `experiments.futureDefaults` (webpack 6 default) `[path]` and `[file]` of a
  242. * module outside of `context` behave like `[containedpath]`/`[containedfile]`,
  243. * so they can't emit the asset outside of `output.path`.
  244. * @param {NormalModule} module module
  245. * @param {RuntimeTemplate} runtimeTemplate runtime template
  246. * @returns {string} source file name for the filename template
  247. */
  248. static getTemplateSourceFileName(module, runtimeTemplate) {
  249. return toTemplateSourceFileName(
  250. AssetGenerator.getSourceFileName(module, runtimeTemplate),
  251. runtimeTemplate.compilation
  252. );
  253. }
  254. /**
  255. * Gets full content hash.
  256. * @param {NormalModule} module module
  257. * @param {RuntimeTemplate} runtimeTemplate runtime template
  258. * @returns {[string, string]} return full hash and non-numeric full hash
  259. */
  260. static getFullContentHash(module, runtimeTemplate) {
  261. const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
  262. if (runtimeTemplate.outputOptions.hashSalt) {
  263. hash.update(runtimeTemplate.outputOptions.hashSalt);
  264. }
  265. const source = module.originalSource();
  266. if (source) {
  267. updateHashFromSource(hash, source);
  268. }
  269. if (module.error) {
  270. hash.update(module.error.toString());
  271. }
  272. const fullContentHash = hash.digest(
  273. runtimeTemplate.outputOptions.hashDigest
  274. );
  275. const contentHash = nonNumericOnlyHash(
  276. fullContentHash,
  277. runtimeTemplate.outputOptions.hashDigestLength
  278. );
  279. return [fullContentHash, contentHash];
  280. }
  281. /**
  282. * Gets filename with info.
  283. * @param {NormalModule} module module for which the code should be generated
  284. * @param {Pick<AssetResourceGeneratorOptions, "filename" | "outputPath">} generatorOptions generator options
  285. * @param {{ runtime: RuntimeSpec, runtimeTemplate: RuntimeTemplate, chunkGraph: ChunkGraph }} generateContext context for generate
  286. * @param {string} contentHash the content hash
  287. * @param {string=} fullContentHash untruncated content hash, so `[contenthash:<digest>]` re-encodes from full entropy
  288. * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
  289. */
  290. static getFilenameWithInfo(
  291. module,
  292. generatorOptions,
  293. { runtime, runtimeTemplate, chunkGraph },
  294. contentHash,
  295. fullContentHash
  296. ) {
  297. const assetModuleFilename =
  298. generatorOptions.filename ||
  299. runtimeTemplate.outputOptions.assetModuleFilename;
  300. // Both templates decide where the asset is written, so they see the
  301. // contained path.
  302. const templateSourceFilename = AssetGenerator.getTemplateSourceFileName(
  303. module,
  304. runtimeTemplate
  305. );
  306. let { path: filename, info: assetInfo } =
  307. runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
  308. module,
  309. runtime,
  310. filename: templateSourceFilename,
  311. chunkGraph,
  312. contentHash,
  313. contentHashFull: fullContentHash
  314. });
  315. const originalFilename = filename;
  316. if (generatorOptions.outputPath) {
  317. const { path: outputPath, info } =
  318. runtimeTemplate.compilation.getAssetPathWithInfo(
  319. generatorOptions.outputPath,
  320. {
  321. module,
  322. runtime,
  323. filename: templateSourceFilename,
  324. chunkGraph,
  325. contentHash,
  326. contentHashFull: fullContentHash
  327. }
  328. );
  329. filename = path.posix.join(outputPath, filename);
  330. assetInfo = mergeAssetInfo(assetInfo, info);
  331. }
  332. return { originalFilename, filename, assetInfo };
  333. }
  334. /**
  335. * Gets asset path with info.
  336. * @param {NormalModule} module module for which the code should be generated
  337. * @param {Pick<AssetResourceGeneratorOptions, "publicPath">} generatorOptions generator options
  338. * @param {GenerateContext} generateContext context for generate
  339. * @param {string} filename the filename
  340. * @param {AssetInfo} assetInfo the asset info
  341. * @param {string} contentHash the content hash
  342. * @param {string=} fullContentHash untruncated content hash, so `[contenthash:<digest>]` re-encodes from full entropy
  343. * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
  344. */
  345. static getAssetPathWithInfo(
  346. module,
  347. generatorOptions,
  348. { runtime, runtimeTemplate, type, chunkGraph, runtimeRequirements },
  349. filename,
  350. assetInfo,
  351. contentHash,
  352. fullContentHash
  353. ) {
  354. // A public path is a url prefix, it doesn't decide where the asset is
  355. // written, so `[path]` stays the real path of the source file here.
  356. const sourceFilename = AssetGenerator.getSourceFileName(
  357. module,
  358. runtimeTemplate
  359. );
  360. /** @type {undefined | string} */
  361. let assetPath;
  362. if (generatorOptions.publicPath !== undefined && type === JAVASCRIPT_TYPE) {
  363. const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
  364. generatorOptions.publicPath,
  365. {
  366. module,
  367. runtime,
  368. filename: sourceFilename,
  369. chunkGraph,
  370. contentHash,
  371. contentHashFull: fullContentHash
  372. }
  373. );
  374. assetInfo = mergeAssetInfo(assetInfo, info);
  375. assetPath = JSON.stringify(path + filename);
  376. } else if (
  377. generatorOptions.publicPath !== undefined &&
  378. type === ASSET_URL_TYPE
  379. ) {
  380. const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
  381. generatorOptions.publicPath,
  382. {
  383. module,
  384. runtime,
  385. filename: sourceFilename,
  386. chunkGraph,
  387. contentHash,
  388. contentHashFull: fullContentHash
  389. }
  390. );
  391. assetInfo = mergeAssetInfo(assetInfo, info);
  392. assetPath = path + filename;
  393. } else if (type === JAVASCRIPT_TYPE) {
  394. // `auto` reads `.p` as this asset's own directory url, which the absolute
  395. // `href` spells too; a complete url of its own is already the whole string.
  396. const constant = runtimeTemplate.constantPublicPath();
  397. const whole =
  398. constant !== undefined &&
  399. (constant.startsWith("//") || getScheme(constant) !== undefined);
  400. const analyzable =
  401. (runtimeTemplate.outputOptions.publicPath === "auto" || whole) &&
  402. runtimeTemplate.supportsAnalyzable("url", chunkGraph, module)
  403. ? runtimeTemplate.getAnalyzableAssetUrl(
  404. module,
  405. chunkGraph,
  406. filename,
  407. runtime
  408. )
  409. : null;
  410. if (analyzable !== null) {
  411. assetPath = whole
  412. ? analyzable
  413. : `${runtimeTemplate.importMetaUrl(analyzable)}.href`;
  414. } else {
  415. // add __webpack_require__.p
  416. runtimeRequirements.add(RuntimeGlobals.publicPath);
  417. assetPath = runtimeTemplate.concatenation(
  418. { expr: RuntimeGlobals.publicPath },
  419. filename
  420. );
  421. }
  422. } else if (type === ASSET_URL_TYPE) {
  423. const compilation = runtimeTemplate.compilation;
  424. const path =
  425. compilation.outputOptions.publicPath === "auto"
  426. ? PUBLIC_PATH_AUTO
  427. : compilation.getAssetPath(compilation.outputOptions.publicPath, {
  428. hash: compilation.hash || `${PUBLIC_PATH_FULL_HASH}0__`,
  429. hashWithLength: (length) =>
  430. compilation.hash
  431. ? compilation.hash.slice(0, length)
  432. : `${PUBLIC_PATH_FULL_HASH}${length}__`
  433. });
  434. assetPath = path + filename;
  435. }
  436. return {
  437. assetPath: /** @type {string} */ (assetPath),
  438. assetInfo: { sourceFilename, ...assetInfo }
  439. };
  440. }
  441. /**
  442. * Returns the reason this module cannot be concatenated, when one exists.
  443. * @param {NormalModule} module module for which the bailout reason should be determined
  444. * @param {ConcatenationBailoutReasonContext} context context
  445. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  446. */
  447. getConcatenationBailoutReason(module, context) {
  448. return undefined;
  449. }
  450. /**
  451. * Returns mime type.
  452. * @param {NormalModule} module module
  453. * @returns {string} mime type
  454. */
  455. getMimeType(module) {
  456. if (typeof this.dataUrlOptions === "function") {
  457. throw new Error(
  458. "This method must not be called when dataUrlOptions is a function"
  459. );
  460. }
  461. /** @type {string | undefined} */
  462. let mimeType =
  463. /** @type {AssetGeneratorDataUrlOptions} */
  464. (this.dataUrlOptions).mimetype;
  465. if (mimeType === undefined) {
  466. const ext = path.extname(
  467. /** @type {NameForCondition} */
  468. (module.nameForCondition())
  469. );
  470. if (
  471. module.resourceResolveData &&
  472. module.resourceResolveData.mimetype !== undefined
  473. ) {
  474. mimeType =
  475. module.resourceResolveData.mimetype +
  476. module.resourceResolveData.parameters;
  477. } else if (ext) {
  478. mimeType = getMimeTypes().lookup(ext);
  479. if (typeof mimeType !== "string") {
  480. throw new Error(
  481. "DataUrl can't be generated automatically, " +
  482. `because there is no mimetype for "${ext}" in mimetype database. ` +
  483. 'Either pass a mimetype via "generator.mimetype" or ' +
  484. 'use type: "asset/resource" to create a resource file instead of a DataUrl'
  485. );
  486. }
  487. }
  488. }
  489. if (typeof mimeType !== "string") {
  490. throw new Error(
  491. "DataUrl can't be generated automatically. " +
  492. 'Either pass a mimetype via "generator.mimetype" or ' +
  493. 'use type: "asset/resource" to create a resource file instead of a DataUrl'
  494. );
  495. }
  496. return /** @type {string} */ (mimeType);
  497. }
  498. /**
  499. * Offer the payload to `renderEmbeddedSource` before it is encoded, so the
  500. * encoding covers what came back. Declines what names no language, and a
  501. * `dataUrl` function, which is handed the source itself.
  502. * @param {NormalModule} module the module holding the payload
  503. * @param {GenerateContext["runtimeTemplate"]} runtimeTemplate runtime template
  504. * @returns {Source | undefined} the rendered payload, when one came back
  505. */
  506. _renderEmbedded(module, runtimeTemplate) {
  507. if (!runtimeTemplate || typeof this.dataUrlOptions === "function") {
  508. return undefined;
  509. }
  510. // Untapped, nothing can change: no mime lookup and no copy of the payload.
  511. const { compilation } = runtimeTemplate;
  512. if (compilation.hooks.renderEmbeddedSource.taps.length === 0) {
  513. return undefined;
  514. }
  515. const type = languageOfMediaType(this.getMimeType(module));
  516. if (type === undefined) return undefined;
  517. // An asset's source holds bytes; the hook is offered text, and the language
  518. // above is only ever one written as text.
  519. const content = /** @type {Source} */ (module.originalSource()).source();
  520. return compilation._resolveEmbeddedSource(
  521. new RawSource(
  522. typeof content === "string" ? content : content.toString("utf8")
  523. ),
  524. { type, hostType: JAVASCRIPT_TYPE, module }
  525. );
  526. }
  527. /**
  528. * Generates data uri.
  529. * @param {NormalModule} module module for which the code should be generated
  530. * @param {Source=} renderedSource the payload to encode, when a hook rendered it
  531. * @returns {string} DataURI
  532. */
  533. generateDataUri(module, renderedSource) {
  534. const source =
  535. renderedSource || /** @type {Source} */ (module.originalSource());
  536. /** @type {string} */
  537. let encodedSource;
  538. if (typeof this.dataUrlOptions === "function") {
  539. encodedSource = this.dataUrlOptions.call(null, source.source(), {
  540. filename: /** @type {string} */ (module.getResource()),
  541. module
  542. });
  543. } else {
  544. let encoding =
  545. /** @type {AssetGeneratorDataUrlOptions} */
  546. (this.dataUrlOptions).encoding;
  547. if (
  548. encoding === undefined &&
  549. module.resourceResolveData &&
  550. module.resourceResolveData.encoding !== undefined
  551. ) {
  552. encoding = module.resourceResolveData.encoding;
  553. }
  554. if (encoding === undefined) {
  555. encoding = DEFAULT_ENCODING;
  556. }
  557. const mimeType = this.getMimeType(module);
  558. /** @type {string} */
  559. let encodedContent;
  560. if (
  561. module.resourceResolveData &&
  562. module.resourceResolveData.encoding === encoding &&
  563. decodeDataUriContent(
  564. module.resourceResolveData.encoding,
  565. /** @type {string} */ (module.resourceResolveData.encodedContent)
  566. ).equals(source.buffer())
  567. ) {
  568. encodedContent =
  569. /** @type {string} */
  570. (module.resourceResolveData.encodedContent);
  571. } else {
  572. encodedContent = encodeDataUri(
  573. /** @type {"base64" | false} */ (encoding),
  574. source
  575. );
  576. }
  577. encodedSource = `data:${mimeType}${
  578. encoding ? `;${encoding}` : ""
  579. },${encodedContent}`;
  580. }
  581. return encodedSource;
  582. }
  583. /**
  584. * Generates generated code for this runtime module.
  585. * @param {NormalModule} module module for which the code should be generated
  586. * @param {GenerateContext} generateContext context for generate
  587. * @returns {Source | null} generated code
  588. */
  589. generate(module, generateContext) {
  590. const {
  591. type,
  592. getData,
  593. runtimeTemplate,
  594. runtimeRequirements,
  595. concatenationScope
  596. } = generateContext;
  597. /** @type {string} */
  598. let content;
  599. const needContent = type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE;
  600. const data = getData ? getData() : undefined;
  601. if (
  602. /** @type {AssetModuleBuildInfo} */
  603. (module.buildInfo).dataUrl &&
  604. needContent
  605. ) {
  606. // A payload bound for CSS or HTML is offered by their serializers, which
  607. // see the `url()` it ends up in; only the JavaScript one is ours.
  608. const encodedSource = this.generateDataUri(
  609. module,
  610. type === JAVASCRIPT_TYPE
  611. ? this._renderEmbedded(module, runtimeTemplate)
  612. : undefined
  613. );
  614. content =
  615. type === JAVASCRIPT_TYPE
  616. ? JSON.stringify(encodedSource)
  617. : encodedSource;
  618. if (data) {
  619. data.set("url", { ...data.get("url"), [type]: content });
  620. }
  621. } else {
  622. const [fullContentHash, contentHash] = AssetGenerator.getFullContentHash(
  623. module,
  624. runtimeTemplate
  625. );
  626. if (data) {
  627. data.set("fullContentHash", fullContentHash);
  628. data.set("contentHash", contentHash);
  629. }
  630. /** @type {AssetModuleBuildInfo} */
  631. (module.buildInfo).fullContentHash = fullContentHash;
  632. const { originalFilename, filename, assetInfo } =
  633. AssetGenerator.getFilenameWithInfo(
  634. module,
  635. { filename: this.filename, outputPath: this.outputPath },
  636. generateContext,
  637. contentHash,
  638. fullContentHash
  639. );
  640. if (data) {
  641. data.set("filename", filename);
  642. }
  643. let { assetPath, assetInfo: newAssetInfo } =
  644. AssetGenerator.getAssetPathWithInfo(
  645. module,
  646. { publicPath: this.publicPath },
  647. generateContext,
  648. originalFilename,
  649. assetInfo,
  650. contentHash,
  651. fullContentHash
  652. );
  653. if (data && (type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE)) {
  654. data.set("url", { ...data.get("url"), [type]: assetPath });
  655. }
  656. if (data) {
  657. const oldAssetInfo = data.get("assetInfo");
  658. if (oldAssetInfo) {
  659. newAssetInfo = mergeAssetInfo(oldAssetInfo, newAssetInfo);
  660. }
  661. }
  662. if (data) {
  663. data.set("assetInfo", newAssetInfo);
  664. }
  665. // Due to code generation caching module.buildInfo.XXX can't used to store such information
  666. // It need to be stored in the code generation results instead, where it's cached too
  667. // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
  668. /** @type {AssetModuleBuildInfo} */
  669. (module.buildInfo).filename = filename;
  670. /** @type {AssetModuleBuildInfo} */
  671. (module.buildInfo).assetInfo = newAssetInfo;
  672. content = assetPath;
  673. }
  674. if (type === JAVASCRIPT_TYPE) {
  675. if (concatenationScope) {
  676. concatenationScope.registerNamespaceExport(
  677. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  678. );
  679. return new RawSource(
  680. `${runtimeTemplate.renderConst()} ${
  681. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  682. } = ${content};`
  683. );
  684. }
  685. runtimeRequirements.add(RuntimeGlobals.module);
  686. return new RawSource(`${module.moduleArgument}.exports = ${content};`);
  687. } else if (type === ASSET_URL_TYPE) {
  688. return null;
  689. }
  690. return /** @type {Source} */ (module.originalSource());
  691. }
  692. /**
  693. * Generates fallback output for the provided error condition.
  694. * @param {Error} error the error
  695. * @param {NormalModule} module module for which the code should be generated
  696. * @param {GenerateContext} generateContext context for generate
  697. * @returns {Source | null} generated code
  698. */
  699. generateError(error, module, generateContext) {
  700. switch (generateContext.type) {
  701. case "asset": {
  702. return new RawSource(error.message);
  703. }
  704. case JAVASCRIPT_TYPE: {
  705. return new RawSource(Generator.throwBuildErrorCode(error));
  706. }
  707. default:
  708. return null;
  709. }
  710. }
  711. /**
  712. * Returns whether the effective publicPath yields a chunk-independent absolute
  713. * url (scheme-relative, protocol-absolute, or root-absolute) with no hash tokens.
  714. * Only then is a `new URL` literal correct for every chunk the asset is shared by,
  715. * so the `module.exports` wrapper can be dropped in favor of the `asset-url` type.
  716. * @returns {boolean} true when the publicPath is a token-free absolute string
  717. */
  718. _hasAbsolutePublicPath() {
  719. const publicPath =
  720. this.publicPath !== undefined
  721. ? this.publicPath
  722. : this._compilation && this._compilation.outputOptions.publicPath;
  723. return (
  724. typeof publicPath === "string" &&
  725. !publicPath.includes("[") &&
  726. (publicPath.startsWith("/") ||
  727. /^[a-z][a-z\d+\-.]*:\/\//i.test(publicPath))
  728. );
  729. }
  730. /**
  731. * Returns the source types available for this module.
  732. * @param {NormalModule} module fresh module
  733. * @returns {SourceTypes} available types (do not mutate)
  734. */
  735. getTypes(module) {
  736. const connections = this._moduleGraph.getIncomingConnections(module);
  737. // One that emits nothing has no name to build a literal from, so expose its url.
  738. const emits =
  739. !(
  740. module.buildInfo &&
  741. /** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
  742. ) && this.emit !== false;
  743. // Only analyzable ESM output can drop a bare `new URL()` to an asset-url; without
  744. // it every JS consumer needs the wrapper, so skip the per-connection dependency
  745. // probe entirely and keep the pre-existing cheap path. Must match the condition
  746. // `URLDependency` uses to emit the literal, or the wrapper is dropped while the
  747. // call site still requires the module — except where the fallback spells the name
  748. // itself (`.p + <file>`), which needs no wrapper to read it out of.
  749. // Build-time execution instead emulates the wrapper (`AssetModulesPlugin`).
  750. const isModule = Boolean(
  751. this._compilation &&
  752. this._compilation.runtimeTemplate.supportsAnalyzable(
  753. emits ? "url-inline" : "url"
  754. )
  755. );
  756. // Collapse the incoming origin types into flags instead of a Set of prefixes:
  757. // on assets shared by many modules this loop runs millions of times, so avoid
  758. // the per-connection `split("/")` allocation and stop early when the result is fixed.
  759. let hasOrigin = false;
  760. // JS origin that needs the `module.exports = …` wrapper (not a `new URL` ref).
  761. let hasOtherJs = false;
  762. // JS origin via `new URL(…, import.meta.url)` — droppable to `asset-url` in ESM.
  763. let hasUrlJs = false;
  764. // A manifest embeds a bare URL string (like css/html), so its icons must
  765. // resolve to `ASSET_URL_TYPE` rather than the JS runtime form.
  766. let hasUrl = false;
  767. for (const connection of connections) {
  768. const originModule = connection.originModule;
  769. if (!originModule) {
  770. continue;
  771. }
  772. hasOrigin = true;
  773. // The dependency says how the asset is read, so a reference added later answers
  774. // for itself; concatenation re-points the origin module, so it cannot.
  775. const dependency = connection.dependency;
  776. if (dependency && dependency.referencedSourceType === ASSET_URL_TYPE) {
  777. hasUrl = true;
  778. } else if (
  779. // Only a bare `new URL()` becomes the analyzable literal; a relative ref
  780. // keeps the runtime form and still needs the wrapper. Prefetch/preload
  781. // refs don't: their `<link>` is emitted at chunk startup, which resolves
  782. // the href from the asset filename when there is no wrapper.
  783. isModule &&
  784. dependency instanceof URLDependency &&
  785. !dependency.relative
  786. ) {
  787. hasUrlJs = true;
  788. } else {
  789. hasOtherJs = true;
  790. }
  791. // Once a non-URL JS consumer and a url consumer coexist the result is fixed.
  792. if (hasOtherJs && hasUrl) break;
  793. }
  794. // Every javascript consumer names the file itself, so nothing reads the wrapper.
  795. // Only an absolute public path settles one url for all of them to share.
  796. const jsWrapperUnused = isModule && hasUrlJs && !hasOtherJs;
  797. const jsAsAssetUrl =
  798. jsWrapperUnused && (!emits || this._hasAbsolutePublicPath());
  799. // JS wrapper is needed for non-URL JS consumers, or URL consumers still reading it.
  800. const wantJs = hasOtherJs || (hasUrlJs && !jsWrapperUnused);
  801. const wantUrl = hasUrl || jsAsAssetUrl;
  802. if (!emits) {
  803. if (hasOrigin) {
  804. if (wantJs && wantUrl) {
  805. return JAVASCRIPT_AND_ASSET_URL_TYPES;
  806. } else if (wantUrl) {
  807. return ASSET_URL_TYPES;
  808. }
  809. return JAVASCRIPT_TYPES;
  810. }
  811. return NO_TYPES;
  812. }
  813. if (hasOrigin) {
  814. if (wantJs && wantUrl) {
  815. return ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES;
  816. } else if (wantUrl) {
  817. return ASSET_AND_ASSET_URL_TYPES;
  818. } else if (jsWrapperUnused) {
  819. // Every javascript consumer names the file itself, so nothing reads it.
  820. return ASSET_TYPES;
  821. }
  822. return ASSET_AND_JAVASCRIPT_TYPES;
  823. }
  824. return ASSET_TYPES;
  825. }
  826. /**
  827. * @returns {boolean} whether getTypes() depends on the module's incoming connections
  828. */
  829. getTypesDependOnIncomingConnections() {
  830. return true;
  831. }
  832. /**
  833. * Returns the estimated size for the requested source type.
  834. * @param {NormalModule} module the module
  835. * @param {SourceType=} type source type
  836. * @returns {number} estimate size of the module
  837. */
  838. getSize(module, type) {
  839. switch (type) {
  840. case ASSET_MODULE_TYPE: {
  841. const originalSource = module.originalSource();
  842. if (!originalSource) {
  843. return 0;
  844. }
  845. return originalSource.size();
  846. }
  847. default:
  848. if (
  849. module.buildInfo &&
  850. /** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
  851. ) {
  852. const originalSource = module.originalSource();
  853. if (!originalSource) {
  854. return 0;
  855. }
  856. // roughly for data url
  857. // Example: m.exports="data:image/png;base64,ag82/f+2=="
  858. // 4/3 = base64 encoding
  859. // 34 = ~ data url header + footer + rounding
  860. return originalSource.size() * 1.34 + 36;
  861. }
  862. // it's only estimated so this number is probably fine
  863. // Example: m.exports=r.p+"0123456789012345678901.ext"
  864. return 42;
  865. }
  866. }
  867. /**
  868. * Updates the hash with the data contributed by this instance.
  869. * @param {Hash} hash hash that will be modified
  870. * @param {UpdateHashContext} updateHashContext context for updating hash
  871. */
  872. updateHash(hash, updateHashContext) {
  873. const { module } = updateHashContext;
  874. if (
  875. /** @type {AssetModuleBuildInfo} */
  876. (module.buildInfo).dataUrl
  877. ) {
  878. hash.update("data-url");
  879. // What a tap varies on has to reach the key, or a changed option replays.
  880. const { runtimeTemplate } = updateHashContext;
  881. if (
  882. runtimeTemplate &&
  883. runtimeTemplate.compilation.hooks.renderEmbeddedSource.taps.length > 0
  884. ) {
  885. runtimeTemplate.compilation.hooks.embeddedSourceHash.call(module, hash);
  886. }
  887. // this.dataUrlOptions as function should be pure and only depend on input source and filename
  888. // therefore it doesn't need to be hashed
  889. if (typeof this.dataUrlOptions === "function") {
  890. const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
  891. .ident;
  892. if (ident) hash.update(ident);
  893. } else {
  894. const dataUrlOptions =
  895. /** @type {AssetGeneratorDataUrlOptions} */
  896. (this.dataUrlOptions);
  897. if (
  898. dataUrlOptions.encoding &&
  899. dataUrlOptions.encoding !== DEFAULT_ENCODING
  900. ) {
  901. hash.update(dataUrlOptions.encoding);
  902. }
  903. if (dataUrlOptions.mimetype) hash.update(dataUrlOptions.mimetype);
  904. // computed mimetype depends only on module filename which is already part of the hash
  905. }
  906. } else {
  907. hash.update("resource");
  908. const { module, chunkGraph, runtime } = updateHashContext;
  909. const runtimeTemplate =
  910. /** @type {NonNullable<UpdateHashContext["runtimeTemplate"]>} */
  911. (updateHashContext.runtimeTemplate);
  912. const pathData = {
  913. module,
  914. runtime,
  915. filename: AssetGenerator.getSourceFileName(module, runtimeTemplate),
  916. chunkGraph,
  917. contentHash: runtimeTemplate.contentHashReplacement
  918. };
  919. if (typeof this.publicPath === "function") {
  920. hash.update("path");
  921. const assetInfo = {};
  922. hash.update(this.publicPath(pathData, assetInfo));
  923. hash.update(JSON.stringify(assetInfo));
  924. } else if (this.publicPath) {
  925. hash.update("path");
  926. hash.update(this.publicPath);
  927. } else {
  928. hash.update("no-path");
  929. }
  930. const assetModuleFilename =
  931. this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
  932. // Hash what generation emits: the filename template sees the contained
  933. // path, unlike the public path above.
  934. const { path: filename, info } =
  935. runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
  936. module,
  937. runtime,
  938. filename: AssetGenerator.getTemplateSourceFileName(
  939. module,
  940. runtimeTemplate
  941. ),
  942. chunkGraph,
  943. contentHash: runtimeTemplate.contentHashReplacement
  944. });
  945. hash.update(filename);
  946. hash.update(JSON.stringify(info));
  947. }
  948. }
  949. }
  950. module.exports = AssetGenerator;