AssetModulesPlugin.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Yuta Hiroto @hiroppy
  4. */
  5. "use strict";
  6. const {
  7. ASSET_MODULE_TYPE_RESOURCE,
  8. ASSET_MODULE_TYPE_INLINE,
  9. ASSET_MODULE_TYPE,
  10. ASSET_MODULE_TYPE_SOURCE
  11. } = require("../ModuleTypeConstants");
  12. const { cleverMerge } = require("../util/cleverMerge");
  13. const { compareModulesByIdOrIdentifier } = require("../util/comparators");
  14. const createSchemaValidation = require("../util/create-schema-validation");
  15. const memoize = require("../util/memoize");
  16. /** @typedef {import("webpack-sources").Source} Source */
  17. /** @typedef {import("../../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */
  18. /** @typedef {import("../Chunk")} Chunk */
  19. /** @typedef {import("../Compilation").AssetInfo} AssetInfo */
  20. /** @typedef {import("../Compiler")} Compiler */
  21. /** @typedef {import("../Module")} Module */
  22. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  23. /** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
  24. /** @typedef {import("../NormalModule")} NormalModule */
  25. /**
  26. * @param {string} name name of definitions
  27. * @returns {TODO} definition
  28. */
  29. const getSchema = name => {
  30. const { definitions } = require("../../schemas/WebpackOptions.json");
  31. return {
  32. definitions,
  33. oneOf: [{ $ref: `#/definitions/${name}` }]
  34. };
  35. };
  36. const generatorValidationOptions = {
  37. name: "Asset Modules Plugin",
  38. baseDataPath: "generator"
  39. };
  40. const validateGeneratorOptions = {
  41. asset: createSchemaValidation(
  42. require("../../schemas/plugins/asset/AssetGeneratorOptions.check.js"),
  43. () => getSchema("AssetGeneratorOptions"),
  44. generatorValidationOptions
  45. ),
  46. "asset/resource": createSchemaValidation(
  47. require("../../schemas/plugins/asset/AssetResourceGeneratorOptions.check.js"),
  48. () => getSchema("AssetResourceGeneratorOptions"),
  49. generatorValidationOptions
  50. ),
  51. "asset/inline": createSchemaValidation(
  52. require("../../schemas/plugins/asset/AssetInlineGeneratorOptions.check.js"),
  53. () => getSchema("AssetInlineGeneratorOptions"),
  54. generatorValidationOptions
  55. )
  56. };
  57. const validateParserOptions = createSchemaValidation(
  58. require("../../schemas/plugins/asset/AssetParserOptions.check.js"),
  59. () => getSchema("AssetParserOptions"),
  60. {
  61. name: "Asset Modules Plugin",
  62. baseDataPath: "parser"
  63. }
  64. );
  65. const getAssetGenerator = memoize(() => require("./AssetGenerator"));
  66. const getAssetParser = memoize(() => require("./AssetParser"));
  67. const getAssetSourceParser = memoize(() => require("./AssetSourceParser"));
  68. const getAssetSourceGenerator = memoize(() =>
  69. require("./AssetSourceGenerator")
  70. );
  71. const type = ASSET_MODULE_TYPE;
  72. const plugin = "AssetModulesPlugin";
  73. class AssetModulesPlugin {
  74. /**
  75. * Apply the plugin
  76. * @param {Compiler} compiler the compiler instance
  77. * @returns {void}
  78. */
  79. apply(compiler) {
  80. compiler.hooks.compilation.tap(
  81. plugin,
  82. (compilation, { normalModuleFactory }) => {
  83. normalModuleFactory.hooks.createParser
  84. .for(ASSET_MODULE_TYPE)
  85. .tap(plugin, parserOptions => {
  86. validateParserOptions(parserOptions);
  87. parserOptions = cleverMerge(
  88. /** @type {AssetParserOptions} */
  89. (compiler.options.module.parser.asset),
  90. parserOptions
  91. );
  92. let dataUrlCondition = parserOptions.dataUrlCondition;
  93. if (!dataUrlCondition || typeof dataUrlCondition === "object") {
  94. dataUrlCondition = {
  95. maxSize: 8096,
  96. ...dataUrlCondition
  97. };
  98. }
  99. const AssetParser = getAssetParser();
  100. return new AssetParser(dataUrlCondition);
  101. });
  102. normalModuleFactory.hooks.createParser
  103. .for(ASSET_MODULE_TYPE_INLINE)
  104. .tap(plugin, _parserOptions => {
  105. const AssetParser = getAssetParser();
  106. return new AssetParser(true);
  107. });
  108. normalModuleFactory.hooks.createParser
  109. .for(ASSET_MODULE_TYPE_RESOURCE)
  110. .tap(plugin, _parserOptions => {
  111. const AssetParser = getAssetParser();
  112. return new AssetParser(false);
  113. });
  114. normalModuleFactory.hooks.createParser
  115. .for(ASSET_MODULE_TYPE_SOURCE)
  116. .tap(plugin, _parserOptions => {
  117. const AssetSourceParser = getAssetSourceParser();
  118. return new AssetSourceParser();
  119. });
  120. for (const type of [
  121. ASSET_MODULE_TYPE,
  122. ASSET_MODULE_TYPE_INLINE,
  123. ASSET_MODULE_TYPE_RESOURCE
  124. ]) {
  125. normalModuleFactory.hooks.createGenerator
  126. .for(type)
  127. .tap(plugin, generatorOptions => {
  128. validateGeneratorOptions[type](generatorOptions);
  129. let dataUrl;
  130. if (type !== ASSET_MODULE_TYPE_RESOURCE) {
  131. dataUrl = generatorOptions.dataUrl;
  132. if (!dataUrl || typeof dataUrl === "object") {
  133. dataUrl = {
  134. encoding: undefined,
  135. mimetype: undefined,
  136. ...dataUrl
  137. };
  138. }
  139. }
  140. let filename;
  141. let publicPath;
  142. let outputPath;
  143. if (type !== ASSET_MODULE_TYPE_INLINE) {
  144. filename = generatorOptions.filename;
  145. publicPath = generatorOptions.publicPath;
  146. outputPath = generatorOptions.outputPath;
  147. }
  148. const AssetGenerator = getAssetGenerator();
  149. return new AssetGenerator(
  150. compilation.moduleGraph,
  151. dataUrl,
  152. filename,
  153. publicPath,
  154. outputPath,
  155. generatorOptions.emit !== false
  156. );
  157. });
  158. }
  159. normalModuleFactory.hooks.createGenerator
  160. .for(ASSET_MODULE_TYPE_SOURCE)
  161. .tap(plugin, () => {
  162. const AssetSourceGenerator = getAssetSourceGenerator();
  163. return new AssetSourceGenerator(compilation.moduleGraph);
  164. });
  165. compilation.hooks.renderManifest.tap(plugin, (result, options) => {
  166. const { chunkGraph } = compilation;
  167. const { chunk, codeGenerationResults, runtimeTemplate } = options;
  168. const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
  169. chunk,
  170. ASSET_MODULE_TYPE,
  171. compareModulesByIdOrIdentifier(chunkGraph)
  172. );
  173. if (modules) {
  174. for (const module of modules) {
  175. try {
  176. const codeGenResult = codeGenerationResults.get(
  177. module,
  178. chunk.runtime
  179. );
  180. const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
  181. const data =
  182. /** @type {NonNullable<CodeGenerationResult["data"]>} */
  183. (codeGenResult.data);
  184. const errored = module.getNumberOfErrors() > 0;
  185. /** @type {string} */
  186. let entryFilename;
  187. /** @type {AssetInfo} */
  188. let entryInfo;
  189. /** @type {string} */
  190. let entryHash;
  191. if (errored) {
  192. const erroredModule = /** @type {NormalModule} */ (module);
  193. const AssetGenerator = getAssetGenerator();
  194. const [fullContentHash, contentHash] =
  195. AssetGenerator.getFullContentHash(
  196. erroredModule,
  197. runtimeTemplate
  198. );
  199. const { filename, assetInfo } =
  200. AssetGenerator.getFilenameWithInfo(
  201. erroredModule,
  202. {
  203. filename:
  204. erroredModule.generatorOptions &&
  205. erroredModule.generatorOptions.filename,
  206. outputPath:
  207. erroredModule.generatorOptions &&
  208. erroredModule.generatorOptions.outputPath
  209. },
  210. {
  211. runtime: chunk.runtime,
  212. runtimeTemplate,
  213. chunkGraph
  214. },
  215. contentHash
  216. );
  217. entryFilename = filename;
  218. entryInfo = assetInfo;
  219. entryHash = fullContentHash;
  220. } else {
  221. entryFilename = buildInfo.filename || data.get("filename");
  222. entryInfo = buildInfo.assetInfo || data.get("assetInfo");
  223. entryHash =
  224. buildInfo.fullContentHash || data.get("fullContentHash");
  225. }
  226. result.push({
  227. render: () =>
  228. /** @type {Source} */ (codeGenResult.sources.get(type)),
  229. filename: entryFilename,
  230. info: entryInfo,
  231. auxiliary: true,
  232. identifier: `assetModule${chunkGraph.getModuleId(module)}`,
  233. hash: entryHash
  234. });
  235. } catch (err) {
  236. /** @type {Error} */ (err).message +=
  237. `\nduring rendering of asset ${module.identifier()}`;
  238. throw err;
  239. }
  240. }
  241. }
  242. return result;
  243. });
  244. compilation.hooks.prepareModuleExecution.tap(
  245. "AssetModulesPlugin",
  246. (options, context) => {
  247. const { codeGenerationResult } = options;
  248. const source = codeGenerationResult.sources.get(ASSET_MODULE_TYPE);
  249. if (source === undefined) return;
  250. const data =
  251. /** @type {NonNullable<CodeGenerationResult["data"]>} */
  252. (codeGenerationResult.data);
  253. context.assets.set(data.get("filename"), {
  254. source,
  255. info: data.get("assetInfo")
  256. });
  257. }
  258. );
  259. }
  260. );
  261. }
  262. }
  263. module.exports = AssetModulesPlugin;