AssetGenerator.js 23 KB

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