From f29d6e89c15f25e1baa8600c4e422c310a089b3e Mon Sep 17 00:00:00 2001 From: Even Stensberg Date: Tue, 16 Sep 2025 15:02:58 +0200 Subject: [PATCH] fix: gracefull shutdown on watch/cache --- packages/webpack-cli/src/webpack-cli.ts | 13 +++--- test/build/cache/cache.test.js | 28 ++++++++++++- .../cache/graceful-exit.webpack.config.js | 40 +++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 test/build/cache/graceful-exit.webpack.config.js diff --git a/packages/webpack-cli/src/webpack-cli.ts b/packages/webpack-cli/src/webpack-cli.ts index e26b7f2a9d0..182527003f6 100644 --- a/packages/webpack-cli/src/webpack-cli.ts +++ b/packages/webpack-cli/src/webpack-cli.ts @@ -2484,9 +2484,7 @@ class WebpackCLI implements IWebpackCLI { compiler = await this.createCompiler(options as WebpackDevServerOptions, callback); - if (!compiler) { - return; - } + if (!compiler) return; const isWatch = (compiler: WebpackCompiler): boolean => Boolean( @@ -2494,8 +2492,14 @@ class WebpackCLI implements IWebpackCLI { ? compiler.compilers.some((compiler) => compiler.options.watch) : compiler.options.watch, ); + const isFileSystemCacheEnabled = (compiler: WebpackCompiler): boolean => + Boolean( + this.isMultipleCompiler(compiler) + ? compiler.compilers.some((compiler) => compiler.options.cache) + : compiler.options.cache, + ); - if (isWatch(compiler)) { + if (isWatch(compiler) || isFileSystemCacheEnabled(compiler)) { let needForceShutdown = false; for (const signal of EXIT_SIGNALS) { @@ -2508,7 +2512,6 @@ class WebpackCLI implements IWebpackCLI { this.logger.info( "Gracefully shutting down. To force exit, press ^C again. Please wait...", ); - needForceShutdown = true; compiler.close(() => { diff --git a/test/build/cache/cache.test.js b/test/build/cache/cache.test.js index 1101f59930b..62c4c3dfe63 100644 --- a/test/build/cache/cache.test.js +++ b/test/build/cache/cache.test.js @@ -2,7 +2,7 @@ const fs = require("node:fs"); const path = require("node:path"); -const { run } = require("../../utils/test-utils"); +const { processKill, run, runAndGetProcess } = require("../../utils/test-utils"); describe("cache", () => { it("should work", async () => { @@ -218,4 +218,30 @@ describe("cache", () => { expect(stderr).toBeTruthy(); expect(stdout).toBeTruthy(); }); + + it("should gracefully exit when cache is true", (done) => { + fs.rmSync(path.join(__dirname, "../../../node_modules/.cache/webpack/graceful-exit-test"), { + recursive: true, + force: true, + }); + + const proc = runAndGetProcess(__dirname, [ + "--config", + "./graceful-exit.webpack.config.js", + "--name", + "graceful-exit-test", + ]); + proc.stdout.on("data", (chunk) => { + const data = chunk.toString(); + if (data.includes("app.bundle.js")) { + processKill(proc); + return; + } + expect(data).toContain( + "Gracefully shutting down. To force exit, press ^C again. Please wait...", + ); + }); + + proc.on("exit", () => done()); + }); }); diff --git a/test/build/cache/graceful-exit.webpack.config.js b/test/build/cache/graceful-exit.webpack.config.js new file mode 100644 index 00000000000..0334a3822c2 --- /dev/null +++ b/test/build/cache/graceful-exit.webpack.config.js @@ -0,0 +1,40 @@ +const path = require("node:path"); + +class InfiniteWaitPlugin { + constructor(options = {}) { + this.wait = options.wait || 10e3; + } + + apply(compiler) { + compiler.hooks.done.tapPromise("Graceful Exit Test", async () => { + // eslint-disable-next-line no-new + new Promise((res) => { + setTimeout(res, this.wait); + }); + }); + } +} + +module.exports = { + mode: "development", + name: "graceful-exit-test", + cache: { + type: "filesystem", + buildDependencies: { + config: [__filename], + }, + }, + infrastructureLogging: { + debug: /cache/, + }, + entry: { + app: "./src/main.js", + }, + output: { + filename: "[name].bundle.js", + chunkFilename: "[name].bundle.js", + path: path.resolve(__dirname, "dist"), + publicPath: "/", + }, + plugins: [new InfiniteWaitPlugin()], +};