ModuleInfoHeaderPlugin.js 9.0 KB

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