AssetModulesPlugin.js 8.6 KB

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