ModuleInfoHeaderPlugin.js 7.2 KB

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