AssetGenerator.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const mimeTypes = require("mime-types");
  7. const path = require("path");
  8. const { RawSource } = require("webpack-sources");
  9. const ConcatenationScope = require("../ConcatenationScope");
  10. const Generator = require("../Generator");
  11. const {
  12. NO_TYPES,
  13. ASSET_TYPES,
  14. ASSET_AND_JS_TYPES,
  15. ASSET_AND_JS_AND_CSS_URL_TYPES,
  16. ASSET_AND_CSS_URL_TYPES,
  17. JS_TYPES,
  18. JS_AND_CSS_URL_TYPES,
  19. CSS_URL_TYPES
  20. } = require("../ModuleSourceTypesConstants");
  21. const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
  22. const RuntimeGlobals = require("../RuntimeGlobals");
  23. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  24. const createHash = require("../util/createHash");
  25. const { makePathsRelative } = require("../util/identifier");
  26. const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
  27. /** @typedef {import("webpack-sources").Source} Source */
  28. /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorDataUrlOptions} AssetGeneratorDataUrlOptions */
  29. /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
  30. /** @typedef {import("../../declarations/WebpackOptions").AssetModuleFilename} AssetModuleFilename */
  31. /** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
  32. /** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
  33. /** @typedef {import("../Compilation")} Compilation */
  34. /** @typedef {import("../Compilation").AssetInfo} AssetInfo */
  35. /** @typedef {import("../Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */
  36. /** @typedef {import("../Compiler")} Compiler */
  37. /** @typedef {import("../Generator").GenerateContext} GenerateContext */
  38. /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
  39. /** @typedef {import("../Module")} Module */
  40. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  41. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  42. /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
  43. /** @typedef {import("../Module").SourceTypes} SourceTypes */
  44. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  45. /** @typedef {import("../NormalModule")} NormalModule */
  46. /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
  47. /** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */
  48. /** @typedef {import("../util/Hash")} Hash */
  49. /** @typedef {import("../util/createHash").Algorithm} Algorithm */
  50. /**
  51. * @template T
  52. * @template U
  53. * @param {Array<T> | Set<T>} a a
  54. * @param {Array<U> | Set<U>} b b
  55. * @returns {Array<T> & Array<U>} array
  56. */
  57. const mergeMaybeArrays = (a, b) => {
  58. const set = new Set();
  59. if (Array.isArray(a)) for (const item of a) set.add(item);
  60. else set.add(a);
  61. if (Array.isArray(b)) for (const item of b) set.add(item);
  62. else set.add(b);
  63. return Array.from(set);
  64. };
  65. /**
  66. * @template {object} T
  67. * @template {object} U
  68. * @param {TODO} a a
  69. * @param {TODO} b b
  70. * @returns {T & U} object
  71. */
  72. const mergeAssetInfo = (a, b) => {
  73. const result = { ...a, ...b };
  74. for (const key of Object.keys(a)) {
  75. if (key in b) {
  76. if (a[key] === b[key]) continue;
  77. switch (key) {
  78. case "fullhash":
  79. case "chunkhash":
  80. case "modulehash":
  81. case "contenthash":
  82. result[key] = mergeMaybeArrays(a[key], b[key]);
  83. break;
  84. case "immutable":
  85. case "development":
  86. case "hotModuleReplacement":
  87. case "javascriptModule":
  88. result[key] = a[key] || b[key];
  89. break;
  90. case "related":
  91. result[key] = mergeRelatedInfo(a[key], b[key]);
  92. break;
  93. default:
  94. throw new Error(`Can't handle conflicting asset info for ${key}`);
  95. }
  96. }
  97. }
  98. return result;
  99. };
  100. /**
  101. * @template {object} T
  102. * @template {object} U
  103. * @param {TODO} a a
  104. * @param {TODO} b b
  105. * @returns {T & U} object
  106. */
  107. const mergeRelatedInfo = (a, b) => {
  108. const result = { ...a, ...b };
  109. for (const key of Object.keys(a)) {
  110. if (key in b) {
  111. if (a[key] === b[key]) continue;
  112. result[key] = mergeMaybeArrays(a[key], b[key]);
  113. }
  114. }
  115. return result;
  116. };
  117. /**
  118. * @param {"base64" | false} encoding encoding
  119. * @param {Source} source source
  120. * @returns {string} encoded data
  121. */
  122. const encodeDataUri = (encoding, source) => {
  123. /** @type {string | undefined} */
  124. let encodedContent;
  125. switch (encoding) {
  126. case "base64": {
  127. encodedContent = source.buffer().toString("base64");
  128. break;
  129. }
  130. case false: {
  131. const content = source.source();
  132. if (typeof content !== "string") {
  133. encodedContent = content.toString("utf-8");
  134. }
  135. encodedContent = encodeURIComponent(
  136. /** @type {string} */
  137. (encodedContent)
  138. ).replace(
  139. /[!'()*]/g,
  140. character =>
  141. `%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`
  142. );
  143. break;
  144. }
  145. default:
  146. throw new Error(`Unsupported encoding '${encoding}'`);
  147. }
  148. return encodedContent;
  149. };
  150. /**
  151. * @param {string} encoding encoding
  152. * @param {string} content content
  153. * @returns {Buffer} decoded content
  154. */
  155. const decodeDataUriContent = (encoding, content) => {
  156. const isBase64 = encoding === "base64";
  157. if (isBase64) {
  158. return Buffer.from(content, "base64");
  159. }
  160. // If we can't decode return the original body
  161. try {
  162. return Buffer.from(decodeURIComponent(content), "ascii");
  163. } catch (_) {
  164. return Buffer.from(content, "ascii");
  165. }
  166. };
  167. const DEFAULT_ENCODING = "base64";
  168. class AssetGenerator extends Generator {
  169. /**
  170. * @param {ModuleGraph} moduleGraph the module graph
  171. * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
  172. * @param {AssetModuleFilename=} filename override for output.assetModuleFilename
  173. * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
  174. * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
  175. * @param {boolean=} emit generate output asset
  176. */
  177. constructor(
  178. moduleGraph,
  179. dataUrlOptions,
  180. filename,
  181. publicPath,
  182. outputPath,
  183. emit
  184. ) {
  185. super();
  186. this.dataUrlOptions = dataUrlOptions;
  187. this.filename = filename;
  188. this.publicPath = publicPath;
  189. this.outputPath = outputPath;
  190. this.emit = emit;
  191. this._moduleGraph = moduleGraph;
  192. }
  193. /**
  194. * @param {NormalModule} module module
  195. * @param {RuntimeTemplate} runtimeTemplate runtime template
  196. * @returns {string} source file name
  197. */
  198. getSourceFileName(module, runtimeTemplate) {
  199. return makePathsRelative(
  200. runtimeTemplate.compilation.compiler.context,
  201. module.matchResource || module.resource,
  202. runtimeTemplate.compilation.compiler.root
  203. ).replace(/^\.\//, "");
  204. }
  205. /**
  206. * @param {NormalModule} module module for which the bailout reason should be determined
  207. * @param {ConcatenationBailoutReasonContext} context context
  208. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  209. */
  210. getConcatenationBailoutReason(module, context) {
  211. return undefined;
  212. }
  213. /**
  214. * @param {NormalModule} module module
  215. * @returns {string} mime type
  216. */
  217. getMimeType(module) {
  218. if (typeof this.dataUrlOptions === "function") {
  219. throw new Error(
  220. "This method must not be called when dataUrlOptions is a function"
  221. );
  222. }
  223. /** @type {string | boolean | undefined} */
  224. let mimeType =
  225. /** @type {AssetGeneratorDataUrlOptions} */
  226. (this.dataUrlOptions).mimetype;
  227. if (mimeType === undefined) {
  228. const ext = path.extname(
  229. /** @type {string} */
  230. (module.nameForCondition())
  231. );
  232. if (
  233. module.resourceResolveData &&
  234. module.resourceResolveData.mimetype !== undefined
  235. ) {
  236. mimeType =
  237. module.resourceResolveData.mimetype +
  238. module.resourceResolveData.parameters;
  239. } else if (ext) {
  240. mimeType = mimeTypes.lookup(ext);
  241. if (typeof mimeType !== "string") {
  242. throw new Error(
  243. "DataUrl can't be generated automatically, " +
  244. `because there is no mimetype for "${ext}" in mimetype database. ` +
  245. 'Either pass a mimetype via "generator.mimetype" or ' +
  246. 'use type: "asset/resource" to create a resource file instead of a DataUrl'
  247. );
  248. }
  249. }
  250. }
  251. if (typeof mimeType !== "string") {
  252. throw new Error(
  253. "DataUrl can't be generated automatically. " +
  254. 'Either pass a mimetype via "generator.mimetype" or ' +
  255. 'use type: "asset/resource" to create a resource file instead of a DataUrl'
  256. );
  257. }
  258. return /** @type {string} */ (mimeType);
  259. }
  260. /**
  261. * @param {NormalModule} module module for which the code should be generated
  262. * @returns {string} DataURI
  263. */
  264. generateDataUri(module) {
  265. const source = /** @type {Source} */ (module.originalSource());
  266. let encodedSource;
  267. if (typeof this.dataUrlOptions === "function") {
  268. encodedSource = this.dataUrlOptions.call(null, source.source(), {
  269. filename: module.matchResource || module.resource,
  270. module
  271. });
  272. } else {
  273. /** @type {"base64" | false | undefined} */
  274. let encoding =
  275. /** @type {AssetGeneratorDataUrlOptions} */
  276. (this.dataUrlOptions).encoding;
  277. if (
  278. encoding === undefined &&
  279. module.resourceResolveData &&
  280. module.resourceResolveData.encoding !== undefined
  281. ) {
  282. encoding = module.resourceResolveData.encoding;
  283. }
  284. if (encoding === undefined) {
  285. encoding = DEFAULT_ENCODING;
  286. }
  287. const mimeType = this.getMimeType(module);
  288. let encodedContent;
  289. if (
  290. module.resourceResolveData &&
  291. module.resourceResolveData.encoding === encoding &&
  292. decodeDataUriContent(
  293. module.resourceResolveData.encoding,
  294. module.resourceResolveData.encodedContent
  295. ).equals(source.buffer())
  296. ) {
  297. encodedContent = module.resourceResolveData.encodedContent;
  298. } else {
  299. encodedContent = encodeDataUri(encoding, source);
  300. }
  301. encodedSource = `data:${mimeType}${
  302. encoding ? `;${encoding}` : ""
  303. },${encodedContent}`;
  304. }
  305. return encodedSource;
  306. }
  307. /**
  308. * @private
  309. * @param {NormalModule} module module for which the code should be generated
  310. * @param {GenerateContext} generateContext context for generate
  311. * @param {string} contentHash the content hash
  312. * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
  313. */
  314. _getFilenameWithInfo(
  315. module,
  316. { runtime, runtimeTemplate, chunkGraph },
  317. contentHash
  318. ) {
  319. const assetModuleFilename =
  320. this.filename ||
  321. /** @type {AssetModuleFilename} */
  322. (runtimeTemplate.outputOptions.assetModuleFilename);
  323. const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
  324. let { path: filename, info: assetInfo } =
  325. runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
  326. module,
  327. runtime,
  328. filename: sourceFilename,
  329. chunkGraph,
  330. contentHash
  331. });
  332. const originalFilename = filename;
  333. if (this.outputPath) {
  334. const { path: outputPath, info } =
  335. runtimeTemplate.compilation.getAssetPathWithInfo(this.outputPath, {
  336. module,
  337. runtime,
  338. filename: sourceFilename,
  339. chunkGraph,
  340. contentHash
  341. });
  342. filename = path.posix.join(outputPath, filename);
  343. assetInfo = mergeAssetInfo(assetInfo, info);
  344. }
  345. return { originalFilename, filename, assetInfo };
  346. }
  347. /**
  348. * @private
  349. * @param {NormalModule} module module for which the code should be generated
  350. * @param {GenerateContext} generateContext context for generate
  351. * @param {string} filename the filename
  352. * @param {AssetInfo} assetInfo the asset info
  353. * @param {string} contentHash the content hash
  354. * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
  355. */
  356. _getAssetPathWithInfo(
  357. module,
  358. { runtimeTemplate, runtime, chunkGraph, type, runtimeRequirements },
  359. filename,
  360. assetInfo,
  361. contentHash
  362. ) {
  363. const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
  364. let assetPath;
  365. if (this.publicPath !== undefined && type === "javascript") {
  366. const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
  367. this.publicPath,
  368. {
  369. module,
  370. runtime,
  371. filename: sourceFilename,
  372. chunkGraph,
  373. contentHash
  374. }
  375. );
  376. assetInfo = mergeAssetInfo(assetInfo, info);
  377. assetPath = JSON.stringify(path + filename);
  378. } else if (this.publicPath !== undefined && type === "css-url") {
  379. const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
  380. this.publicPath,
  381. {
  382. module,
  383. runtime,
  384. filename: sourceFilename,
  385. chunkGraph,
  386. contentHash
  387. }
  388. );
  389. assetInfo = mergeAssetInfo(assetInfo, info);
  390. assetPath = path + filename;
  391. } else if (type === "javascript") {
  392. // add __webpack_require__.p
  393. runtimeRequirements.add(RuntimeGlobals.publicPath);
  394. assetPath = runtimeTemplate.concatenation(
  395. { expr: RuntimeGlobals.publicPath },
  396. filename
  397. );
  398. } else if (type === "css-url") {
  399. const compilation = runtimeTemplate.compilation;
  400. const path =
  401. compilation.outputOptions.publicPath === "auto"
  402. ? CssUrlDependency.PUBLIC_PATH_AUTO
  403. : compilation.getAssetPath(
  404. /** @type {TemplatePath} */
  405. (compilation.outputOptions.publicPath),
  406. {
  407. hash: compilation.hash
  408. }
  409. );
  410. assetPath = path + filename;
  411. }
  412. return {
  413. // eslint-disable-next-line object-shorthand
  414. assetPath: /** @type {string} */ (assetPath),
  415. assetInfo: { sourceFilename, ...assetInfo }
  416. };
  417. }
  418. /**
  419. * @param {NormalModule} module module for which the code should be generated
  420. * @param {GenerateContext} generateContext context for generate
  421. * @returns {Source | null} generated code
  422. */
  423. generate(module, generateContext) {
  424. const {
  425. type,
  426. getData,
  427. runtimeTemplate,
  428. runtimeRequirements,
  429. concatenationScope
  430. } = generateContext;
  431. let content;
  432. const needContent = type === "javascript" || type === "css-url";
  433. const data = getData ? getData() : undefined;
  434. if (
  435. /** @type {BuildInfo} */
  436. (module.buildInfo).dataUrl &&
  437. needContent
  438. ) {
  439. const encodedSource = this.generateDataUri(module);
  440. content =
  441. type === "javascript" ? JSON.stringify(encodedSource) : encodedSource;
  442. if (data) {
  443. data.set("url", { [type]: content, ...data.get("url") });
  444. }
  445. } else {
  446. const hash = createHash(
  447. /** @type {Algorithm} */
  448. (runtimeTemplate.outputOptions.hashFunction)
  449. );
  450. if (runtimeTemplate.outputOptions.hashSalt) {
  451. hash.update(runtimeTemplate.outputOptions.hashSalt);
  452. }
  453. hash.update(/** @type {Source} */ (module.originalSource()).buffer());
  454. const fullHash =
  455. /** @type {string} */
  456. (hash.digest(runtimeTemplate.outputOptions.hashDigest));
  457. if (data) {
  458. data.set("fullContentHash", fullHash);
  459. }
  460. /** @type {BuildInfo} */
  461. (module.buildInfo).fullContentHash = fullHash;
  462. /** @type {string} */
  463. const contentHash = nonNumericOnlyHash(
  464. fullHash,
  465. /** @type {number} */
  466. (generateContext.runtimeTemplate.outputOptions.hashDigestLength)
  467. );
  468. if (data) {
  469. data.set("contentHash", contentHash);
  470. }
  471. const { originalFilename, filename, assetInfo } =
  472. this._getFilenameWithInfo(module, generateContext, contentHash);
  473. if (data) {
  474. data.set("filename", filename);
  475. }
  476. let { assetPath, assetInfo: newAssetInfo } = this._getAssetPathWithInfo(
  477. module,
  478. generateContext,
  479. originalFilename,
  480. assetInfo,
  481. contentHash
  482. );
  483. if (data && (type === "javascript" || type === "css-url")) {
  484. data.set("url", { [type]: assetPath, ...data.get("url") });
  485. }
  486. if (data && data.get("assetInfo")) {
  487. newAssetInfo = mergeAssetInfo(data.get("assetInfo"), newAssetInfo);
  488. }
  489. if (data) {
  490. data.set("assetInfo", newAssetInfo);
  491. }
  492. // Due to code generation caching module.buildInfo.XXX can't used to store such information
  493. // It need to be stored in the code generation results instead, where it's cached too
  494. // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
  495. /** @type {BuildInfo} */
  496. (module.buildInfo).filename = filename;
  497. /** @type {BuildInfo} */
  498. (module.buildInfo).assetInfo = newAssetInfo;
  499. content = assetPath;
  500. }
  501. if (type === "javascript") {
  502. if (concatenationScope) {
  503. concatenationScope.registerNamespaceExport(
  504. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  505. );
  506. return new RawSource(
  507. `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
  508. ConcatenationScope.NAMESPACE_OBJECT_EXPORT
  509. } = ${content};`
  510. );
  511. }
  512. runtimeRequirements.add(RuntimeGlobals.module);
  513. return new RawSource(`${RuntimeGlobals.module}.exports = ${content};`);
  514. } else if (type === "css-url") {
  515. return null;
  516. }
  517. return /** @type {Source} */ (module.originalSource());
  518. }
  519. /**
  520. * @param {NormalModule} module fresh module
  521. * @returns {SourceTypes} available types (do not mutate)
  522. */
  523. getTypes(module) {
  524. const sourceTypes = new Set();
  525. const connections = this._moduleGraph.getIncomingConnections(module);
  526. for (const connection of connections) {
  527. if (!connection.originModule) {
  528. continue;
  529. }
  530. sourceTypes.add(connection.originModule.type.split("/")[0]);
  531. }
  532. if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {
  533. if (sourceTypes) {
  534. if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
  535. return JS_AND_CSS_URL_TYPES;
  536. } else if (sourceTypes.has("javascript")) {
  537. return JS_TYPES;
  538. } else if (sourceTypes.has("css")) {
  539. return CSS_URL_TYPES;
  540. }
  541. }
  542. return NO_TYPES;
  543. }
  544. if (sourceTypes) {
  545. if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
  546. return ASSET_AND_JS_AND_CSS_URL_TYPES;
  547. } else if (sourceTypes.has("javascript")) {
  548. return ASSET_AND_JS_TYPES;
  549. } else if (sourceTypes.has("css")) {
  550. return ASSET_AND_CSS_URL_TYPES;
  551. }
  552. }
  553. return ASSET_TYPES;
  554. }
  555. /**
  556. * @param {NormalModule} module the module
  557. * @param {string=} type source type
  558. * @returns {number} estimate size of the module
  559. */
  560. getSize(module, type) {
  561. switch (type) {
  562. case ASSET_MODULE_TYPE: {
  563. const originalSource = module.originalSource();
  564. if (!originalSource) {
  565. return 0;
  566. }
  567. return originalSource.size();
  568. }
  569. default:
  570. if (module.buildInfo && module.buildInfo.dataUrl) {
  571. const originalSource = module.originalSource();
  572. if (!originalSource) {
  573. return 0;
  574. }
  575. // roughly for data url
  576. // Example: m.exports="data:image/png;base64,ag82/f+2=="
  577. // 4/3 = base64 encoding
  578. // 34 = ~ data url header + footer + rounding
  579. return originalSource.size() * 1.34 + 36;
  580. }
  581. // it's only estimated so this number is probably fine
  582. // Example: m.exports=r.p+"0123456789012345678901.ext"
  583. return 42;
  584. }
  585. }
  586. /**
  587. * @param {Hash} hash hash that will be modified
  588. * @param {UpdateHashContext} updateHashContext context for updating hash
  589. */
  590. updateHash(hash, updateHashContext) {
  591. const { module } = updateHashContext;
  592. if (
  593. /** @type {BuildInfo} */
  594. (module.buildInfo).dataUrl
  595. ) {
  596. hash.update("data-url");
  597. // this.dataUrlOptions as function should be pure and only depend on input source and filename
  598. // therefore it doesn't need to be hashed
  599. if (typeof this.dataUrlOptions === "function") {
  600. const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
  601. .ident;
  602. if (ident) hash.update(ident);
  603. } else {
  604. const dataUrlOptions =
  605. /** @type {AssetGeneratorDataUrlOptions} */
  606. (this.dataUrlOptions);
  607. if (
  608. dataUrlOptions.encoding &&
  609. dataUrlOptions.encoding !== DEFAULT_ENCODING
  610. ) {
  611. hash.update(dataUrlOptions.encoding);
  612. }
  613. if (dataUrlOptions.mimetype) hash.update(dataUrlOptions.mimetype);
  614. // computed mimetype depends only on module filename which is already part of the hash
  615. }
  616. } else {
  617. hash.update("resource");
  618. const { module, chunkGraph, runtime } = updateHashContext;
  619. const runtimeTemplate =
  620. /** @type {NonNullable<UpdateHashContext["runtimeTemplate"]>} */
  621. (updateHashContext.runtimeTemplate);
  622. const pathData = {
  623. module,
  624. runtime,
  625. filename: this.getSourceFileName(module, runtimeTemplate),
  626. chunkGraph,
  627. contentHash: runtimeTemplate.contentHashReplacement
  628. };
  629. if (typeof this.publicPath === "function") {
  630. hash.update("path");
  631. const assetInfo = {};
  632. hash.update(this.publicPath(pathData, assetInfo));
  633. hash.update(JSON.stringify(assetInfo));
  634. } else if (this.publicPath) {
  635. hash.update("path");
  636. hash.update(this.publicPath);
  637. } else {
  638. hash.update("no-path");
  639. }
  640. const assetModuleFilename =
  641. this.filename ||
  642. /** @type {AssetModuleFilename} */
  643. (runtimeTemplate.outputOptions.assetModuleFilename);
  644. const { path: filename, info } =
  645. runtimeTemplate.compilation.getAssetPathWithInfo(
  646. assetModuleFilename,
  647. pathData
  648. );
  649. hash.update(filename);
  650. hash.update(JSON.stringify(info));
  651. }
  652. }
  653. }
  654. module.exports = AssetGenerator;