ModuleInfoHeaderPlugin.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { CachedSource, ConcatSource, RawSource } = require("webpack-sources");
  7. const { UsageState } = require("./ExportsInfo");
  8. const Template = require("./Template");
  9. const CssModulesPlugin = require("./css/CssModulesPlugin");
  10. const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
  11. /** @typedef {import("webpack-sources").Source} Source */
  12. /** @typedef {import("./Compiler")} Compiler */
  13. /** @typedef {import("./ExportsInfo")} ExportsInfo */
  14. /** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */
  15. /** @typedef {import("./Module")} Module */
  16. /** @typedef {import("./Module").BuildMeta} BuildMeta */
  17. /** @typedef {import("./ModuleGraph")} ModuleGraph */
  18. /** @typedef {import("./RequestShortener")} RequestShortener */
  19. /**
  20. * Join iterable with comma.
  21. * @template T
  22. * @param {Iterable<T>} iterable iterable
  23. * @returns {string} joined with comma
  24. */
  25. const joinIterableWithComma = (iterable) => {
  26. // This is more performant than Array.from().join(", ")
  27. // as it doesn't create an array
  28. let str = "";
  29. let first = true;
  30. for (const item of iterable) {
  31. if (first) {
  32. first = false;
  33. } else {
  34. str += ", ";
  35. }
  36. str += item;
  37. }
  38. return str;
  39. };
  40. /**
  41. * Print exports info to source.
  42. * @param {ConcatSource} source output
  43. * @param {string} indent spacing
  44. * @param {ExportsInfo} exportsInfo data
  45. * @param {ModuleGraph} moduleGraph moduleGraph
  46. * @param {RequestShortener} requestShortener requestShortener
  47. * @param {Set<ExportInfo>} alreadyPrinted deduplication set
  48. * @returns {void}
  49. */
  50. const printExportsInfoToSource = (
  51. source,
  52. indent,
  53. exportsInfo,
  54. moduleGraph,
  55. requestShortener,
  56. alreadyPrinted = new Set()
  57. ) => {
  58. const otherExportsInfo = exportsInfo.otherExportsInfo;
  59. let alreadyPrintedExports = 0;
  60. // determine exports to print
  61. /** @type {ExportInfo[]} */
  62. const printedExports = [];
  63. for (const exportInfo of exportsInfo.orderedExports) {
  64. if (!alreadyPrinted.has(exportInfo)) {
  65. alreadyPrinted.add(exportInfo);
  66. printedExports.push(exportInfo);
  67. } else {
  68. alreadyPrintedExports++;
  69. }
  70. }
  71. let showOtherExports = false;
  72. if (!alreadyPrinted.has(otherExportsInfo)) {
  73. alreadyPrinted.add(otherExportsInfo);
  74. showOtherExports = true;
  75. } else {
  76. alreadyPrintedExports++;
  77. }
  78. // print the exports
  79. for (const exportInfo of printedExports) {
  80. const target = exportInfo.getTarget(moduleGraph);
  81. source.add(
  82. `${Template.toComment(
  83. `${indent}export ${JSON.stringify(exportInfo.name).slice(
  84. 1,
  85. -1
  86. )} [${exportInfo.getProvidedInfo()}] [${exportInfo.getUsedInfo()}] [${exportInfo.getRenameInfo()}]${
  87. target
  88. ? ` -> ${target.module.readableIdentifier(requestShortener)}${
  89. target.export
  90. ? ` .${target.export
  91. .map((e) => JSON.stringify(e).slice(1, -1))
  92. .join(".")}`
  93. : ""
  94. }`
  95. : ""
  96. }`
  97. )}\n`
  98. );
  99. if (exportInfo.exportsInfo) {
  100. printExportsInfoToSource(
  101. source,
  102. `${indent} `,
  103. exportInfo.exportsInfo,
  104. moduleGraph,
  105. requestShortener,
  106. alreadyPrinted
  107. );
  108. }
  109. }
  110. if (alreadyPrintedExports) {
  111. source.add(
  112. `${Template.toComment(
  113. `${indent}... (${alreadyPrintedExports} already listed exports)`
  114. )}\n`
  115. );
  116. }
  117. if (showOtherExports) {
  118. const target = otherExportsInfo.getTarget(moduleGraph);
  119. if (
  120. target ||
  121. otherExportsInfo.provided !== false ||
  122. otherExportsInfo.getUsed(undefined) !== UsageState.Unused
  123. ) {
  124. const title =
  125. printedExports.length > 0 || alreadyPrintedExports > 0
  126. ? "other exports"
  127. : "exports";
  128. source.add(
  129. `${Template.toComment(
  130. `${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]${
  131. target
  132. ? ` -> ${target.module.readableIdentifier(requestShortener)}`
  133. : ""
  134. }`
  135. )}\n`
  136. );
  137. }
  138. }
  139. };
  140. /** @typedef {{ header: RawSource | undefined, full: WeakMap<Source, CachedSource> }} CacheEntry */
  141. /** @type {WeakMap<RequestShortener, WeakMap<Module, CacheEntry>>} */
  142. const caches = new WeakMap();
  143. const PLUGIN_NAME = "ModuleInfoHeaderPlugin";
  144. class ModuleInfoHeaderPlugin {
  145. /**
  146. * Creates an instance of ModuleInfoHeaderPlugin.
  147. * @param {boolean=} verbose add more information like exports, runtime requirements and bailouts
  148. */
  149. constructor(verbose = true) {
  150. /** @type {boolean} */
  151. this._verbose = verbose;
  152. }
  153. /**
  154. * Applies the plugin by registering its hooks on the compiler.
  155. * @param {Compiler} compiler the compiler
  156. * @returns {void}
  157. */
  158. apply(compiler) {
  159. const { _verbose: verbose } = this;
  160. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  161. const javascriptHooks =
  162. JavascriptModulesPlugin.getCompilationHooks(compilation);
  163. javascriptHooks.renderModulePackage.tap(
  164. PLUGIN_NAME,
  165. (
  166. moduleSource,
  167. module,
  168. { chunk, chunkGraph, moduleGraph, runtimeTemplate }
  169. ) => {
  170. const { requestShortener } = runtimeTemplate;
  171. /** @type {undefined | CacheEntry} */
  172. let cacheEntry;
  173. let cache = caches.get(requestShortener);
  174. if (cache === undefined) {
  175. caches.set(requestShortener, (cache = new WeakMap()));
  176. cache.set(
  177. module,
  178. (cacheEntry = { header: undefined, full: new WeakMap() })
  179. );
  180. } else {
  181. cacheEntry = cache.get(module);
  182. if (cacheEntry === undefined) {
  183. cache.set(
  184. module,
  185. (cacheEntry = { header: undefined, full: new WeakMap() })
  186. );
  187. } else if (!verbose) {
  188. const cachedSource = cacheEntry.full.get(moduleSource);
  189. if (cachedSource !== undefined) return cachedSource;
  190. }
  191. }
  192. const source = new ConcatSource();
  193. let header = cacheEntry.header;
  194. if (header === undefined) {
  195. header = this.generateHeader(module, requestShortener);
  196. cacheEntry.header = header;
  197. }
  198. source.add(header);
  199. if (verbose) {
  200. const exportsType = /** @type {BuildMeta} */ (module.buildMeta)
  201. .exportsType;
  202. source.add(
  203. `${Template.toComment(
  204. exportsType
  205. ? `${exportsType} exports`
  206. : "unknown exports (runtime-defined)"
  207. )}\n`
  208. );
  209. if (exportsType) {
  210. const exportsInfo = moduleGraph.getExportsInfo(module);
  211. printExportsInfoToSource(
  212. source,
  213. "",
  214. exportsInfo,
  215. moduleGraph,
  216. requestShortener
  217. );
  218. }
  219. source.add(
  220. `${Template.toComment(
  221. `runtime requirements: ${joinIterableWithComma(
  222. chunkGraph.getModuleRuntimeRequirements(module, chunk.runtime)
  223. )}`
  224. )}\n`
  225. );
  226. const optimizationBailout =
  227. moduleGraph.getOptimizationBailout(module);
  228. if (optimizationBailout) {
  229. for (const text of optimizationBailout) {
  230. const code =
  231. typeof text === "function" ? text(requestShortener) : text;
  232. source.add(`${Template.toComment(`${code}`)}\n`);
  233. }
  234. }
  235. source.add(moduleSource);
  236. return source;
  237. }
  238. source.add(moduleSource);
  239. const cachedSource = new CachedSource(source);
  240. cacheEntry.full.set(moduleSource, cachedSource);
  241. return cachedSource;
  242. }
  243. );
  244. javascriptHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
  245. hash.update(PLUGIN_NAME);
  246. hash.update("1");
  247. });
  248. const cssHooks = CssModulesPlugin.getCompilationHooks(compilation);
  249. cssHooks.renderModulePackage.tap(
  250. PLUGIN_NAME,
  251. (moduleSource, module, { runtimeTemplate }) => {
  252. const { requestShortener } = runtimeTemplate;
  253. /** @type {undefined | CacheEntry} */
  254. let cacheEntry;
  255. let cache = caches.get(requestShortener);
  256. if (cache === undefined) {
  257. caches.set(requestShortener, (cache = new WeakMap()));
  258. cache.set(
  259. module,
  260. (cacheEntry = { header: undefined, full: new WeakMap() })
  261. );
  262. } else {
  263. cacheEntry = cache.get(module);
  264. if (cacheEntry === undefined) {
  265. cache.set(
  266. module,
  267. (cacheEntry = { header: undefined, full: new WeakMap() })
  268. );
  269. } else if (!verbose) {
  270. const cachedSource = cacheEntry.full.get(moduleSource);
  271. if (cachedSource !== undefined) return cachedSource;
  272. }
  273. }
  274. const source = new ConcatSource();
  275. let header = cacheEntry.header;
  276. if (header === undefined) {
  277. header = this.generateHeader(module, requestShortener);
  278. cacheEntry.header = header;
  279. }
  280. source.add(header);
  281. source.add(moduleSource);
  282. const cachedSource = new CachedSource(source);
  283. cacheEntry.full.set(moduleSource, cachedSource);
  284. return cachedSource;
  285. }
  286. );
  287. cssHooks.chunkHash.tap(PLUGIN_NAME, (_chunk, hash) => {
  288. hash.update(PLUGIN_NAME);
  289. hash.update("1");
  290. });
  291. });
  292. }
  293. /**
  294. * Returns the header.
  295. * @param {Module} module the module
  296. * @param {RequestShortener} requestShortener request shortener
  297. * @returns {RawSource} the header
  298. */
  299. generateHeader(module, requestShortener) {
  300. const req = module.readableIdentifier(requestShortener);
  301. const reqStr = req.replace(/\*\//g, "*_/");
  302. const reqStrStar = "*".repeat(reqStr.length);
  303. const headerStr = `/*!****${reqStrStar}****!*\\\n !*** ${reqStr} ***!\n \\****${reqStrStar}****/\n`;
  304. return new RawSource(headerStr);
  305. }
  306. }
  307. module.exports = ModuleInfoHeaderPlugin;