setupHooks.js 4.9 KB

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