Skip to content

output.clean removes HMR update files after 10 s while a slow rebuild is still serving them #22336

Closed

Description

@erictheswift

Bug report

What is the current behavior?

With output.clean: true, CleanPlugin keeps *.hot-update.js / *.hot-update.json of the previous compilation for a fixed 10 s (_10sec in lib/output/CleanPlugin.js, added in #15405 for #15206). When the next compilation takes longer than that, the update files of the previous one are deleted on its emit while a client may still be loading them.

Sequence with webpack-dev-server / webpack-dev-middleware:

  1. Compilation N+1 emits main.<hashN>.hot-update.js (+ runtime.<hashN>.hot-update.js with a runtime chunk), kept until emit(N+1) + 10 s.
  2. The client receives ok, hotCheck fetches main.<hashN>.hot-update.json — fine.
  3. Before it requests the .hot-update.js files, compilation N+2 starts (second save, or any other invalidation). dev-middleware queues the requests until N+2 is done.
  4. N+2 takes > 10 s. Its CleanPlugin pass removes the N+1 update files (ts < now). The queued requests get 404, or, with historyApiFallback, index.html as JS:
entry.86f6914295f12e02.hot-update.js:1 Uncaught SyntaxError: Unexpected token '<'
    __webpack_require__.l
    loadUpdateChunk
    __webpack_require__.hmrC.jsonp
    hotCheck
    check @ dev-server.js
    reloadApp @ index.js
  1. Loading hot update chunk ... failed (missing) → [HMR] Cannot apply update. Need to do a full reload! → location.reload().

Rebuilds slower than 10 s are routine in css-in-js / large TS setups, so this is not an edge case there. With [contenthash] in dev filenames the runtime chunk's url map changes on nearly every rebuild, so the runtime chunk gets its own .hot-update.js every time, which widens the window further.

If the current behavior is a bug, please provide the steps to reproduce.

Failing unit test next to the existing "output.clean=true should keep 1 last update": erictheswift/webpack@f89a7c1 (test/unitCases/HotModuleReplacementPluginCompilation.unittest.js, "output.clean=true should keep the last update until a newer one is emitted, regardless of elapsed time").

It builds three times, waits past the 10 s window, builds a fourth time and expects the update emitted by the third build to still exist, because no newer update existed while the fourth build ran. On main the files are gone:

expect(hasFile(updates[2])).toBe(true)
Expected: true
Received: false

What is the expected behavior?

Retention of HMR update files should not depend on wall-clock time. Keep the update set of the previous compilation regardless of how long the next one takes, and drop it only once a newer update set has been emitted (files from compilation N go at the emit of N+2, not N+1). The existing test "should keep 1 last update" already describes that intent; only the 10 s timer contradicts it, and its final assertion (everything removed after 10 s) would need to change to "only the older set removed".

A count option (e.g. output.clean.keepHotUpdates) would also work, but any time-based default is wrong for builds that can exceed the constant.

Other relevant information:
webpack version: 5.108.4 (test reproduced on main at e95be18)
Node.js version: 24
Operating System: macOS
Additional tools: webpack-dev-server 6.0.0 / webpack-dev-middleware 8.0.3

Activity

  1. added a commit that references this issue on Sep 28, 2026
    94e616a
  2. added a commit that references this issue on Sep 28, 2026
    891ff49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: watchWatching, HMR, lazy compilation (lib/hmr, hot/)

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions