AssetModulesPlugin.js 11 KB

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