setupHooks.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. "use strict";
  2. const webpack = require("webpack");
  3. const {
  4. isColorSupported
  5. } = require("colorette");
  6. /** @typedef {import("webpack").Configuration} Configuration */
  7. /** @typedef {import("webpack").Compiler} Compiler */
  8. /** @typedef {import("webpack").MultiCompiler} MultiCompiler */
  9. /** @typedef {import("webpack").Stats} Stats */
  10. /** @typedef {import("webpack").MultiStats} MultiStats */
  11. /** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
  12. /** @typedef {import("../index.js").ServerResponse} ServerResponse */
  13. /** @typedef {Configuration["stats"]} StatsOptions */
  14. /** @typedef {{ children: Configuration["stats"][] }} MultiStatsOptions */
  15. /** @typedef {Exclude<Configuration["stats"], boolean | string | undefined>} NormalizedStatsOptions */
  16. // TODO remove `color` after dropping webpack v4
  17. /** @typedef {{ children: StatsOptions[], colors?: any }} MultiNormalizedStatsOptions */
  18. /**
  19. * @template {IncomingMessage} Request
  20. * @template {ServerResponse} Response
  21. * @param {import("../index.js").Context<Request, Response>} context
  22. */
  23. function setupHooks(context) {
  24. function invalid() {
  25. if (context.state) {
  26. context.logger.log("Compilation starting...");
  27. } // We are now in invalid state
  28. // eslint-disable-next-line no-param-reassign
  29. context.state = false; // eslint-disable-next-line no-param-reassign, no-undefined
  30. context.stats = undefined;
  31. } // @ts-ignore
  32. const statsForWebpack4 = webpack.Stats && webpack.Stats.presetToOptions;
  33. /**
  34. * @param {Configuration["stats"]} statsOptions
  35. * @returns {NormalizedStatsOptions}
  36. */
  37. function normalizeStatsOptions(statsOptions) {
  38. if (statsForWebpack4) {
  39. if (typeof statsOptions === "undefined") {
  40. // eslint-disable-next-line no-param-reassign
  41. statsOptions = {};
  42. } else if (typeof statsOptions === "boolean" || typeof statsOptions === "string") {
  43. // @ts-ignore
  44. // eslint-disable-next-line no-param-reassign
  45. statsOptions = webpack.Stats.presetToOptions(statsOptions);
  46. } // @ts-ignore
  47. return statsOptions;
  48. }
  49. if (typeof statsOptions === "undefined") {
  50. // eslint-disable-next-line no-param-reassign
  51. statsOptions = {
  52. preset: "normal"
  53. };
  54. } else if (typeof statsOptions === "boolean") {
  55. // eslint-disable-next-line no-param-reassign
  56. statsOptions = statsOptions ? {
  57. preset: "normal"
  58. } : {
  59. preset: "none"
  60. };
  61. } else if (typeof statsOptions === "string") {
  62. // eslint-disable-next-line no-param-reassign
  63. statsOptions = {
  64. preset: statsOptions
  65. };
  66. }
  67. return statsOptions;
  68. }
  69. /**
  70. * @param {Stats | MultiStats} stats
  71. */
  72. function done(stats) {
  73. // We are now on valid state
  74. // eslint-disable-next-line no-param-reassign
  75. context.state = true; // eslint-disable-next-line no-param-reassign
  76. context.stats = stats; // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling
  77. process.nextTick(() => {
  78. const {
  79. compiler,
  80. logger,
  81. options,
  82. state,
  83. callbacks
  84. } = context; // Check if still in valid state
  85. if (!state) {
  86. return;
  87. }
  88. logger.log("Compilation finished");
  89. const isMultiCompilerMode = Boolean(
  90. /** @type {MultiCompiler} */
  91. compiler.compilers);
  92. /**
  93. * @type {StatsOptions | MultiStatsOptions | NormalizedStatsOptions | MultiNormalizedStatsOptions}
  94. */
  95. let statsOptions;
  96. if (typeof options.stats !== "undefined") {
  97. statsOptions = isMultiCompilerMode ? {
  98. children:
  99. /** @type {MultiCompiler} */
  100. compiler.compilers.map(() => options.stats)
  101. } : options.stats;
  102. } else {
  103. statsOptions = isMultiCompilerMode ? {
  104. children:
  105. /** @type {MultiCompiler} */
  106. compiler.compilers.map(child => child.options.stats)
  107. } :
  108. /** @type {Compiler} */
  109. compiler.options.stats;
  110. }
  111. if (isMultiCompilerMode) {
  112. /** @type {MultiNormalizedStatsOptions} */
  113. statsOptions.children =
  114. /** @type {MultiStatsOptions} */
  115. statsOptions.children.map(
  116. /**
  117. * @param {StatsOptions} childStatsOptions
  118. * @return {NormalizedStatsOptions}
  119. */
  120. childStatsOptions => {
  121. // eslint-disable-next-line no-param-reassign
  122. childStatsOptions = normalizeStatsOptions(childStatsOptions);
  123. if (typeof childStatsOptions.colors === "undefined") {
  124. // eslint-disable-next-line no-param-reassign
  125. childStatsOptions.colors = isColorSupported;
  126. }
  127. return childStatsOptions;
  128. });
  129. } else {
  130. /** @type {NormalizedStatsOptions} */
  131. statsOptions = normalizeStatsOptions(
  132. /** @type {StatsOptions} */
  133. statsOptions);
  134. if (typeof statsOptions.colors === "undefined") {
  135. statsOptions.colors = isColorSupported;
  136. }
  137. } // TODO webpack@4 doesn't support `{ children: [{ colors: true }, { colors: true }] }` for stats
  138. if (
  139. /** @type {MultiCompiler} */
  140. compiler.compilers && statsForWebpack4) {
  141. /** @type {MultiNormalizedStatsOptions} */
  142. statsOptions.colors =
  143. /** @type {MultiNormalizedStatsOptions} */
  144. statsOptions.children.some(
  145. /**
  146. * @param {StatsOptions} child
  147. */
  148. // @ts-ignore
  149. child => child.colors);
  150. }
  151. const printedStats = stats.toString(statsOptions); // Avoid extra empty line when `stats: 'none'`
  152. if (printedStats) {
  153. // eslint-disable-next-line no-console
  154. console.log(printedStats);
  155. } // eslint-disable-next-line no-param-reassign
  156. context.callbacks = []; // Execute callback that are delayed
  157. callbacks.forEach(
  158. /**
  159. * @param {(...args: any[]) => Stats | MultiStats} callback
  160. */
  161. callback => {
  162. callback(stats);
  163. });
  164. });
  165. }
  166. context.compiler.hooks.watchRun.tap("webpack-dev-middleware", invalid);
  167. context.compiler.hooks.invalid.tap("webpack-dev-middleware", invalid);
  168. context.compiler.hooks.done.tap("webpack-dev-middleware", done);
  169. }
  170. module.exports = setupHooks;