AssetModulesPlugin.js 11 KB

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