setupHooks.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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>} StatsObjectOptions */
  12. /**
  13. * @template {IncomingMessage} Request
  14. * @template {ServerResponse} Response
  15. * @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} 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 {StatsOptions} statsOptions
  30. * @returns {StatsObjectOptions}
  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 | undefined}
  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 {StatsObjectOptions}
  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. statsOptions = normalizeStatsOptions( /** @type {StatsOptions} */statsOptions);
  114. if (typeof statsOptions.colors === "undefined") {
  115. // eslint-disable-next-line global-require
  116. statsOptions.colors = require("colorette").isColorSupported;
  117. }
  118. }
  119. const printedStats = stats.toString( /** @type {StatsObjectOptions} */statsOptions);
  120. // Avoid extra empty line when `stats: 'none'`
  121. if (printedStats) {
  122. // eslint-disable-next-line no-console
  123. console.log(printedStats);
  124. }
  125. // eslint-disable-next-line no-param-reassign
  126. context.callbacks = [];
  127. // Execute callback that are delayed
  128. for (const callback of callbacks) {
  129. callback(stats);
  130. }
  131. });
  132. }
  133. // eslint-disable-next-line prefer-destructuring
  134. const compiler = /** @type {import("../index.js").Context<Request, Response>} */
  135. context.compiler;
  136. compiler.hooks.watchRun.tap("webpack-dev-middleware", invalid);
  137. compiler.hooks.invalid.tap("webpack-dev-middleware", invalid);
  138. compiler.hooks.done.tap("webpack-dev-middleware", done);
  139. }
  140. module.exports = setupHooks;