WebManifestGenerator.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const { RawSource, ReplaceSource } = require("webpack-sources");
  7. const { ASSET_URL_TYPE } = require("../ModuleSourceTypeConstants");
  8. const createHash = require("../util/createHash");
  9. const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
  10. const { PUBLIC_PATH_AUTO } = require("../util/publicPathPlaceholder");
  11. const AssetGenerator = require("./AssetGenerator");
  12. /** @import { Source } from "webpack-sources" */
  13. /** @import CodeGenerationResults from "../CodeGenerationResults" */
  14. /** @import { DependencyConstructor } from "../Compilation" */
  15. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  16. /** @import { GenerateContext } from "../Generator" */
  17. /** @import { SourceType, SourceTypes } from "../Module" */
  18. /** @import ModuleGraph from "../ModuleGraph" */
  19. /** @import NormalModule from "../NormalModule" */
  20. const WEBMANIFEST_TYPE = "webmanifest";
  21. const WEBMANIFEST_TYPES = new Set([WEBMANIFEST_TYPE]);
  22. const WEBMANIFEST_AND_ASSET_URL_TYPES = new Set([
  23. WEBMANIFEST_TYPE,
  24. ASSET_URL_TYPE
  25. ]);
  26. /**
  27. * A Web App Manifest is a "typed asset": a variant of `AssetGenerator` (reusing
  28. * its filename/hash and `ASSET_URL_TYPE` machinery) whose emitted content is the
  29. * raw JSON with each icon `src` rewritten to its bundled asset URL. The manifest
  30. * itself is emitted — with its own URL exposed to the referencing
  31. * `<link rel="manifest">` — by `AssetModulesPlugin`'s `renderManifest`, which
  32. * resolves the per-file public path (like the CSS/HTML plugins).
  33. */
  34. class WebManifestGenerator extends AssetGenerator {
  35. /**
  36. * @param {ModuleGraph} moduleGraph the module graph
  37. */
  38. constructor(moduleGraph) {
  39. super(moduleGraph, undefined, undefined, undefined, undefined, true);
  40. }
  41. /**
  42. * Returns the source types available for this module.
  43. * @param {NormalModule} module fresh module
  44. * @returns {SourceTypes} available types (do not mutate)
  45. */
  46. getTypes(module) {
  47. // Expose the manifest's own URL only when it is referenced (from HTML).
  48. const connections = this._moduleGraph.getIncomingConnections(module);
  49. const referenced = !connections[Symbol.iterator]().next().done;
  50. return referenced ? WEBMANIFEST_AND_ASSET_URL_TYPES : WEBMANIFEST_TYPES;
  51. }
  52. /**
  53. * Returns the estimated size for the requested source type.
  54. * @param {NormalModule} module the module
  55. * @param {SourceType=} type source type
  56. * @returns {number} estimate size of the module
  57. */
  58. getSize(module, type) {
  59. const originalSource = module.originalSource();
  60. return originalSource ? originalSource.size() : 0;
  61. }
  62. /**
  63. * Rewrites each icon `src` in the raw manifest to its emitted asset URL.
  64. * @param {NormalModule} module the manifest module
  65. * @param {GenerateContext} generateContext context for generate
  66. * @returns {string} the rewritten manifest content
  67. */
  68. _rewrite(module, generateContext) {
  69. const originalSource = /** @type {Source} */ (module.originalSource());
  70. const source = new ReplaceSource(originalSource);
  71. // `HtmlSourceDependency.Template` only reads `moduleGraph`/`runtimeTemplate`/
  72. // `codeGenerationResults`; the rest is filled to satisfy the context shape.
  73. /** @type {DependencyTemplateContext} */
  74. const templateContext = {
  75. runtimeTemplate: generateContext.runtimeTemplate,
  76. dependencyTemplates: generateContext.dependencyTemplates,
  77. moduleGraph: generateContext.moduleGraph,
  78. chunkGraph: generateContext.chunkGraph,
  79. module,
  80. runtime: generateContext.runtime,
  81. runtimeRequirements: generateContext.runtimeRequirements,
  82. concatenationScope: generateContext.concatenationScope,
  83. codeGenerationResults:
  84. /** @type {CodeGenerationResults} */
  85. (generateContext.codeGenerationResults),
  86. initFragments: [],
  87. chunkInitFragments: []
  88. };
  89. for (const dependency of module.dependencies) {
  90. const constructor = /** @type {DependencyConstructor} */ (
  91. dependency.constructor
  92. );
  93. const template = templateContext.dependencyTemplates.get(constructor);
  94. if (!template) {
  95. throw new Error(
  96. `No template for dependency: ${dependency.constructor.name}`
  97. );
  98. }
  99. template.apply(dependency, source, templateContext);
  100. }
  101. return /** @type {string} */ (source.source());
  102. }
  103. /**
  104. * Generates generated code for this runtime module.
  105. * @param {NormalModule} module module for which the code should be generated
  106. * @param {GenerateContext} generateContext context for generate
  107. * @returns {Source | null} generated code
  108. */
  109. generate(module, generateContext) {
  110. if (!module.originalSource()) return new RawSource("");
  111. const { type, getData } = generateContext;
  112. const content = this._rewrite(module, generateContext);
  113. // Hash the *rewritten* content (so the filename tracks the icons) and reuse
  114. // the asset filename machinery, so a plain (icon-less) manifest emits like a
  115. // plain asset (`output.assetModuleFilename`, `[name]`, `[ext]`).
  116. const outputOptions = generateContext.runtimeTemplate.outputOptions;
  117. const hash = createHash(outputOptions.hashFunction);
  118. if (outputOptions.hashSalt) hash.update(outputOptions.hashSalt);
  119. hash.update(content);
  120. const fullContentHash = /** @type {string} */ (
  121. hash.digest(outputOptions.hashDigest)
  122. );
  123. const contentHash = nonNumericOnlyHash(
  124. fullContentHash,
  125. /** @type {number} */ (outputOptions.hashDigestLength)
  126. );
  127. const { filename, assetInfo } = AssetGenerator.getFilenameWithInfo(
  128. module,
  129. { filename: undefined, outputPath: undefined },
  130. generateContext,
  131. contentHash,
  132. fullContentHash
  133. );
  134. const data = getData ? getData() : undefined;
  135. if (data) {
  136. data.set("filename", filename);
  137. data.set("assetInfo", assetInfo);
  138. if (type === ASSET_URL_TYPE) {
  139. data.set("url", {
  140. ...data.get("url"),
  141. [ASSET_URL_TYPE]: PUBLIC_PATH_AUTO + filename
  142. });
  143. }
  144. }
  145. return type === ASSET_URL_TYPE ? null : new RawSource(content);
  146. }
  147. }
  148. module.exports = WebManifestGenerator;