AbstractLibraryPlugin.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
  8. /** @typedef {import("webpack-sources").Source} Source */
  9. /** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
  10. /** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
  11. /** @typedef {import("../Chunk")} Chunk */
  12. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  13. /** @typedef {import("../Compilation")} Compilation */
  14. /** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
  15. /** @typedef {import("../Compiler")} Compiler */
  16. /** @typedef {import("../Module")} Module */
  17. /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
  18. /** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
  19. /** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */
  20. /** @typedef {import("../javascript/JavascriptModulesPlugin").ModuleRenderContext} ModuleRenderContext */
  21. /** @typedef {import("../util/Hash")} Hash */
  22. const COMMON_LIBRARY_NAME_MESSAGE =
  23. "Common configuration options that specific library names are 'output.library[.name]', 'entry.xyz.library[.name]', 'ModuleFederationPlugin.name' and 'ModuleFederationPlugin.library[.name]'.";
  24. /**
  25. * @template T
  26. * @typedef {object} LibraryContext
  27. * @property {Compilation} compilation
  28. * @property {ChunkGraph} chunkGraph
  29. * @property {T} options
  30. */
  31. /**
  32. * @typedef {object} AbstractLibraryPluginOptions
  33. * @property {string} pluginName name of the plugin
  34. * @property {LibraryType} type used library type
  35. */
  36. /**
  37. * @template T
  38. */
  39. class AbstractLibraryPlugin {
  40. /**
  41. * @param {AbstractLibraryPluginOptions} options options
  42. */
  43. constructor({ pluginName, type }) {
  44. /** @type {AbstractLibraryPluginOptions["pluginName"]} */
  45. this._pluginName = pluginName;
  46. /** @type {AbstractLibraryPluginOptions["type"]} */
  47. this._type = type;
  48. /** @type {WeakMap<LibraryOptions, T>} */
  49. this._parseCache = new WeakMap();
  50. }
  51. /**
  52. * Apply the plugin
  53. * @param {Compiler} compiler the compiler instance
  54. * @returns {void}
  55. */
  56. apply(compiler) {
  57. const { _pluginName } = this;
  58. compiler.hooks.thisCompilation.tap(_pluginName, (compilation) => {
  59. compilation.hooks.finishModules.tap(
  60. { name: _pluginName, stage: 10 },
  61. () => {
  62. for (const [
  63. name,
  64. {
  65. dependencies: deps,
  66. options: { library }
  67. }
  68. ] of compilation.entries) {
  69. const options = this._parseOptionsCached(
  70. library !== undefined
  71. ? library
  72. : compilation.outputOptions.library
  73. );
  74. if (options !== false) {
  75. const dep = deps[deps.length - 1];
  76. if (dep) {
  77. const module = compilation.moduleGraph.getModule(dep);
  78. if (module) {
  79. this.finishEntryModule(module, name, {
  80. options,
  81. compilation,
  82. chunkGraph: compilation.chunkGraph
  83. });
  84. }
  85. }
  86. }
  87. }
  88. }
  89. );
  90. /**
  91. * @param {Chunk} chunk chunk
  92. * @returns {T | false} options for the chunk
  93. */
  94. const getOptionsForChunk = (chunk) => {
  95. if (compilation.chunkGraph.getNumberOfEntryModules(chunk) === 0) {
  96. return false;
  97. }
  98. const options = chunk.getEntryOptions();
  99. const library = options && options.library;
  100. return this._parseOptionsCached(
  101. library !== undefined ? library : compilation.outputOptions.library
  102. );
  103. };
  104. if (
  105. this.render !== AbstractLibraryPlugin.prototype.render ||
  106. this.runtimeRequirements !==
  107. AbstractLibraryPlugin.prototype.runtimeRequirements
  108. ) {
  109. compilation.hooks.additionalChunkRuntimeRequirements.tap(
  110. _pluginName,
  111. (chunk, set, { chunkGraph }) => {
  112. const options = getOptionsForChunk(chunk);
  113. if (options !== false) {
  114. this.runtimeRequirements(chunk, set, {
  115. options,
  116. compilation,
  117. chunkGraph
  118. });
  119. }
  120. }
  121. );
  122. }
  123. const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
  124. if (this.render !== AbstractLibraryPlugin.prototype.render) {
  125. hooks.render.tap(_pluginName, (source, renderContext) => {
  126. const options = getOptionsForChunk(renderContext.chunk);
  127. if (options === false) return source;
  128. return this.render(source, renderContext, {
  129. options,
  130. compilation,
  131. chunkGraph: compilation.chunkGraph
  132. });
  133. });
  134. }
  135. if (
  136. this.embedInRuntimeBailout !==
  137. AbstractLibraryPlugin.prototype.embedInRuntimeBailout
  138. ) {
  139. hooks.embedInRuntimeBailout.tap(
  140. _pluginName,
  141. (module, renderContext) => {
  142. const options = getOptionsForChunk(renderContext.chunk);
  143. if (options === false) return;
  144. return this.embedInRuntimeBailout(module, renderContext, {
  145. options,
  146. compilation,
  147. chunkGraph: compilation.chunkGraph
  148. });
  149. }
  150. );
  151. }
  152. if (
  153. this.strictRuntimeBailout !==
  154. AbstractLibraryPlugin.prototype.strictRuntimeBailout
  155. ) {
  156. hooks.strictRuntimeBailout.tap(_pluginName, (renderContext) => {
  157. const options = getOptionsForChunk(renderContext.chunk);
  158. if (options === false) return;
  159. return this.strictRuntimeBailout(renderContext, {
  160. options,
  161. compilation,
  162. chunkGraph: compilation.chunkGraph
  163. });
  164. });
  165. }
  166. if (
  167. this.renderModuleContent !==
  168. AbstractLibraryPlugin.prototype.renderModuleContent
  169. ) {
  170. hooks.renderModuleContent.tap(
  171. _pluginName,
  172. (source, module, renderContext) =>
  173. this.renderModuleContent(source, module, renderContext, {
  174. compilation,
  175. chunkGraph: compilation.chunkGraph
  176. })
  177. );
  178. }
  179. if (
  180. this.renderStartup !== AbstractLibraryPlugin.prototype.renderStartup
  181. ) {
  182. hooks.renderStartup.tap(
  183. _pluginName,
  184. (source, module, renderContext) => {
  185. const options = getOptionsForChunk(renderContext.chunk);
  186. if (options === false) return source;
  187. return this.renderStartup(source, module, renderContext, {
  188. options,
  189. compilation,
  190. chunkGraph: compilation.chunkGraph
  191. });
  192. }
  193. );
  194. }
  195. hooks.chunkHash.tap(_pluginName, (chunk, hash, context) => {
  196. const options = getOptionsForChunk(chunk);
  197. if (options === false) return;
  198. this.chunkHash(chunk, hash, context, {
  199. options,
  200. compilation,
  201. chunkGraph: compilation.chunkGraph
  202. });
  203. });
  204. });
  205. }
  206. /**
  207. * @param {LibraryOptions=} library normalized library option
  208. * @returns {T | false} preprocess as needed by overriding
  209. */
  210. _parseOptionsCached(library) {
  211. if (!library) return false;
  212. if (library.type !== this._type) return false;
  213. const cacheEntry = this._parseCache.get(library);
  214. if (cacheEntry !== undefined) return cacheEntry;
  215. const result = this.parseOptions(library);
  216. this._parseCache.set(library, result);
  217. return result;
  218. }
  219. /* istanbul ignore next */
  220. /**
  221. * @abstract
  222. * @param {LibraryOptions} library normalized library option
  223. * @returns {T} preprocess as needed by overriding
  224. */
  225. parseOptions(library) {
  226. const AbstractMethodError = require("../AbstractMethodError");
  227. throw new AbstractMethodError();
  228. }
  229. /**
  230. * @param {Module} module the exporting entry module
  231. * @param {string} entryName the name of the entrypoint
  232. * @param {LibraryContext<T>} libraryContext context
  233. * @returns {void}
  234. */
  235. finishEntryModule(module, entryName, libraryContext) {}
  236. /**
  237. * @param {Module} module the exporting entry module
  238. * @param {RenderContext} renderContext render context
  239. * @param {LibraryContext<T>} libraryContext context
  240. * @returns {string | undefined} bailout reason
  241. */
  242. embedInRuntimeBailout(module, renderContext, libraryContext) {
  243. return undefined;
  244. }
  245. /**
  246. * @param {RenderContext} renderContext render context
  247. * @param {LibraryContext<T>} libraryContext context
  248. * @returns {string | undefined} bailout reason
  249. */
  250. strictRuntimeBailout(renderContext, libraryContext) {
  251. return undefined;
  252. }
  253. /**
  254. * @param {Chunk} chunk the chunk
  255. * @param {RuntimeRequirements} set runtime requirements
  256. * @param {LibraryContext<T>} libraryContext context
  257. * @returns {void}
  258. */
  259. runtimeRequirements(chunk, set, libraryContext) {
  260. if (this.render !== AbstractLibraryPlugin.prototype.render) {
  261. set.add(RuntimeGlobals.returnExportsFromRuntime);
  262. }
  263. }
  264. /**
  265. * @param {Source} source source
  266. * @param {RenderContext} renderContext render context
  267. * @param {LibraryContext<T>} libraryContext context
  268. * @returns {Source} source with library export
  269. */
  270. render(source, renderContext, libraryContext) {
  271. return source;
  272. }
  273. /**
  274. * @param {Source} source source
  275. * @param {Module} module module
  276. * @param {StartupRenderContext} renderContext render context
  277. * @param {LibraryContext<T>} libraryContext context
  278. * @returns {Source} source with library export
  279. */
  280. renderStartup(source, module, renderContext, libraryContext) {
  281. return source;
  282. }
  283. /**
  284. * @param {Source} source source
  285. * @param {Module} module module
  286. * @param {ModuleRenderContext} renderContext render context
  287. * @param {Omit<LibraryContext<T>, "options">} libraryContext context
  288. * @returns {Source} source with library export
  289. */
  290. renderModuleContent(source, module, renderContext, libraryContext) {
  291. return source;
  292. }
  293. /**
  294. * @param {Chunk} chunk the chunk
  295. * @param {Hash} hash hash
  296. * @param {ChunkHashContext} chunkHashContext chunk hash context
  297. * @param {LibraryContext<T>} libraryContext context
  298. * @returns {void}
  299. */
  300. chunkHash(chunk, hash, chunkHashContext, libraryContext) {
  301. const options = this._parseOptionsCached(
  302. libraryContext.compilation.outputOptions.library
  303. );
  304. hash.update(this._pluginName);
  305. hash.update(JSON.stringify(options));
  306. }
  307. }
  308. AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE = COMMON_LIBRARY_NAME_MESSAGE;
  309. module.exports = AbstractLibraryPlugin;