AssetGenerator.js 24 KB

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