setupHooks.js 5.0 KB

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