Repository navigation
RFC: regroup the options — cache, mime, build, and folding hot.client into hot #2454
Description
There are 32 options across three levels: 16 at the top, 10 under hot, 15 under hot.client. Individually each is defensible; together they read as an inbox rather than a design. This is the plan for fixing that, landing non-breaking first and completing at the next major.
What is actually wrong
The top level isn't "too many options" — it's that 16 scalars sit next to one 25-key object, so hot looks like the design and everything else looks like sediment. Within that list, four options are the same topic (caching), two are the same topic (mime), and four have nothing to do with serving a request at all.
hot vs hot.client is a mechanism boundary, not a concept boundary. It splits on which side of the wire applies the setting, which is not how anyone thinks about configuration — and it is why four names appear on both sides:
hot.* |
hot.client.* |
what it really is | |
|---|---|---|---|
transport |
the server speaks | the browser speaks | one setting; the client defaults to the server's |
token |
endpoint requires | browser sends | one setting; the client defaults to the server's |
progress |
publish the events | draw the badge | unrelated, sharing a word |
path |
served at | connected to | genuinely different |
Step 1 — cache and mime (doing now, non-breaking)
// before
{ etag: "strong", lastModified: true, cacheControl: "max-age=0", cacheImmutable: true,
mimeTypes: { "text/x-y": ["z"] }, mimeTypeDefault: "text/plain" }
// after
{ cache: { etag: "strong", lastModified: true, control: "max-age=0", immutable: true },
mime: { types: { "text/x-y": ["z"] }, default: "text/plain" } }6 → 2. cacheControl/cacheImmutable lose their stutter, and mimeTypeDefault — the least pleasant name in the API — stops existing.
Step 2 — build (proposed)
These four are not about answering a request, which is why writeToDisk reads like a serving option and isn't:
{ build: { fileSystem, writeToDisk, stats, serverSideRender } }serverSideRender is the weak member — it exposes stats on res.locals, so it is arguably HTTP-side. Either leave it at the top or accept the slight impurity.
Step 3 — fold hot.client into hot (proposed, next major)
hot: {
// the wire
transport: "sse", path: "/__webpack_hmr", heartbeat: 10000,
server, cors, token, inject,
// the browser, no longer in a sub-object
url, // was client.path — an absolute url when it differs from `path`
name, autoConnect, reconnect, timeout, dynamicPublicPath,
overlay, progress, logging,
hot, liveReload, reload,
}Two options stop existing: client.transport and client.token only ever mattered for a browser pointed at a different endpoint, and hot.url already carries a scheme and a query — so the token travels in it. client.path → hot.url removes the path/path collision, and progress resolves itself once the deprecated server-side option is gone (#2451).
One flat namespace, no name appearing twice, and nothing to learn about which side applies what.
Rejected: grouping the serving options
publicPath, index, methods, headers stay flat. They are the four people actually set, they match serve-static naming, and response: {} or serve: {} buys nothing but migration pain.
Where that lands
middleware(compiler, {
publicPath, index, methods, headers, // serving, flat
modifyResponseData, forwardError, // escape hatches
cache: {}, // 4 options
mime: {}, // 2
build: {}, // 4
hot: {}, // ~17, flat
});Six flat keys and four namespaces. hot stops being the odd one out.
Names worth fixing while we are here
reloadvsliveReloadis the worst pair in the API: one reloads on a build, the other reloads when an update cannot be applied.reload→reloadOnFailedUpdate.urlPrefixdoes not say it names url parameters →urlParamPrefix.
Compatibility
Every step lands the same way, which is the path hot.statsOptions is already on:
- accept both spellings, the new one winning when both are given
- warn on the legacy name, with the replacement in the message
// TODO in the next major releaseon the alias- delete at the major
Translation happens in one normalizeOptions at the entry point, so the rest of the code only ever sees the new shape, and the legacy keys are left on the options object untouched for anything reading instance.context.options.
One cost worth stating: webpack-dev-server forwards options.devMiddleware verbatim (lib/Server.js:2526), so every rename here is also a rename in devServer.devMiddleware.*. It does not block anything, but the migration note has to cover both packages.
Open questions
- Warn immediately, or stay silent until the release before the major? Warning is what moves people, but
etagandlastModifiedare common, so a minor that starts warning is noisy for configurations that are otherwise fine. - Does
buildearn its keep, or iswriteToDisk+stats+outputFileSystem+serverSideRenderfine where it is? serverSideRenderinbuild, or left at the top?
🤖 Generated with Claude Code
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
feat: group the cache and mime options (#2455)
feat: rename `hot.client.reload` to `reloadOnFailedUpdate` and `urlPr…
Jayneet2536 commentedon Oct 16, 2026
Can I work on this issue? @alexander-akait
alexander-akait commentedon Oct 16, 2026
Closing as resolved for 8.4.0:
cache.{etag,lastModified,control,immutable}andmime.{types,default}; the old names warn and keep working until the next major release.writeToDisk,stats,outputFileSystemandserverSideRenderare well named and widely used, including through webpack-dev-server'sdevMiddleware; grouping them would buy layout, not clarity, at the cost of a warning in most configurations.hot.clientstays a sub-object: the browser options, matching the entry query name for name. The collisions that motivated folding are gone:client.path→url,client.tokenremoved,reload/liveReload/hotreplaced byapplyandconnect,urlPrefix→pageParamPrefix. What is added to the compilation is now one enum,hot.inject: "all" | "plugin" | "client" | "none"(refactor: one hot.inject enum for what is added to the compilation #2484 replaces the boolean andhot.client: false).client.transportstays, since it is also how a custom client module plugs in.@Jayneet2536 thanks for offering — with this closed there is nothing left on it.
Generated by Claude Code