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