You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProgressPlugin.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const validateOptions = require("schema-utils");
  7. const schema = require("../schemas/plugins/ProgressPlugin.json");
  8. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
  9. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
  10. const createDefaultHandler = (profile, logger) => {
  11. let lastState;
  12. let lastStateTime;
  13. const defaultHandler = (percentage, msg, ...args) => {
  14. logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
  15. if (profile) {
  16. let state = msg;
  17. state = state.replace(/^\d+\/\d+\s+/, "");
  18. if (percentage === 0) {
  19. lastState = null;
  20. lastStateTime = Date.now();
  21. } else if (state !== lastState || percentage === 1) {
  22. const now = Date.now();
  23. if (lastState) {
  24. const diff = now - lastStateTime;
  25. const stateMsg = `${diff}ms ${lastState}`;
  26. if (diff > 1000) {
  27. logger.warn(stateMsg);
  28. } else if (diff > 10) {
  29. logger.info(stateMsg);
  30. } else if (diff > 0) {
  31. logger.log(stateMsg);
  32. } else {
  33. logger.debug(stateMsg);
  34. }
  35. }
  36. lastState = state;
  37. lastStateTime = now;
  38. }
  39. }
  40. if (percentage === 1) logger.status();
  41. };
  42. return defaultHandler;
  43. };
  44. class ProgressPlugin {
  45. /**
  46. * @param {ProgressPluginArgument} options options
  47. */
  48. constructor(options) {
  49. if (typeof options === "function") {
  50. options = {
  51. handler: options
  52. };
  53. }
  54. options = options || {};
  55. validateOptions(schema, options, "Progress Plugin");
  56. options = Object.assign({}, ProgressPlugin.defaultOptions, options);
  57. this.profile = options.profile;
  58. this.handler = options.handler;
  59. this.modulesCount = options.modulesCount;
  60. this.showEntries = options.entries;
  61. this.showModules = options.modules;
  62. this.showActiveModules = options.activeModules;
  63. }
  64. apply(compiler) {
  65. const { modulesCount } = this;
  66. const handler =
  67. this.handler ||
  68. createDefaultHandler(
  69. this.profile,
  70. compiler.getInfrastructureLogger("webpack.Progress")
  71. );
  72. const showEntries = this.showEntries;
  73. const showModules = this.showModules;
  74. const showActiveModules = this.showActiveModules;
  75. if (compiler.compilers) {
  76. const states = new Array(compiler.compilers.length);
  77. compiler.compilers.forEach((compiler, idx) => {
  78. new ProgressPlugin((p, msg, ...args) => {
  79. states[idx] = [p, msg, ...args];
  80. handler(
  81. states
  82. .map(state => (state && state[0]) || 0)
  83. .reduce((a, b) => a + b) / states.length,
  84. `[${idx}] ${msg}`,
  85. ...args
  86. );
  87. }).apply(compiler);
  88. });
  89. } else {
  90. let lastModulesCount = 0;
  91. let lastEntriesCount = 0;
  92. let moduleCount = modulesCount;
  93. let entriesCount = 1;
  94. let doneModules = 0;
  95. let doneEntries = 0;
  96. const activeModules = new Set();
  97. let lastActiveModule = "";
  98. const update = () => {
  99. const percentByModules =
  100. doneModules / Math.max(lastModulesCount, moduleCount);
  101. const percentByEntries =
  102. doneEntries / Math.max(lastEntriesCount, entriesCount);
  103. const items = [
  104. 0.1 + Math.max(percentByModules, percentByEntries) * 0.6,
  105. "building"
  106. ];
  107. if (showEntries) {
  108. items.push(`${doneEntries}/${entriesCount} entries`);
  109. }
  110. if (showModules) {
  111. items.push(`${doneModules}/${moduleCount} modules`);
  112. }
  113. if (showActiveModules) {
  114. items.push(`${activeModules.size} active`);
  115. items.push(lastActiveModule);
  116. }
  117. handler(...items);
  118. };
  119. const moduleAdd = module => {
  120. moduleCount++;
  121. if (showActiveModules) {
  122. const ident = module.identifier();
  123. if (ident) {
  124. activeModules.add(ident);
  125. lastActiveModule = ident;
  126. }
  127. }
  128. update();
  129. };
  130. const entryAdd = (entry, name) => {
  131. entriesCount++;
  132. update();
  133. };
  134. const moduleDone = module => {
  135. doneModules++;
  136. if (showActiveModules) {
  137. const ident = module.identifier();
  138. if (ident) {
  139. activeModules.delete(ident);
  140. if (lastActiveModule === ident) {
  141. lastActiveModule = "";
  142. for (const m of activeModules) {
  143. lastActiveModule = m;
  144. }
  145. }
  146. }
  147. }
  148. update();
  149. };
  150. const entryDone = (entry, name) => {
  151. doneEntries++;
  152. update();
  153. };
  154. compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
  155. if (compilation.compiler.isChild()) return;
  156. lastModulesCount = moduleCount;
  157. lastEntriesCount = entriesCount;
  158. moduleCount = entriesCount = 0;
  159. doneModules = doneEntries = 0;
  160. handler(0, "compiling");
  161. compilation.hooks.buildModule.tap("ProgressPlugin", moduleAdd);
  162. compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
  163. compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);
  164. compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
  165. compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
  166. compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);
  167. const hooks = {
  168. finishModules: "finish module graph",
  169. seal: "sealing",
  170. beforeChunks: "chunk graph",
  171. afterChunks: "after chunk graph",
  172. optimizeDependenciesBasic: "basic dependencies optimization",
  173. optimizeDependencies: "dependencies optimization",
  174. optimizeDependenciesAdvanced: "advanced dependencies optimization",
  175. afterOptimizeDependencies: "after dependencies optimization",
  176. optimize: "optimizing",
  177. optimizeModulesBasic: "basic module optimization",
  178. optimizeModules: "module optimization",
  179. optimizeModulesAdvanced: "advanced module optimization",
  180. afterOptimizeModules: "after module optimization",
  181. optimizeChunksBasic: "basic chunk optimization",
  182. optimizeChunks: "chunk optimization",
  183. optimizeChunksAdvanced: "advanced chunk optimization",
  184. afterOptimizeChunks: "after chunk optimization",
  185. optimizeTree: "module and chunk tree optimization",
  186. afterOptimizeTree: "after module and chunk tree optimization",
  187. optimizeChunkModulesBasic: "basic chunk modules optimization",
  188. optimizeChunkModules: "chunk modules optimization",
  189. optimizeChunkModulesAdvanced: "advanced chunk modules optimization",
  190. afterOptimizeChunkModules: "after chunk modules optimization",
  191. reviveModules: "module reviving",
  192. optimizeModuleOrder: "module order optimization",
  193. advancedOptimizeModuleOrder: "advanced module order optimization",
  194. beforeModuleIds: "before module ids",
  195. moduleIds: "module ids",
  196. optimizeModuleIds: "module id optimization",
  197. afterOptimizeModuleIds: "module id optimization",
  198. reviveChunks: "chunk reviving",
  199. optimizeChunkOrder: "chunk order optimization",
  200. beforeChunkIds: "before chunk ids",
  201. optimizeChunkIds: "chunk id optimization",
  202. afterOptimizeChunkIds: "after chunk id optimization",
  203. recordModules: "record modules",
  204. recordChunks: "record chunks",
  205. beforeHash: "hashing",
  206. afterHash: "after hashing",
  207. recordHash: "record hash",
  208. beforeModuleAssets: "module assets processing",
  209. beforeChunkAssets: "chunk assets processing",
  210. additionalChunkAssets: "additional chunk assets processing",
  211. record: "recording",
  212. additionalAssets: "additional asset processing",
  213. optimizeChunkAssets: "chunk asset optimization",
  214. afterOptimizeChunkAssets: "after chunk asset optimization",
  215. optimizeAssets: "asset optimization",
  216. afterOptimizeAssets: "after asset optimization",
  217. afterSeal: "after seal"
  218. };
  219. const numberOfHooks = Object.keys(hooks).length;
  220. Object.keys(hooks).forEach((name, idx) => {
  221. const title = hooks[name];
  222. const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
  223. compilation.hooks[name].intercept({
  224. name: "ProgressPlugin",
  225. context: true,
  226. call: () => {
  227. handler(percentage, title);
  228. },
  229. tap: (context, tap) => {
  230. if (context) {
  231. // p is percentage from 0 to 1
  232. // args is any number of messages in a hierarchical matter
  233. context.reportProgress = (p, ...args) => {
  234. handler(percentage, title, tap.name, ...args);
  235. };
  236. }
  237. handler(percentage, title, tap.name);
  238. }
  239. });
  240. });
  241. });
  242. compiler.hooks.emit.intercept({
  243. name: "ProgressPlugin",
  244. context: true,
  245. call: () => {
  246. handler(0.95, "emitting");
  247. },
  248. tap: (context, tap) => {
  249. if (context) {
  250. context.reportProgress = (p, ...args) => {
  251. handler(0.95, "emitting", tap.name, ...args);
  252. };
  253. }
  254. handler(0.95, "emitting", tap.name);
  255. }
  256. });
  257. compiler.hooks.afterEmit.intercept({
  258. name: "ProgressPlugin",
  259. context: true,
  260. call: () => {
  261. handler(0.98, "after emitting");
  262. },
  263. tap: (context, tap) => {
  264. if (context) {
  265. context.reportProgress = (p, ...args) => {
  266. handler(0.98, "after emitting", tap.name, ...args);
  267. };
  268. }
  269. handler(0.98, "after emitting", tap.name);
  270. }
  271. });
  272. compiler.hooks.done.tap("ProgressPlugin", () => {
  273. handler(1, "");
  274. });
  275. }
  276. }
  277. }
  278. ProgressPlugin.defaultOptions = {
  279. profile: false,
  280. modulesCount: 500,
  281. modules: true,
  282. activeModules: true,
  283. // TODO webpack 5 default this to true
  284. entries: false
  285. };
  286. module.exports = ProgressPlugin;