ProgressPlugin.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Compiler = require("./Compiler");
  7. const MultiCompiler = require("./MultiCompiler");
  8. const NormalModule = require("./NormalModule");
  9. const createSchemaValidation = require("./util/create-schema-validation");
  10. const { contextify } = require("./util/identifier");
  11. /** @typedef {import("../declarations/plugins/ProgressPlugin").HandlerFunction} HandlerFunction */
  12. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
  13. /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
  14. const validate = createSchemaValidation(
  15. require("../schemas/plugins/ProgressPlugin.check.js"),
  16. () => require("../schemas/plugins/ProgressPlugin.json"),
  17. {
  18. name: "Progress Plugin",
  19. baseDataPath: "options"
  20. }
  21. );
  22. const median3 = (a, b, c) => {
  23. return a + b + c - Math.max(a, b, c) - Math.min(a, b, c);
  24. };
  25. const createDefaultHandler = (profile, logger) => {
  26. /** @type {{ value: string, time: number }[]} */
  27. const lastStateInfo = [];
  28. const defaultHandler = (percentage, msg, ...args) => {
  29. if (profile) {
  30. if (percentage === 0) {
  31. lastStateInfo.length = 0;
  32. }
  33. const fullState = [msg, ...args];
  34. const state = fullState.map(s => s.replace(/\d+\/\d+ /g, ""));
  35. const now = Date.now();
  36. const len = Math.max(state.length, lastStateInfo.length);
  37. for (let i = len; i >= 0; i--) {
  38. const stateItem = i < state.length ? state[i] : undefined;
  39. const lastStateItem =
  40. i < lastStateInfo.length ? lastStateInfo[i] : undefined;
  41. if (lastStateItem) {
  42. if (stateItem !== lastStateItem.value) {
  43. const diff = now - lastStateItem.time;
  44. if (lastStateItem.value) {
  45. let reportState = lastStateItem.value;
  46. if (i > 0) {
  47. reportState = lastStateInfo[i - 1].value + " > " + reportState;
  48. }
  49. const stateMsg = `${" | ".repeat(i)}${diff} ms ${reportState}`;
  50. const d = diff;
  51. // This depends on timing so we ignore it for coverage
  52. /* istanbul ignore next */
  53. {
  54. if (d > 10000) {
  55. logger.error(stateMsg);
  56. } else if (d > 1000) {
  57. logger.warn(stateMsg);
  58. } else if (d > 10) {
  59. logger.info(stateMsg);
  60. } else if (d > 5) {
  61. logger.log(stateMsg);
  62. } else {
  63. logger.debug(stateMsg);
  64. }
  65. }
  66. }
  67. if (stateItem === undefined) {
  68. lastStateInfo.length = i;
  69. } else {
  70. lastStateItem.value = stateItem;
  71. lastStateItem.time = now;
  72. lastStateInfo.length = i + 1;
  73. }
  74. }
  75. } else {
  76. lastStateInfo[i] = {
  77. value: stateItem,
  78. time: now
  79. };
  80. }
  81. }
  82. }
  83. logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
  84. if (percentage === 1 || (!msg && args.length === 0)) logger.status();
  85. };
  86. return defaultHandler;
  87. };
  88. /**
  89. * @callback ReportProgress
  90. * @param {number} p
  91. * @param {...string} [args]
  92. * @returns {void}
  93. */
  94. /** @type {WeakMap<Compiler,ReportProgress>} */
  95. const progressReporters = new WeakMap();
  96. class ProgressPlugin {
  97. /**
  98. * @param {Compiler} compiler the current compiler
  99. * @returns {ReportProgress} a progress reporter, if any
  100. */
  101. static getReporter(compiler) {
  102. return progressReporters.get(compiler);
  103. }
  104. /**
  105. * @param {ProgressPluginArgument} options options
  106. */
  107. constructor(options = {}) {
  108. if (typeof options === "function") {
  109. options = {
  110. handler: options
  111. };
  112. }
  113. validate(options);
  114. options = { ...ProgressPlugin.defaultOptions, ...options };
  115. this.profile = options.profile;
  116. this.handler = options.handler;
  117. this.modulesCount = options.modulesCount;
  118. this.dependenciesCount = options.dependenciesCount;
  119. this.showEntries = options.entries;
  120. this.showModules = options.modules;
  121. this.showDependencies = options.dependencies;
  122. this.showActiveModules = options.activeModules;
  123. this.percentBy = options.percentBy;
  124. }
  125. /**
  126. * @param {Compiler | MultiCompiler} compiler webpack compiler
  127. * @returns {void}
  128. */
  129. apply(compiler) {
  130. const handler =
  131. this.handler ||
  132. createDefaultHandler(
  133. this.profile,
  134. compiler.getInfrastructureLogger("webpack.Progress")
  135. );
  136. if (compiler instanceof MultiCompiler) {
  137. this._applyOnMultiCompiler(compiler, handler);
  138. } else if (compiler instanceof Compiler) {
  139. this._applyOnCompiler(compiler, handler);
  140. }
  141. }
  142. /**
  143. * @param {MultiCompiler} compiler webpack multi-compiler
  144. * @param {HandlerFunction} handler function that executes for every progress step
  145. * @returns {void}
  146. */
  147. _applyOnMultiCompiler(compiler, handler) {
  148. const states = compiler.compilers.map(
  149. () => /** @type {[number, ...string[]]} */ ([0])
  150. );
  151. compiler.compilers.forEach((compiler, idx) => {
  152. new ProgressPlugin((p, msg, ...args) => {
  153. states[idx] = [p, msg, ...args];
  154. let sum = 0;
  155. for (const [p] of states) sum += p;
  156. handler(sum / states.length, `[${idx}] ${msg}`, ...args);
  157. }).apply(compiler);
  158. });
  159. }
  160. /**
  161. * @param {Compiler} compiler webpack compiler
  162. * @param {HandlerFunction} handler function that executes for every progress step
  163. * @returns {void}
  164. */
  165. _applyOnCompiler(compiler, handler) {
  166. const showEntries = this.showEntries;
  167. const showModules = this.showModules;
  168. const showDependencies = this.showDependencies;
  169. const showActiveModules = this.showActiveModules;
  170. let lastActiveModule = "";
  171. let currentLoader = "";
  172. let lastModulesCount = 0;
  173. let lastDependenciesCount = 0;
  174. let lastEntriesCount = 0;
  175. let modulesCount = 0;
  176. let dependenciesCount = 0;
  177. let entriesCount = 1;
  178. let doneModules = 0;
  179. let doneDependencies = 0;
  180. let doneEntries = 0;
  181. const activeModules = new Set();
  182. let lastUpdate = 0;
  183. const updateThrottled = () => {
  184. if (lastUpdate + 500 < Date.now()) update();
  185. };
  186. const update = () => {
  187. /** @type {string[]} */
  188. const items = [];
  189. const percentByModules =
  190. doneModules /
  191. Math.max(lastModulesCount || this.modulesCount || 1, modulesCount);
  192. const percentByEntries =
  193. doneEntries /
  194. Math.max(lastEntriesCount || this.dependenciesCount || 1, entriesCount);
  195. const percentByDependencies =
  196. doneDependencies /
  197. Math.max(lastDependenciesCount || 1, dependenciesCount);
  198. let percentageFactor;
  199. switch (this.percentBy) {
  200. case "entries":
  201. percentageFactor = percentByEntries;
  202. break;
  203. case "dependencies":
  204. percentageFactor = percentByDependencies;
  205. break;
  206. case "modules":
  207. percentageFactor = percentByModules;
  208. break;
  209. default:
  210. percentageFactor = median3(
  211. percentByModules,
  212. percentByEntries,
  213. percentByDependencies
  214. );
  215. }
  216. const percentage = 0.1 + percentageFactor * 0.55;
  217. if (currentLoader) {
  218. items.push(
  219. `import loader ${contextify(
  220. compiler.context,
  221. currentLoader,
  222. compiler.root
  223. )}`
  224. );
  225. } else {
  226. const statItems = [];
  227. if (showEntries) {
  228. statItems.push(`${doneEntries}/${entriesCount} entries`);
  229. }
  230. if (showDependencies) {
  231. statItems.push(
  232. `${doneDependencies}/${dependenciesCount} dependencies`
  233. );
  234. }
  235. if (showModules) {
  236. statItems.push(`${doneModules}/${modulesCount} modules`);
  237. }
  238. if (showActiveModules) {
  239. statItems.push(`${activeModules.size} active`);
  240. }
  241. if (statItems.length > 0) {
  242. items.push(statItems.join(" "));
  243. }
  244. if (showActiveModules) {
  245. items.push(lastActiveModule);
  246. }
  247. }
  248. handler(percentage, "building", ...items);
  249. lastUpdate = Date.now();
  250. };
  251. const factorizeAdd = () => {
  252. dependenciesCount++;
  253. if (dependenciesCount < 50 || dependenciesCount % 100 === 0)
  254. updateThrottled();
  255. };
  256. const factorizeDone = () => {
  257. doneDependencies++;
  258. if (doneDependencies < 50 || doneDependencies % 100 === 0)
  259. updateThrottled();
  260. };
  261. const moduleAdd = () => {
  262. modulesCount++;
  263. if (modulesCount < 50 || modulesCount % 100 === 0) updateThrottled();
  264. };
  265. // only used when showActiveModules is set
  266. const moduleBuild = module => {
  267. const ident = module.identifier();
  268. if (ident) {
  269. activeModules.add(ident);
  270. lastActiveModule = ident;
  271. update();
  272. }
  273. };
  274. const entryAdd = (entry, options) => {
  275. entriesCount++;
  276. if (entriesCount < 5 || entriesCount % 10 === 0) updateThrottled();
  277. };
  278. const moduleDone = module => {
  279. doneModules++;
  280. if (showActiveModules) {
  281. const ident = module.identifier();
  282. if (ident) {
  283. activeModules.delete(ident);
  284. if (lastActiveModule === ident) {
  285. lastActiveModule = "";
  286. for (const m of activeModules) {
  287. lastActiveModule = m;
  288. }
  289. update();
  290. return;
  291. }
  292. }
  293. }
  294. if (doneModules < 50 || doneModules % 100 === 0) updateThrottled();
  295. };
  296. const entryDone = (entry, options) => {
  297. doneEntries++;
  298. update();
  299. };
  300. const cache = compiler
  301. .getCache("ProgressPlugin")
  302. .getItemCache("counts", null);
  303. let cacheGetPromise;
  304. compiler.hooks.beforeCompile.tap("ProgressPlugin", () => {
  305. if (!cacheGetPromise) {
  306. cacheGetPromise = cache.getPromise().then(
  307. data => {
  308. if (data) {
  309. lastModulesCount = lastModulesCount || data.modulesCount;
  310. lastDependenciesCount =
  311. lastDependenciesCount || data.dependenciesCount;
  312. }
  313. return data;
  314. },
  315. err => {
  316. // Ignore error
  317. }
  318. );
  319. }
  320. });
  321. compiler.hooks.afterCompile.tapPromise("ProgressPlugin", compilation => {
  322. if (compilation.compiler.isChild()) return Promise.resolve();
  323. return cacheGetPromise.then(async oldData => {
  324. if (
  325. !oldData ||
  326. oldData.modulesCount !== modulesCount ||
  327. oldData.dependenciesCount !== dependenciesCount
  328. ) {
  329. await cache.storePromise({ modulesCount, dependenciesCount });
  330. }
  331. });
  332. });
  333. compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
  334. if (compilation.compiler.isChild()) return;
  335. lastModulesCount = modulesCount;
  336. lastEntriesCount = entriesCount;
  337. lastDependenciesCount = dependenciesCount;
  338. modulesCount = dependenciesCount = entriesCount = 0;
  339. doneModules = doneDependencies = doneEntries = 0;
  340. compilation.factorizeQueue.hooks.added.tap(
  341. "ProgressPlugin",
  342. factorizeAdd
  343. );
  344. compilation.factorizeQueue.hooks.result.tap(
  345. "ProgressPlugin",
  346. factorizeDone
  347. );
  348. compilation.addModuleQueue.hooks.added.tap("ProgressPlugin", moduleAdd);
  349. compilation.processDependenciesQueue.hooks.result.tap(
  350. "ProgressPlugin",
  351. moduleDone
  352. );
  353. if (showActiveModules) {
  354. compilation.hooks.buildModule.tap("ProgressPlugin", moduleBuild);
  355. }
  356. compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
  357. compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
  358. compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);
  359. // avoid dynamic require if bundled with webpack
  360. // @ts-expect-error
  361. if (typeof __webpack_require__ !== "function") {
  362. const requiredLoaders = new Set();
  363. NormalModule.getCompilationHooks(compilation).beforeLoaders.tap(
  364. "ProgressPlugin",
  365. loaders => {
  366. for (const loader of loaders) {
  367. if (
  368. loader.type !== "module" &&
  369. !requiredLoaders.has(loader.loader)
  370. ) {
  371. requiredLoaders.add(loader.loader);
  372. currentLoader = loader.loader;
  373. update();
  374. require(loader.loader);
  375. }
  376. }
  377. if (currentLoader) {
  378. currentLoader = "";
  379. update();
  380. }
  381. }
  382. );
  383. }
  384. const hooks = {
  385. finishModules: "finish module graph",
  386. seal: "plugins",
  387. optimizeDependencies: "dependencies optimization",
  388. afterOptimizeDependencies: "after dependencies optimization",
  389. beforeChunks: "chunk graph",
  390. afterChunks: "after chunk graph",
  391. optimize: "optimizing",
  392. optimizeModules: "module optimization",
  393. afterOptimizeModules: "after module optimization",
  394. optimizeChunks: "chunk optimization",
  395. afterOptimizeChunks: "after chunk optimization",
  396. optimizeTree: "module and chunk tree optimization",
  397. afterOptimizeTree: "after module and chunk tree optimization",
  398. optimizeChunkModules: "chunk modules optimization",
  399. afterOptimizeChunkModules: "after chunk modules optimization",
  400. reviveModules: "module reviving",
  401. beforeModuleIds: "before module ids",
  402. moduleIds: "module ids",
  403. optimizeModuleIds: "module id optimization",
  404. afterOptimizeModuleIds: "module id optimization",
  405. reviveChunks: "chunk reviving",
  406. beforeChunkIds: "before chunk ids",
  407. chunkIds: "chunk ids",
  408. optimizeChunkIds: "chunk id optimization",
  409. afterOptimizeChunkIds: "after chunk id optimization",
  410. recordModules: "record modules",
  411. recordChunks: "record chunks",
  412. beforeModuleHash: "module hashing",
  413. beforeCodeGeneration: "code generation",
  414. beforeRuntimeRequirements: "runtime requirements",
  415. beforeHash: "hashing",
  416. afterHash: "after hashing",
  417. recordHash: "record hash",
  418. beforeModuleAssets: "module assets processing",
  419. beforeChunkAssets: "chunk assets processing",
  420. processAssets: "asset processing",
  421. afterProcessAssets: "after asset optimization",
  422. record: "recording",
  423. afterSeal: "after seal"
  424. };
  425. const numberOfHooks = Object.keys(hooks).length;
  426. Object.keys(hooks).forEach((name, idx) => {
  427. const title = hooks[name];
  428. const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
  429. compilation.hooks[name].intercept({
  430. name: "ProgressPlugin",
  431. call() {
  432. handler(percentage, "sealing", title);
  433. },
  434. done() {
  435. progressReporters.set(compiler, undefined);
  436. handler(percentage, "sealing", title);
  437. },
  438. result() {
  439. handler(percentage, "sealing", title);
  440. },
  441. error() {
  442. handler(percentage, "sealing", title);
  443. },
  444. tap(tap) {
  445. // p is percentage from 0 to 1
  446. // args is any number of messages in a hierarchical matter
  447. progressReporters.set(compilation.compiler, (p, ...args) => {
  448. handler(percentage, "sealing", title, tap.name, ...args);
  449. });
  450. handler(percentage, "sealing", title, tap.name);
  451. }
  452. });
  453. });
  454. });
  455. compiler.hooks.make.intercept({
  456. name: "ProgressPlugin",
  457. call() {
  458. handler(0.1, "building");
  459. },
  460. done() {
  461. handler(0.65, "building");
  462. }
  463. });
  464. const interceptHook = (hook, progress, category, name) => {
  465. hook.intercept({
  466. name: "ProgressPlugin",
  467. call() {
  468. handler(progress, category, name);
  469. },
  470. done() {
  471. progressReporters.set(compiler, undefined);
  472. handler(progress, category, name);
  473. },
  474. result() {
  475. handler(progress, category, name);
  476. },
  477. error() {
  478. handler(progress, category, name);
  479. },
  480. tap(tap) {
  481. progressReporters.set(compiler, (p, ...args) => {
  482. handler(progress, category, name, tap.name, ...args);
  483. });
  484. handler(progress, category, name, tap.name);
  485. }
  486. });
  487. };
  488. compiler.cache.hooks.endIdle.intercept({
  489. name: "ProgressPlugin",
  490. call() {
  491. handler(0, "");
  492. }
  493. });
  494. interceptHook(compiler.cache.hooks.endIdle, 0.01, "cache", "end idle");
  495. compiler.hooks.beforeRun.intercept({
  496. name: "ProgressPlugin",
  497. call() {
  498. handler(0, "");
  499. }
  500. });
  501. interceptHook(compiler.hooks.beforeRun, 0.01, "setup", "before run");
  502. interceptHook(compiler.hooks.run, 0.02, "setup", "run");
  503. interceptHook(compiler.hooks.watchRun, 0.03, "setup", "watch run");
  504. interceptHook(
  505. compiler.hooks.normalModuleFactory,
  506. 0.04,
  507. "setup",
  508. "normal module factory"
  509. );
  510. interceptHook(
  511. compiler.hooks.contextModuleFactory,
  512. 0.05,
  513. "setup",
  514. "context module factory"
  515. );
  516. interceptHook(
  517. compiler.hooks.beforeCompile,
  518. 0.06,
  519. "setup",
  520. "before compile"
  521. );
  522. interceptHook(compiler.hooks.compile, 0.07, "setup", "compile");
  523. interceptHook(compiler.hooks.thisCompilation, 0.08, "setup", "compilation");
  524. interceptHook(compiler.hooks.compilation, 0.09, "setup", "compilation");
  525. interceptHook(compiler.hooks.finishMake, 0.69, "building", "finish");
  526. interceptHook(compiler.hooks.emit, 0.95, "emitting", "emit");
  527. interceptHook(compiler.hooks.afterEmit, 0.98, "emitting", "after emit");
  528. interceptHook(compiler.hooks.done, 0.99, "done", "plugins");
  529. compiler.hooks.done.intercept({
  530. name: "ProgressPlugin",
  531. done() {
  532. handler(0.99, "");
  533. }
  534. });
  535. interceptHook(
  536. compiler.cache.hooks.storeBuildDependencies,
  537. 0.99,
  538. "cache",
  539. "store build dependencies"
  540. );
  541. interceptHook(compiler.cache.hooks.shutdown, 0.99, "cache", "shutdown");
  542. interceptHook(compiler.cache.hooks.beginIdle, 0.99, "cache", "begin idle");
  543. interceptHook(
  544. compiler.hooks.watchClose,
  545. 0.99,
  546. "end",
  547. "closing watch compilation"
  548. );
  549. compiler.cache.hooks.beginIdle.intercept({
  550. name: "ProgressPlugin",
  551. done() {
  552. handler(1, "");
  553. }
  554. });
  555. compiler.cache.hooks.shutdown.intercept({
  556. name: "ProgressPlugin",
  557. done() {
  558. handler(1, "");
  559. }
  560. });
  561. }
  562. }
  563. ProgressPlugin.defaultOptions = {
  564. profile: false,
  565. modulesCount: 5000,
  566. dependenciesCount: 10000,
  567. modules: true,
  568. dependencies: true,
  569. activeModules: false,
  570. entries: true
  571. };
  572. module.exports = ProgressPlugin;