From 91b31dbef23fe7ee7b535ef8dcb58bd2cf01a82f Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:14:11 +0000 Subject: [PATCH 1/7] feat(hot)!: replace six browser options with `apply` and `connect` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unions, both of options that shipped after 8.3.0 — so nothing released has to keep working and no aliases are needed. `apply` replaces `hot`, `liveReload` and `reload`. Only four of their eight combinations ever differed, which the control flow says plainly: `liveReload` is read only in the `else` branch of `if (hot)`, and `reload` only inside `applyUpdate`, which the `if` branch calls. Three booleans for one decision. hot: true, reload: true -> apply: "hmr" hot: true, reload: false -> apply: "hmr-only" hot: false, liveReload: true -> apply: "reload" hot: false, liveReload: false -> apply: "nothing" `connect` replaces `autoConnect`, `reconnect` and `timeout`: `false` does not connect on load, and an object carries `retries` and `timeout`. The per-transport semantics are unchanged, still in `socket-options.js`, which is why `connect` could land without re-deriving them. The page-url parameter follows `apply` and gains from it: it used to be able only to turn something off, so `-hot=false` and `-liveReload=false` were two switches with a gap between them. `-apply=` asks for an outcome, and `=false` is still taken as `nothing` because that is what the old pair meant together. Both are the shape this codebase already uses everywhere — `overlay`, `token`, `cors`, `progress` and `transport` are all a scalar that may be an object — so neither reads as an exception. Two things found on the way. The symmetry test could not see a type with an object literal in it: `[^}]+` stopped at the brace inside `{ retries?: number }`, so `connect` looked absent from the typedef. And one live-reload case had combined a recognised `hot=false` with an unrecognised `live-reload=false`; with one option that scenario cannot exist, so it now asserts what it was really about — an unrecognised value leaves the default in force. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/hot-client-apply-and-connect.md | 26 +++ README.md | 15 +- client-src/index.js | 152 ++++++++++++------ client-src/utils/socket-options.js | 26 ++- src/hot.js | 7 +- src/options.check.js | 2 +- src/options.json | 38 +++-- test/client-socket-options.test.js | 32 ++-- .../process-update.test.js.snap.webpack5 | 13 ++ test/e2e/client.test.js | 12 +- test/e2e/inject.test.js | 4 +- test/e2e/live-reload.test.js | 47 +++--- test/e2e/process-update.test.js | 4 +- test/inject-client.test.js | 4 +- types/client/index.d.ts | 52 +++--- types/client/utils/socket-options.d.ts | 14 +- types/hot.d.ts | 22 +-- 17 files changed, 308 insertions(+), 162 deletions(-) create mode 100644 .changeset/hot-client-apply-and-connect.md diff --git a/.changeset/hot-client-apply-and-connect.md b/.changeset/hot-client-apply-and-connect.md new file mode 100644 index 000000000..7c3a59aa0 --- /dev/null +++ b/.changeset/hot-client-apply-and-connect.md @@ -0,0 +1,26 @@ +--- +"webpack-dev-middleware": minor +--- + +Two unions on the browser options, replacing six with two. + +`hot.client.apply` replaces `hot`, `liveReload` and `reload`. Only four of their eight combinations ever differed — `liveReload` was read only when HMR was off, `reload` only when it was on — so the three booleans were one decision written three ways: + +| before | now | +| ------------------------------- | ------------------- | +| `hot: true, reload: true` | `apply: "hmr"` | +| `hot: true, reload: false` | `apply: "hmr-only"` | +| `hot: false, liveReload: true` | `apply: "reload"` | +| `hot: false, liveReload: false` | `apply: "nothing"` | + +The page-url parameter follows it: `?webpack-dev-middleware-apply=nothing` in place of `-hot=false` and `-liveReload=false`, with `=false` still accepted as `nothing`. It is more expressive than before, since a page can now ask for a mode rather than only turn something off. + +`hot.client.connect` replaces `autoConnect`, `reconnect` and `timeout`. `false` does not connect on load; an object carries `retries` and `timeout`: + +```js +middleware(compiler, { + hot: { client: { connect: { retries: 3, timeout: 5000 } } }, +}); +``` + +Both options shipped after 8.3.0, so there is nothing released to keep working and no aliases are needed. diff --git a/README.md b/README.md index a86ab8d0a..07775a502 100644 --- a/README.md +++ b/README.md @@ -1025,22 +1025,25 @@ Three layers, from build to presentation: ### Opting one page out -A page can turn `hot` or `liveReload` off for itself with a url parameter, +A page can choose its own [`apply`](#client-options) mode with a url parameter, without changing anything the project is configured with — useful when you are working _in_ a page that keeps reloading under you: ``` -http://localhost:3000/?webpack-dev-middleware-liveReload=false -http://localhost:3000/?webpack-dev-middleware-hot=false +http://localhost:3000/?webpack-dev-middleware-apply=nothing +http://localhost:3000/?webpack-dev-middleware-apply=reload +http://localhost:3000/?webpack-dev-middleware-apply=false ``` -The `webpack-dev-middleware` part is the client's [`urlPrefix`](#client-options), so a server built on this middleware can set `hot.client.urlPrefix` and name these parameters after itself. What follows it is the option, spelled the one way the option is spelled. The name is matched whole and case-insensitively, and only the value `false` turns anything off — a parameter that merely contains those words, or a value such as `falsehood`, is left alone. +`false` is taken as `nothing`, which is what this parameter meant before `apply` replaced the three booleans it covers. + +The `webpack-dev-middleware` part is the client's [`urlPrefix`](#client-options), so a server built on this middleware can set `hot.client.urlPrefix` and name the parameter after itself. What follows it is the option, spelled the one way the option is spelled. The name is matched whole and case-insensitively, and a value that is not one of the modes is left alone — a parameter that merely contains the words, or a value such as `nothingness`, changes nothing. ### Reloading the page from the server Some changes belong to no compilation — a file served straight from disk, for instance — so nothing tells the page it is stale. Publish a `reload` and it -loads itself again, whatever `hot` and `liveReload` are set to: +loads itself again, whatever `apply` is set to: ```js const instance = middleware(compiler, { hot: true }); @@ -1217,7 +1220,7 @@ That replaces [`hot.progress`](#hotprogress), which applied the plugin for you a Rounding and de-duplicating are the two things `hot.progress` did that become yours, which is why the example above does both. -It is not only for progress. Any action the clients understand can be published, and `{ action: "reload" }` is the other useful one — every page loads itself again, whatever `hot` and `liveReload` are set to: +It is not only for progress. Any action the clients understand can be published, and `{ action: "reload" }` is the other useful one — every page loads itself again, whatever `apply` is set to: ```js chokidar.watch("content/**/*.md").on("change", () => { diff --git a/client-src/index.js b/client-src/index.js index 4b4600b3b..b157d76aa 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -35,21 +35,36 @@ import withToken from "./utils/with-token.js"; * @property {string=} id id of the overlay element, for a package embedding this overlay that has its own id to keep */ +/** + * What a build does to the page. One option rather than three booleans, + * because only four of their eight combinations differed: `liveReload` was + * read only when Hot Module Replacement was off, and `reload` only when it + * was on. + * + * - `"hmr"` — apply the update; reload if it cannot be applied + * - `"hmr-only"` — apply the update; say so and stop if it cannot be applied + * - `"reload"` — no Hot Module Replacement, reload on a build that changed something + * - `"nothing"` — leave the page alone until it is reloaded by hand + * @typedef {("hmr" | "hmr-only" | "reload" | "nothing")} ApplyMode + */ + +/** + * @typedef {object} ConnectOptions + * @property {number=} retries how many times to reconnect before giving up + * @property {number=} timeout how long silence is tolerated before reconnecting, in milliseconds — Server-Sent Events only + */ + /** * @typedef {object} ClientOptions * @property {("sse" | "ws")} transport how the events are carried, matching the server's `hot.transport` * @property {string} path endpoint path - * @property {number} timeout reconnection timeout in milliseconds + * @property {ApplyMode} apply what a build does to the page + * @property {boolean | ConnectOptions} connect whether to connect when the entry runs, and how the connection is held open * @property {boolean | OverlayOptions} overlay enable the in-page error overlay (same value shape as webpack-dev-server's `client.overlay`) - * @property {boolean} hot apply a build through Hot Module Replacement - * @property {boolean} liveReload reload the page on a build that changed something, when `hot` is off - * @property {boolean} reload reload the page when HMR cannot apply the update - * @property {string} urlPrefix prefix of the page-url parameters that turn `hot` and `liveReload` off for one page + * @property {string} urlPrefix prefix of the page-url parameters that override `apply` for one page * @property {LogLevel} logging logger level * @property {string} name limit updates to this compilation name * @property {string} token the secret the endpoint requires, when it requires one, put on the connection url — empty when it requires none - * @property {boolean} autoConnect connect immediately when the entry runs - * @property {number=} reconnect how many times to reconnect before giving up, unset to use the transport's default * @property {boolean | "circular" | "linear"} progress show an indicator while a rebuild is in progress — `true` and `"circular"` a small badge, `"linear"` a thin bar across the top of the viewport */ @@ -57,11 +72,9 @@ import withToken from "./utils/with-token.js"; const options = { transport: "sse", path: "/__webpack_hmr", - timeout: 20 * 1000, + apply: "hmr", + connect: true, overlay: true, - hot: true, - liveReload: true, - reload: true, urlPrefix: "webpack-dev-middleware", logging: "info", name: "", @@ -69,7 +82,6 @@ const options = { // rather than sent as a header: neither `EventSource` nor `WebSocket` lets a // page set one. token: "", - autoConnect: true, progress: true, }; @@ -133,25 +145,48 @@ function decodeOverlayOptions(overlayOptions) { setLogLevel(options.logging); +/** @type {ApplyMode[]} */ +const APPLY_MODES = ["hmr", "hmr-only", "reload", "nothing"]; + +/** + * One of the four modes, or nothing when the value is not one of them. + * `"false"` is taken as `"nothing"`, which is what the three booleans this + * replaced meant when they were all turned off. + * @param {string} value a value from the entry query or the page's url + * @returns {ApplyMode | undefined} the mode, when it is one + */ +function readApplyMode(value) { + if (value === "false") { + return "nothing"; + } + + // `indexOf` rather than `includes`: this file is compiled to ES5 and sticks + // to ES5 runtime APIs. + return APPLY_MODES.indexOf(/** @type {ApplyMode} */ (value)) === -1 + ? undefined + : /** @type {ApplyMode} */ (value); +} + /** - * Whether one of the page's own url parameters turns a setting off, which is - * how a single tab opts out of what the rest of the project is configured for - * — `?webpack-dev-middleware-liveReload=false` to stop a page reloading under - * you while you work in it, for instance. `urlPrefix` names them, so a server - * built on this middleware can name them after itself. + * What one of the page's own url parameters asks for, which is how a single + * tab opts out of what the rest of the project is configured for — + * `?webpack-dev-middleware-apply=nothing` to stop a page reloading under you + * while you work in it, for instance, or `=false` for the same thing. + * `urlPrefix` names them, so a server built on this middleware can name them + * after itself. * * The parameter is the option, spelled the one way the option is spelled. - * @param {("hot" | "liveReload")} setting which option the page may have turned off - * @returns {boolean} whether the page turned it off + * @param {string} setting which option the page may have something to say about + * @returns {string | undefined} the value the page gave it, when it gave one */ -function turnedOffByUrl(setting) { - // Parsed rather than searched for as text: `?note=…-hot=false` carries the - // words without being the parameter, and `…-hot=falsehood` is not `false`. - // The name is compared case-insensitively on both sides, so a `urlPrefix` - // with capitals in it works as written. +function urlOverride(setting) { + // Parsed rather than searched for as text: `?note=…-apply=false` carries + // the words without being the parameter. The name is compared + // case-insensitively on both sides, so a `urlPrefix` with capitals in it + // works as written. const wanted = `${options.urlPrefix}-${setting}`.toLowerCase(); // Nowhere this runs is without a url, but nothing here needs one either: an - // empty query turns nothing off. + // empty query asks for nothing. const search = typeof self === "undefined" || !self.location ? "" : self.location.search; const parameters = parseQuery(search); @@ -159,11 +194,22 @@ function turnedOffByUrl(setting) { for (let index = 0; index < names.length; index++) { if (names[index].toLowerCase() === wanted) { - return parameters[names[index]].toLowerCase() === "false"; + return parameters[names[index]].toLowerCase(); } } - return false; + return undefined; +} + +/** + * What a build should do to this page: what `apply` was set to, unless the + * page's own url asks for something else. + * @returns {ApplyMode} the mode in force for this page + */ +function applyMode() { + const override = urlOverride("apply"); + + return (override && readApplyMode(override)) || options.apply; } /** @@ -183,13 +229,24 @@ function setOverrides(overrides) { // when the endpoint is on another origin. if (overrides.path) options.path = overrides.path; if (overrides.token) options.token = overrides.token; - if (overrides.timeout) { - const timeout = Number(overrides.timeout); + if (overrides.connect) { + // A boolean or a JSON object, the same two shapes `overlay` takes. + try { + options.connect = JSON.parse(overrides.connect); + } catch { + options.connect = overrides.connect !== "false"; + } + + if (typeof options.connect === "object" && options.connect !== null) { + const { retries, timeout } = options.connect; - // A non-numeric value would make the watchdog fire in a loop (`NaN` never - // compares greater), so it is ignored rather than applied. - if (timeout > 0) { - options.timeout = timeout; + // A non-numeric timeout would make the watchdog fire in a loop (`NaN` + // never compares greater), and a negative retry count is not a count; + // either one is dropped rather than applied. + options.connect = { + ...(typeof retries === "number" && retries >= 0 ? { retries } : {}), + ...(typeof timeout === "number" && timeout > 0 ? { timeout } : {}), + }; } } if (overrides.overlay) { @@ -214,21 +271,13 @@ function setOverrides(overrides) { decodeOverlayOptions(options.overlay); } } - if (overrides.reconnect) { - const reconnect = Number(overrides.reconnect); + if (overrides.apply) { + const mode = readApplyMode(overrides.apply); - if (reconnect >= 0) { - options.reconnect = reconnect; + if (mode) { + options.apply = mode; } } - if (overrides.hot) options.hot = overrides.hot !== "false"; - // Two different things: `liveReload` is what happens on a build when `hot` - // is off, `reload` is what happens when an update was tried and could not be - // applied. - if (overrides.liveReload) { - options.liveReload = overrides.liveReload !== "false"; - } - if (overrides.reload) options.reload = overrides.reload !== "false"; if (overrides.urlPrefix) options.urlPrefix = overrides.urlPrefix; if (overrides.logging) { options.logging = /** @type {LogLevel} */ (overrides.logging); @@ -591,19 +640,22 @@ function processMessage(obj) { sendMessage(obj.action === "built" ? "Ok" : "StillOk"); } if (shouldApply) { - if (options.hot && !turnedOffByUrl("hot")) { + const mode = applyMode(); + + if (mode === "hmr" || mode === "hmr-only") { // Posted before the update is applied, in the shape // webpack-dev-server has always used for this one — a bare string // rather than the `{ type, data }` the others carry. sendMessage.raw(`webpackHotUpdate${obj.hash}`); - applyUpdate(obj.hash, options, obj.name); + // `"hmr"` falls back to loading the page when an update cannot be + // applied; `"hmr-only"` says so and stops. + applyUpdate(obj.hash, { reload: mode === "hmr" }, obj.name); } else if ( // Without Hot Module Replacement the new code can only reach the // page by loading it again. `sync` is left alone: it reports what // the page is already running. obj.action === "built" && - options.liveReload && - !turnedOffByUrl("liveReload") + mode === "reload" ) { log.info("App updated. Reloading..."); reloadPage(); @@ -716,7 +768,7 @@ if (typeof self !== "undefined") { "Include a polyfill if you want to support this browser: " + "https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools", ); - } else if (options.autoConnect) { + } else if (options.connect !== false) { connect(); } } diff --git a/client-src/utils/socket-options.js b/client-src/utils/socket-options.js index 4d901766f..857c43e3a 100644 --- a/client-src/utils/socket-options.js +++ b/client-src/utils/socket-options.js @@ -1,5 +1,5 @@ -// What the two transports make of `reconnect` and `timeout` is not the same, -// because what they can observe is not the same. +// What the two transports make of `connect`'s `retries` and `timeout` is not +// the same, because what they can observe is not the same. // // Server-Sent Events carry the heartbeat as data — `data: 💓` — so the client // can tell a silent connection from a working one, and `timeout` is how long @@ -16,17 +16,27 @@ // rather than a rule — asking for a bounded number of attempts is honoured. const DEFAULT_SSE_RETRIES = Infinity; +// How long silence is tolerated before reconnecting, when `connect` does not +// say. `EventSourceClient` has the same figure for when it is given nothing. +const DEFAULT_TIMEOUT = 20_000; + /** * How `createSocket` should hold the connection open, for the transport in use. - * @param {{ transport?: string, reconnect?: number, timeout?: number }} options the client options - * @returns {{ retries: (number | undefined), retryDelay: (() => number) | undefined, clientOptions: { timeout?: number } | undefined }} what `createSocket` takes + * @param {{ transport?: string, connect?: boolean | { retries?: number, timeout?: number } }} options the client options + * @returns {{ retries: (number | undefined), retryDelay: (() => number) | undefined, clientOptions: { timeout: number } | undefined }} what `createSocket` takes */ export default function socketOptions(options) { const isEventSource = options.transport !== "ws"; + const connect = + typeof options.connect === "object" && options.connect !== null + ? options.connect + : {}; + const timeout = + connect.timeout === undefined ? DEFAULT_TIMEOUT : connect.timeout; if (!isEventSource) { return { - retries: options.reconnect, + retries: connect.retries, // Left to `createSocket`, which backs off: a WebSocket that dropped // because the server is restarting should not be asked again on a fixed // interval. @@ -39,8 +49,8 @@ export default function socketOptions(options) { return { retries: - options.reconnect === undefined ? DEFAULT_SSE_RETRIES : options.reconnect, - retryDelay: () => /** @type {number} */ (options.timeout), - clientOptions: { timeout: options.timeout }, + connect.retries === undefined ? DEFAULT_SSE_RETRIES : connect.retries, + retryDelay: () => timeout, + clientOptions: { timeout }, }; } diff --git a/src/hot.js b/src/hot.js index ec3540f7a..d152731c8 100644 --- a/src/hot.js +++ b/src/hot.js @@ -32,10 +32,9 @@ * @property {string=} token the secret the runtime puts on its connection url, `hot.token` by default * @property {(boolean | Record)=} overlay show build problems and uncaught runtime errors in an overlay * @property {(boolean | "circular" | "linear")=} progress show an indicator while a rebuild is in progress - * @property {boolean=} hot apply a build through Hot Module Replacement - * @property {boolean=} liveReload reload the page on a build that changed something, when `hot` is off - * @property {boolean=} reload reload the page when an update cannot be applied - * @property {string=} urlPrefix name of the page-url parameters that turn `hot` and `liveReload` off for a single page + * @property {("hmr" | "hmr-only" | "reload" | "nothing")=} apply what a build does to the page — apply the update and reload if it cannot be applied, apply it and stop with a message if it cannot, load the page again on any build that changed something, or leave the page alone + * @property {(boolean | { retries?: number, timeout?: number })=} connect whether to connect when the entry runs, and how the connection is held open + * @property {string=} urlPrefix prefix of the page-url parameter that overrides `apply` for a single page * @property {("none" | "error" | "warn" | "info" | "log" | "verbose")=} logging how much the runtime logs to the browser console * @property {number=} reconnect how many times to reconnect before giving up; unset, Server-Sent Events keep trying for as long as the page is open while a WebSocket gives up after 10 * @property {number=} timeout how long the runtime tolerates silence before reconnecting, in milliseconds — Server-Sent Events only, since a WebSocket's heartbeat is a protocol ping JavaScript cannot see diff --git a/src/options.check.js b/src/options.check.js index a294bd4c8..536c4de72 100644 --- a/src/options.check.js +++ b/src/options.check.js @@ -2,4 +2,4 @@ // DO NOT MODIFY BY HAND. Run `npm run fix:schema-check` to update. /* eslint-disable */ // @ts-nocheck -"use strict";module.exports = validate10;module.exports.default = validate10;const schema11 = {"definitions":{"CorsOrigin":{"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]}},"type":"object","properties":{"cache":{"description":"How responses are cached.","link":"https://github.com/webpack/webpack-dev-middleware#cache","type":"object","additionalProperties":false,"properties":{"etag":{"description":"Enable or disable etag generation.","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","type":"boolean"},"control":{"description":"Enable or disable setting `Cache-Control` response header.","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"immutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","type":"boolean"}}},"mime":{"description":"How a file's media type is decided.","link":"https://github.com/webpack/webpack-dev-middleware#mime","type":"object","additionalProperties":false,"properties":{"types":{"description":"Allows a user to register custom mime types or extension mappings.","type":"object"},"default":{"description":"Allows a user to register a default mime type when we can't determine the content type.","type":"string"}}},"mimeTypes":{"description":"Allows a user to register custom mime types or extension mappings.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypes","type":"object"},"mimeTypeDefault":{"description":"Allows a user to register a default mime type when we can't determine the content type.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypedefault","type":"string"},"writeToDisk":{"description":"Allows to write generated files on disk.","link":"https://github.com/webpack/webpack-dev-middleware#writetodisk","anyOf":[{"type":"boolean"},{"instanceof":"Function"}]},"methods":{"description":"Allows to pass the list of HTTP request methods accepted by the middleware.","link":"https://github.com/webpack/webpack-dev-middleware#methods","type":"array","items":{"type":"string","minLength":1}},"headers":{"anyOf":[{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"key":{"description":"key of header.","type":"string"},"value":{"description":"value of header.","type":"string"}}},"minItems":1},{"type":"object"},{"instanceof":"Function"}],"description":"Allows to pass custom HTTP headers on each request","link":"https://github.com/webpack/webpack-dev-middleware#headers"},"publicPath":{"description":"The `publicPath` specifies the public URL address of the output files when referenced in a browser.","link":"https://github.com/webpack/webpack-dev-middleware#publicpath","anyOf":[{"enum":["auto"]},{"type":"string"},{"instanceof":"Function"}]},"stats":{"description":"Stats options object or preset name.","link":"https://github.com/webpack/webpack-dev-middleware#stats","anyOf":[{"enum":["none","summary","errors-only","errors-warnings","minimal","normal","detailed","verbose"]},{"type":"boolean"},{"type":"object","additionalProperties":true}]},"serverSideRender":{"description":"Instructs the module to enable or disable the server-side rendering mode.","link":"https://github.com/webpack/webpack-dev-middleware#serversiderender","type":"boolean"},"outputFileSystem":{"description":"Set the default file system which will be used by webpack as primary destination of generated files.","link":"https://github.com/webpack/webpack-dev-middleware#outputfilesystem","type":"object"},"index":{"description":"Allows to serve an index of the directory.","link":"https://github.com/webpack/webpack-dev-middleware#index","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"modifyResponseData":{"description":"Allows to set up a callback to change the response data.","link":"https://github.com/webpack/webpack-dev-middleware#modifyresponsedata","instanceof":"Function"},"etag":{"description":"Enable or disable etag generation.","link":"https://github.com/webpack/webpack-dev-middleware#etag","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","link":"https://github.com/webpack/webpack-dev-middleware#lastmodified","type":"boolean"},"cacheControl":{"description":"Enable or disable setting `Cache-Control` response header.","link":"https://github.com/webpack/webpack-dev-middleware#cachecontrol","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"cacheImmutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","link":"https://github.com/webpack/webpack-dev-middleware#cacheimmutable","type":"boolean"},"forwardError":{"description":"Enable or disable forwarding errors to next middleware.","link":"https://github.com/webpack/webpack-dev-middleware#forwarderrors","type":"boolean"},"hot":{"description":"Enable hot module replacement over a Server-Sent Events or WebSocket endpoint.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":false,"properties":{"transport":{"description":"How events reach the clients: `sse`, `ws` (needs the optional `ws` dependency and an HTTP server to answer upgrades on, given as `server` or through the middleware's `attach` method), or a function building a transport of your own.","anyOf":[{"enum":["sse","ws"]},{"instanceof":"Function"}]},"path":{"description":"The path the endpoint is served at. Must start with a slash and carry no query string or fragment.","type":"string","pattern":"^/[^?#]*$"},"heartbeat":{"description":"Heartbeat interval (in milliseconds) used to keep the connection alive.","type":"number","minimum":1},"server":{"description":"HTTP server the `ws` transport answers upgrades on, when it is already built. Otherwise hand it over later with the middleware's `attach` method.","type":"object","additionalProperties":true},"progress":{"description":"Publish compilation progress events to the clients.","type":"boolean"},"token":{"description":"A secret the injected client carries and the endpoint requires, so reaching the stream takes something a page has to have been given rather than a header the browser may not send. `true` mints one per run, a string uses that one — for a client of your own that has to build the url itself — and `false` requires none. Defaults to `false`, because requiring one would refuse a client this middleware did not inject; `true` in the next major release.","link":"https://github.com/webpack/webpack-dev-middleware#hottoken","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"cors":{"description":"Which origins may read the Server-Sent Events endpoint from a page on another one. Local origins only by default. `true` grants every origin, which lets any site the developer has open read the build's errors, including the source frames webpack puts in them.","link":"https://github.com/webpack/webpack-dev-middleware#hotcors","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"},{"type":"object","additionalProperties":false,"properties":{"origin":{"description":"Which origins may read it, as Vite and `expressjs/cors` are configured.","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"}]}}}]},"statsOptions":{"description":"Deprecated, do not use, will be removed in the next major release. Use the `stats` option instead, which decides whether a payload carries errors and warnings.","type":"object","additionalProperties":true},"inject":{"description":"Add the hot client entry and HotModuleReplacementPlugin to the compilation. Turn it off to wire them yourself.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"path":{"description":"Where the runtime connects. Defaults to the resolved `hot.path`; may be an absolute url for an endpoint reached on another origin or through a proxy.","type":"string","minLength":1},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","type":"string"},"token":{"description":"The secret the runtime puts on its connection url. The middleware sets this to whatever 'hot.token' resolved to, so it only needs setting for a client pointed at another endpoint that requires a different one.","type":"string"},"overlay":{"description":"Show build problems and uncaught runtime errors in an overlay.","anyOf":[{"type":"boolean"},{"type":"object"}]},"progress":{"description":"Show an indicator while a rebuild is in progress.","anyOf":[{"type":"boolean"},{"enum":["circular","linear"]}]},"hot":{"description":"Apply a build through Hot Module Replacement.","type":"boolean"},"liveReload":{"description":"Reload the page on a build that changed something, when `hot` is off.","type":"boolean"},"reload":{"description":"Reload the page when an update cannot be applied.","type":"boolean"},"urlPrefix":{"description":"Names the page-url parameters that turn `hot` and `liveReload` off for a single page.","type":"string","minLength":1},"logging":{"description":"How much the runtime logs to the browser console.","enum":["none","error","warn","info","log","verbose"]},"reconnect":{"description":"How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open — a dev server is expected to come back — while a WebSocket gives up after 10. Setting it applies to both.","type":"number","minimum":0},"timeout":{"description":"How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead.","type":"number","exclusiveMinimum":0},"autoConnect":{"description":"Connect as soon as the entry runs.","type":"boolean"},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}}}}]}},"additionalProperties":false};const schema12 = {"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]};const func2 = Object.prototype.hasOwnProperty;const pattern0 = new RegExp("^/[^?#]*$", "u");function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!(func2.call(schema11.properties, key0))){validate10.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.cache !== undefined){let data0 = data.cache;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){const _errs5 = errors;for(const key1 in data0){if(!((((key1 === "etag") || (key1 === "lastModified")) || (key1 === "control")) || (key1 === "immutable"))){validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}];return false;break;}}if(_errs5 === errors){if(data0.etag !== undefined){let data1 = data0.etag;const _errs6 = errors;if(!((data1 === "weak") || (data1 === "strong"))){validate10.errors = [{instancePath:instancePath+"/cache/etag",schemaPath:"#/properties/cache/properties/etag/enum",keyword:"enum",params:{allowedValues: schema11.properties.cache.properties.etag.enum},message:"must be equal to one of the allowed values"}];return false;}var valid1 = _errs6 === errors;}else {var valid1 = true;}if(valid1){if(data0.lastModified !== undefined){const _errs7 = errors;if(typeof data0.lastModified !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/lastModified",schemaPath:"#/properties/cache/properties/lastModified/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs7 === errors;}else {var valid1 = true;}if(valid1){if(data0.control !== undefined){let data3 = data0.control;const _errs9 = errors;const _errs10 = errors;let valid2 = false;const _errs11 = errors;if(typeof data3 !== "boolean"){const err0 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs13 = errors;if(!(typeof data3 == "number")){const err1 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}var _valid0 = _errs13 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs15 = errors;if(errors === _errs15){if(typeof data3 === "string"){if(data3.length < 1){const err2 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}var _valid0 = _errs15 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs17 = errors;if(errors === _errs17){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){const _errs19 = errors;for(const key2 in data3){if(!((key2 === "maxAge") || (key2 === "immutable"))){const err4 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;break;}}if(_errs19 === errors){if(data3.maxAge !== undefined){const _errs20 = errors;if(!(typeof data3.maxAge == "number")){const err5 = {instancePath:instancePath+"/cache/control/maxAge",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/maxAge/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}var valid3 = _errs20 === errors;}else {var valid3 = true;}if(valid3){if(data3.immutable !== undefined){const _errs22 = errors;if(typeof data3.immutable !== "boolean"){const err6 = {instancePath:instancePath+"/cache/control/immutable",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}var valid3 = _errs22 === errors;}else {var valid3 = true;}}}}else {const err7 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}var _valid0 = _errs17 === errors;valid2 = valid2 || _valid0;}}}if(!valid2){const err8 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}var valid1 = _errs9 === errors;}else {var valid1 = true;}if(valid1){if(data0.immutable !== undefined){const _errs24 = errors;if(typeof data0.immutable !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/immutable",schemaPath:"#/properties/cache/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs24 === errors;}else {var valid1 = true;}}}}}}else {validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.mime !== undefined){let data7 = data.mime;const _errs26 = errors;if(errors === _errs26){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){const _errs29 = errors;for(const key3 in data7){if(!((key3 === "types") || (key3 === "default"))){validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"}];return false;break;}}if(_errs29 === errors){if(data7.types !== undefined){let data8 = data7.types;const _errs30 = errors;if(!(data8 && typeof data8 == "object" && !Array.isArray(data8))){validate10.errors = [{instancePath:instancePath+"/mime/types",schemaPath:"#/properties/mime/properties/types/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid4 = _errs30 === errors;}else {var valid4 = true;}if(valid4){if(data7.default !== undefined){const _errs32 = errors;if(typeof data7.default !== "string"){validate10.errors = [{instancePath:instancePath+"/mime/default",schemaPath:"#/properties/mime/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs32 === errors;}else {var valid4 = true;}}}}else {validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypes !== undefined){let data10 = data.mimeTypes;const _errs34 = errors;if(!(data10 && typeof data10 == "object" && !Array.isArray(data10))){validate10.errors = [{instancePath:instancePath+"/mimeTypes",schemaPath:"#/properties/mimeTypes/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid0 = _errs34 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypeDefault !== undefined){const _errs37 = errors;if(typeof data.mimeTypeDefault !== "string"){validate10.errors = [{instancePath:instancePath+"/mimeTypeDefault",schemaPath:"#/properties/mimeTypeDefault/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid0 = _errs37 === errors;}else {var valid0 = true;}if(valid0){if(data.writeToDisk !== undefined){let data12 = data.writeToDisk;const _errs40 = errors;const _errs41 = errors;let valid5 = false;const _errs42 = errors;if(typeof data12 !== "boolean"){const err9 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}var _valid1 = _errs42 === errors;valid5 = valid5 || _valid1;if(!valid5){const _errs44 = errors;if(!(data12 instanceof Function)){const err10 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}var _valid1 = _errs44 === errors;valid5 = valid5 || _valid1;}if(!valid5){const err11 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs41;if(vErrors !== null){if(_errs41){vErrors.length = _errs41;}else {vErrors = null;}}}var valid0 = _errs40 === errors;}else {var valid0 = true;}if(valid0){if(data.methods !== undefined){let data13 = data.methods;const _errs46 = errors;if(errors === _errs46){if(Array.isArray(data13)){var valid6 = true;const len0 = data13.length;for(let i0=0; i0=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/hot/heartbeat",schemaPath:"#/properties/hot/anyOf/1/properties/heartbeat/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}var valid16 = _errs137 === errors;}else {var valid16 = true;}if(valid16){if(data32.server !== undefined){let data36 = data32.server;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/server",schemaPath:"#/properties/hot/anyOf/1/properties/server/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}var valid16 = _errs139 === errors;}else {var valid16 = true;}if(valid16){if(data32.progress !== undefined){const _errs142 = errors;if(typeof data32.progress !== "boolean"){const err52 = {instancePath:instancePath+"/hot/progress",schemaPath:"#/properties/hot/anyOf/1/properties/progress/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}var valid16 = _errs142 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs144 = errors;const _errs145 = errors;let valid18 = false;const _errs146 = errors;if(typeof data38 !== "boolean"){const err53 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid9 = _errs146 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs148 = errors;if(errors === _errs148){if(typeof data38 === "string"){if(data38.length < 1){const err54 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}else {const err55 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}var _valid9 = _errs148 === errors;valid18 = valid18 || _valid9;}if(!valid18){const err56 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}else {errors = _errs145;if(vErrors !== null){if(_errs145){vErrors.length = _errs145;}else {vErrors = null;}}}var valid16 = _errs144 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs151 = errors;const _errs152 = errors;let valid19 = false;const _errs153 = errors;if(typeof data39 !== "boolean"){const err57 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/properties/hot/anyOf/1/properties/cors/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}var _valid10 = _errs153 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs155 = errors;const _errs157 = errors;let valid21 = false;const _errs158 = errors;if(errors === _errs158){if(typeof data39 === "string"){if(data39.length < 1){const err58 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}var _valid11 = _errs158 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs160 = errors;if(!(data39 instanceof RegExp)){const err60 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}var _valid11 = _errs160 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = errors;if(errors === _errs161){if(Array.isArray(data39)){if(data39.length < 1){const err61 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/2/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}else {var valid22 = true;const len2 = data39.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/hot/client/reconnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reconnect/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}var valid30 = _errs228 === errors;}else {var valid30 = true;}if(valid30){if(data45.timeout !== undefined){let data58 = data45.timeout;const _errs230 = errors;if(errors === _errs230){if(typeof data58 == "number"){if(data58 <= 0 || isNaN(data58)){const err107 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}var valid30 = _errs230 === errors;}else {var valid30 = true;}if(valid30){if(data45.autoConnect !== undefined){const _errs232 = errors;if(typeof data45.autoConnect !== "boolean"){const err109 = {instancePath:instancePath+"/hot/client/autoConnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/autoConnect/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}var valid30 = _errs232 === errors;}else {var valid30 = true;}if(valid30){if(data45.dynamicPublicPath !== undefined){const _errs234 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err110 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}var valid30 = _errs234 === errors;}else {var valid30 = true;}}}}}}}}}}}}}}}}}else {const err111 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}var valid16 = _errs197 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err112 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err113 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs125;if(vErrors !== null){if(_errs125){vErrors.length = _errs125;}else {vErrors = null;}}}var valid0 = _errs124 === errors;}else {var valid0 = true;}}}}}}}}}}}}}}}}}}}}}else {validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate10.errors = vErrors;return errors === 0;} \ No newline at end of file +"use strict";module.exports = validate10;module.exports.default = validate10;const schema11 = {"definitions":{"CorsOrigin":{"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]}},"type":"object","properties":{"cache":{"description":"How responses are cached.","link":"https://github.com/webpack/webpack-dev-middleware#cache","type":"object","additionalProperties":false,"properties":{"etag":{"description":"Enable or disable etag generation.","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","type":"boolean"},"control":{"description":"Enable or disable setting `Cache-Control` response header.","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"immutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","type":"boolean"}}},"mime":{"description":"How a file's media type is decided.","link":"https://github.com/webpack/webpack-dev-middleware#mime","type":"object","additionalProperties":false,"properties":{"types":{"description":"Allows a user to register custom mime types or extension mappings.","type":"object"},"default":{"description":"Allows a user to register a default mime type when we can't determine the content type.","type":"string"}}},"mimeTypes":{"description":"Allows a user to register custom mime types or extension mappings.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypes","type":"object"},"mimeTypeDefault":{"description":"Allows a user to register a default mime type when we can't determine the content type.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypedefault","type":"string"},"writeToDisk":{"description":"Allows to write generated files on disk.","link":"https://github.com/webpack/webpack-dev-middleware#writetodisk","anyOf":[{"type":"boolean"},{"instanceof":"Function"}]},"methods":{"description":"Allows to pass the list of HTTP request methods accepted by the middleware.","link":"https://github.com/webpack/webpack-dev-middleware#methods","type":"array","items":{"type":"string","minLength":1}},"headers":{"anyOf":[{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"key":{"description":"key of header.","type":"string"},"value":{"description":"value of header.","type":"string"}}},"minItems":1},{"type":"object"},{"instanceof":"Function"}],"description":"Allows to pass custom HTTP headers on each request","link":"https://github.com/webpack/webpack-dev-middleware#headers"},"publicPath":{"description":"The `publicPath` specifies the public URL address of the output files when referenced in a browser.","link":"https://github.com/webpack/webpack-dev-middleware#publicpath","anyOf":[{"enum":["auto"]},{"type":"string"},{"instanceof":"Function"}]},"stats":{"description":"Stats options object or preset name.","link":"https://github.com/webpack/webpack-dev-middleware#stats","anyOf":[{"enum":["none","summary","errors-only","errors-warnings","minimal","normal","detailed","verbose"]},{"type":"boolean"},{"type":"object","additionalProperties":true}]},"serverSideRender":{"description":"Instructs the module to enable or disable the server-side rendering mode.","link":"https://github.com/webpack/webpack-dev-middleware#serversiderender","type":"boolean"},"outputFileSystem":{"description":"Set the default file system which will be used by webpack as primary destination of generated files.","link":"https://github.com/webpack/webpack-dev-middleware#outputfilesystem","type":"object"},"index":{"description":"Allows to serve an index of the directory.","link":"https://github.com/webpack/webpack-dev-middleware#index","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"modifyResponseData":{"description":"Allows to set up a callback to change the response data.","link":"https://github.com/webpack/webpack-dev-middleware#modifyresponsedata","instanceof":"Function"},"etag":{"description":"Enable or disable etag generation.","link":"https://github.com/webpack/webpack-dev-middleware#etag","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","link":"https://github.com/webpack/webpack-dev-middleware#lastmodified","type":"boolean"},"cacheControl":{"description":"Enable or disable setting `Cache-Control` response header.","link":"https://github.com/webpack/webpack-dev-middleware#cachecontrol","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"cacheImmutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","link":"https://github.com/webpack/webpack-dev-middleware#cacheimmutable","type":"boolean"},"forwardError":{"description":"Enable or disable forwarding errors to next middleware.","link":"https://github.com/webpack/webpack-dev-middleware#forwarderrors","type":"boolean"},"hot":{"description":"Enable hot module replacement over a Server-Sent Events or WebSocket endpoint.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":false,"properties":{"transport":{"description":"How events reach the clients: `sse`, `ws` (needs the optional `ws` dependency and an HTTP server to answer upgrades on, given as `server` or through the middleware's `attach` method), or a function building a transport of your own.","anyOf":[{"enum":["sse","ws"]},{"instanceof":"Function"}]},"path":{"description":"The path the endpoint is served at. Must start with a slash and carry no query string or fragment.","type":"string","pattern":"^/[^?#]*$"},"heartbeat":{"description":"Heartbeat interval (in milliseconds) used to keep the connection alive.","type":"number","minimum":1},"server":{"description":"HTTP server the `ws` transport answers upgrades on, when it is already built. Otherwise hand it over later with the middleware's `attach` method.","type":"object","additionalProperties":true},"progress":{"description":"Publish compilation progress events to the clients.","type":"boolean"},"token":{"description":"A secret the injected client carries and the endpoint requires, so reaching the stream takes something a page has to have been given rather than a header the browser may not send. `true` mints one per run, a string uses that one — for a client of your own that has to build the url itself — and `false` requires none. Defaults to `false`, because requiring one would refuse a client this middleware did not inject; `true` in the next major release.","link":"https://github.com/webpack/webpack-dev-middleware#hottoken","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"cors":{"description":"Which origins may read the Server-Sent Events endpoint from a page on another one. Local origins only by default. `true` grants every origin, which lets any site the developer has open read the build's errors, including the source frames webpack puts in them.","link":"https://github.com/webpack/webpack-dev-middleware#hotcors","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"},{"type":"object","additionalProperties":false,"properties":{"origin":{"description":"Which origins may read it, as Vite and `expressjs/cors` are configured.","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"}]}}}]},"statsOptions":{"description":"Deprecated, do not use, will be removed in the next major release. Use the `stats` option instead, which decides whether a payload carries errors and warnings.","type":"object","additionalProperties":true},"inject":{"description":"Add the hot client entry and HotModuleReplacementPlugin to the compilation. Turn it off to wire them yourself.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"path":{"description":"Where the runtime connects. Defaults to the resolved `hot.path`; may be an absolute url for an endpoint reached on another origin or through a proxy.","type":"string","minLength":1},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","type":"string"},"token":{"description":"The secret the runtime puts on its connection url. The middleware sets this to whatever 'hot.token' resolved to, so it only needs setting for a client pointed at another endpoint that requires a different one.","type":"string"},"overlay":{"description":"Show build problems and uncaught runtime errors in an overlay.","anyOf":[{"type":"boolean"},{"type":"object"}]},"progress":{"description":"Show an indicator while a rebuild is in progress.","anyOf":[{"type":"boolean"},{"enum":["circular","linear"]}]},"hot":{"description":"Apply a build through Hot Module Replacement.","type":"boolean"},"liveReload":{"description":"Reload the page on a build that changed something, when `hot` is off.","type":"boolean"},"reload":{"description":"Reload the page when an update cannot be applied.","type":"boolean"},"urlPrefix":{"description":"Names the page-url parameters that turn `hot` and `liveReload` off for a single page.","type":"string","minLength":1},"logging":{"description":"How much the runtime logs to the browser console.","enum":["none","error","warn","info","log","verbose"]},"reconnect":{"description":"How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open — a dev server is expected to come back — while a WebSocket gives up after 10. Setting it applies to both.","type":"number","minimum":0},"timeout":{"description":"How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead.","type":"number","exclusiveMinimum":0},"autoConnect":{"description":"Connect as soon as the entry runs.","type":"boolean"},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}}}}]}},"additionalProperties":false};const schema12 = {"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]};const func2 = Object.prototype.hasOwnProperty;const pattern0 = new RegExp("^/[^?#]*$", "u");function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!(func2.call(schema11.properties, key0))){validate10.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.cache !== undefined){let data0 = data.cache;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){const _errs5 = errors;for(const key1 in data0){if(!((((key1 === "etag") || (key1 === "lastModified")) || (key1 === "control")) || (key1 === "immutable"))){validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}];return false;break;}}if(_errs5 === errors){if(data0.etag !== undefined){let data1 = data0.etag;const _errs6 = errors;if(!((data1 === "weak") || (data1 === "strong"))){validate10.errors = [{instancePath:instancePath+"/cache/etag",schemaPath:"#/properties/cache/properties/etag/enum",keyword:"enum",params:{allowedValues: schema11.properties.cache.properties.etag.enum},message:"must be equal to one of the allowed values"}];return false;}var valid1 = _errs6 === errors;}else {var valid1 = true;}if(valid1){if(data0.lastModified !== undefined){const _errs7 = errors;if(typeof data0.lastModified !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/lastModified",schemaPath:"#/properties/cache/properties/lastModified/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs7 === errors;}else {var valid1 = true;}if(valid1){if(data0.control !== undefined){let data3 = data0.control;const _errs9 = errors;const _errs10 = errors;let valid2 = false;const _errs11 = errors;if(typeof data3 !== "boolean"){const err0 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs13 = errors;if(!(typeof data3 == "number")){const err1 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}var _valid0 = _errs13 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs15 = errors;if(errors === _errs15){if(typeof data3 === "string"){if(data3.length < 1){const err2 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}var _valid0 = _errs15 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs17 = errors;if(errors === _errs17){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){const _errs19 = errors;for(const key2 in data3){if(!((key2 === "maxAge") || (key2 === "immutable"))){const err4 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;break;}}if(_errs19 === errors){if(data3.maxAge !== undefined){const _errs20 = errors;if(!(typeof data3.maxAge == "number")){const err5 = {instancePath:instancePath+"/cache/control/maxAge",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/maxAge/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}var valid3 = _errs20 === errors;}else {var valid3 = true;}if(valid3){if(data3.immutable !== undefined){const _errs22 = errors;if(typeof data3.immutable !== "boolean"){const err6 = {instancePath:instancePath+"/cache/control/immutable",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}var valid3 = _errs22 === errors;}else {var valid3 = true;}}}}else {const err7 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}var _valid0 = _errs17 === errors;valid2 = valid2 || _valid0;}}}if(!valid2){const err8 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}var valid1 = _errs9 === errors;}else {var valid1 = true;}if(valid1){if(data0.immutable !== undefined){const _errs24 = errors;if(typeof data0.immutable !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/immutable",schemaPath:"#/properties/cache/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs24 === errors;}else {var valid1 = true;}}}}}}else {validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.mime !== undefined){let data7 = data.mime;const _errs26 = errors;if(errors === _errs26){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){const _errs29 = errors;for(const key3 in data7){if(!((key3 === "types") || (key3 === "default"))){validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"}];return false;break;}}if(_errs29 === errors){if(data7.types !== undefined){let data8 = data7.types;const _errs30 = errors;if(!(data8 && typeof data8 == "object" && !Array.isArray(data8))){validate10.errors = [{instancePath:instancePath+"/mime/types",schemaPath:"#/properties/mime/properties/types/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid4 = _errs30 === errors;}else {var valid4 = true;}if(valid4){if(data7.default !== undefined){const _errs32 = errors;if(typeof data7.default !== "string"){validate10.errors = [{instancePath:instancePath+"/mime/default",schemaPath:"#/properties/mime/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs32 === errors;}else {var valid4 = true;}}}}else {validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypes !== undefined){let data10 = data.mimeTypes;const _errs34 = errors;if(!(data10 && typeof data10 == "object" && !Array.isArray(data10))){validate10.errors = [{instancePath:instancePath+"/mimeTypes",schemaPath:"#/properties/mimeTypes/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid0 = _errs34 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypeDefault !== undefined){const _errs37 = errors;if(typeof data.mimeTypeDefault !== "string"){validate10.errors = [{instancePath:instancePath+"/mimeTypeDefault",schemaPath:"#/properties/mimeTypeDefault/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid0 = _errs37 === errors;}else {var valid0 = true;}if(valid0){if(data.writeToDisk !== undefined){let data12 = data.writeToDisk;const _errs40 = errors;const _errs41 = errors;let valid5 = false;const _errs42 = errors;if(typeof data12 !== "boolean"){const err9 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}var _valid1 = _errs42 === errors;valid5 = valid5 || _valid1;if(!valid5){const _errs44 = errors;if(!(data12 instanceof Function)){const err10 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}var _valid1 = _errs44 === errors;valid5 = valid5 || _valid1;}if(!valid5){const err11 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs41;if(vErrors !== null){if(_errs41){vErrors.length = _errs41;}else {vErrors = null;}}}var valid0 = _errs40 === errors;}else {var valid0 = true;}if(valid0){if(data.methods !== undefined){let data13 = data.methods;const _errs46 = errors;if(errors === _errs46){if(Array.isArray(data13)){var valid6 = true;const len0 = data13.length;for(let i0=0; i0=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/hot/heartbeat",schemaPath:"#/properties/hot/anyOf/1/properties/heartbeat/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}var valid16 = _errs137 === errors;}else {var valid16 = true;}if(valid16){if(data32.server !== undefined){let data36 = data32.server;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/server",schemaPath:"#/properties/hot/anyOf/1/properties/server/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}var valid16 = _errs139 === errors;}else {var valid16 = true;}if(valid16){if(data32.progress !== undefined){const _errs142 = errors;if(typeof data32.progress !== "boolean"){const err52 = {instancePath:instancePath+"/hot/progress",schemaPath:"#/properties/hot/anyOf/1/properties/progress/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}var valid16 = _errs142 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs144 = errors;const _errs145 = errors;let valid18 = false;const _errs146 = errors;if(typeof data38 !== "boolean"){const err53 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid9 = _errs146 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs148 = errors;if(errors === _errs148){if(typeof data38 === "string"){if(data38.length < 1){const err54 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}else {const err55 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}var _valid9 = _errs148 === errors;valid18 = valid18 || _valid9;}if(!valid18){const err56 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}else {errors = _errs145;if(vErrors !== null){if(_errs145){vErrors.length = _errs145;}else {vErrors = null;}}}var valid16 = _errs144 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs151 = errors;const _errs152 = errors;let valid19 = false;const _errs153 = errors;if(typeof data39 !== "boolean"){const err57 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/properties/hot/anyOf/1/properties/cors/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}var _valid10 = _errs153 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs155 = errors;const _errs157 = errors;let valid21 = false;const _errs158 = errors;if(errors === _errs158){if(typeof data39 === "string"){if(data39.length < 1){const err58 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}var _valid11 = _errs158 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs160 = errors;if(!(data39 instanceof RegExp)){const err60 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}var _valid11 = _errs160 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = errors;if(errors === _errs161){if(Array.isArray(data39)){if(data39.length < 1){const err61 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/2/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}else {var valid22 = true;const len2 = data39.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/hot/client/reconnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reconnect/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}var valid30 = _errs228 === errors;}else {var valid30 = true;}if(valid30){if(data45.timeout !== undefined){let data58 = data45.timeout;const _errs230 = errors;if(errors === _errs230){if(typeof data58 == "number"){if(data58 <= 0 || isNaN(data58)){const err107 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}var valid30 = _errs230 === errors;}else {var valid30 = true;}if(valid30){if(data45.autoConnect !== undefined){const _errs232 = errors;if(typeof data45.autoConnect !== "boolean"){const err109 = {instancePath:instancePath+"/hot/client/autoConnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/autoConnect/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}var valid30 = _errs232 === errors;}else {var valid30 = true;}if(valid30){if(data45.dynamicPublicPath !== undefined){const _errs234 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err110 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}var valid30 = _errs234 === errors;}else {var valid30 = true;}}}}}}}}}}}}}}}}}else {const err111 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}var valid16 = _errs197 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err112 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err113 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs125;if(vErrors !== null){if(_errs125){vErrors.length = _errs125;}else {vErrors = null;}}}var valid0 = _errs124 === errors;}else {var valid0 = true;}}}}}}}}}}}}}}}}}}}}}else {validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate10.errors = vErrors;return errors === 0;} diff --git a/src/options.json b/src/options.json index 632a5cd5a..8d2c138e6 100644 --- a/src/options.json +++ b/src/options.json @@ -411,20 +411,36 @@ } ] }, - "hot": { - "description": "Apply a build through Hot Module Replacement.", - "type": "boolean" - }, - "liveReload": { - "description": "Reload the page on a build that changed something, when `hot` is off.", - "type": "boolean" + "apply": { + "description": "What a build does to the page: `hmr` applies the update and reloads if it cannot be applied, `hmr-only` applies it and stops with a message if it cannot, `reload` loads the page again on a build that changed something, and `nothing` leaves the page alone until you reload it.", + "enum": ["hmr", "hmr-only", "reload", "nothing"] }, - "reload": { - "description": "Reload the page when an update cannot be applied.", - "type": "boolean" + "connect": { + "description": "Whether to connect when the entry runs, and how the connection is held open. `false` does not connect; an object sets `retries` and `timeout`.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "retries": { + "description": "How many times to reconnect before giving up. Server-Sent Events keep trying for as long as the page is open unless this is set, since a dev server is expected to come back.", + "type": "number", + "minimum": 0 + }, + "timeout": { + "description": "How long silence is tolerated before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript.", + "type": "number", + "minimum": 1 + } + } + } + ] }, "urlPrefix": { - "description": "Names the page-url parameters that turn `hot` and `liveReload` off for a single page.", + "description": "Prefix of the page-url parameter that overrides `apply` for a single page.", "type": "string", "minLength": 1 }, diff --git a/test/client-socket-options.test.js b/test/client-socket-options.test.js index 8c82fbeac..76a470f2e 100644 --- a/test/client-socket-options.test.js +++ b/test/client-socket-options.test.js @@ -30,26 +30,34 @@ describe("what each transport makes of reconnect and timeout", () => { it("keeps trying for as long as the page is open, by default", () => { // A dev server is expected to come back, and a tab left open across a // restart has to find it again. - expect(socketOptions({ timeout: 20_000 }).retries).toBe(Infinity); + expect(socketOptions({ connect: { timeout: 20_000 } }).retries).toBe( + Infinity, + ); }); it("honours a bounded number of attempts when one was asked for", () => { // This is the half that did nothing before. - expect(socketOptions({ reconnect: 3, timeout: 20_000 }).retries).toBe(3); + expect( + socketOptions({ connect: { retries: 3, timeout: 20_000 } }).retries, + ).toBe(3); }); - it("takes `reconnect: 0` as none rather than as unset", () => { - expect(socketOptions({ reconnect: 0, timeout: 20_000 }).retries).toBe(0); + it("takes `retries: 0` as none rather than as unset", () => { + expect( + socketOptions({ connect: { retries: 0, timeout: 20_000 } }).retries, + ).toBe(0); }); it("retries at the interval its watchdog already waits", () => { - const { retryDelay } = socketOptions({ timeout: 5000 }); + const { retryDelay } = socketOptions({ connect: { timeout: 5000 } }); expect(/** @type {() => number} */ (retryDelay)()).toBe(5000); }); it("gives the client the silence it should tolerate", () => { - expect(socketOptions({ timeout: 5000 }).clientOptions).toStrictEqual({ + expect( + socketOptions({ connect: { timeout: 5000 } }).clientOptions, + ).toStrictEqual({ timeout: 5000, }); }); @@ -69,8 +77,10 @@ describe("what each transport makes of reconnect and timeout", () => { describe("a WebSocket", () => { it("takes the number of attempts as given", () => { expect( - socketOptions({ transport: "ws", reconnect: 3, timeout: 20_000 }) - .retries, + socketOptions({ + transport: "ws", + connect: { retries: 3, timeout: 20_000 }, + }).retries, ).toBe(3); }); @@ -78,7 +88,8 @@ describe("what each transport makes of reconnect and timeout", () => { // A socket that dropped because the server is restarting should not be // asked again every `timeout` milliseconds. expect( - socketOptions({ transport: "ws", timeout: 20_000 }).retryDelay, + socketOptions({ transport: "ws", connect: { timeout: 20_000 } }) + .retryDelay, ).toBeUndefined(); }); @@ -88,7 +99,8 @@ describe("what each transport makes of reconnect and timeout", () => { // fire on a healthy idle connection. The server terminates half-open // sockets instead. expect( - socketOptions({ transport: "ws", timeout: 20_000 }).clientOptions, + socketOptions({ transport: "ws", connect: { timeout: 20_000 } }) + .clientOptions, ).toBeUndefined(); }); }); diff --git a/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 b/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 index 394a8462f..ac75dce19 100644 --- a/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 @@ -23,6 +23,19 @@ exports[`update processing (browser) logs the disabled HMR runtime once and neve ] `; +exports[`update processing (browser) stays on the broken state when apply is hmr-only and an accept handler throws 1`] = ` +[ + "[webpack-dev-middleware] connected", + "[webpack-dev-middleware] bundle rebuilding ( changed)", + "[webpack-dev-middleware] bundle rebuilt in Xms", + "[webpack-dev-middleware] Checking for updates on the server...", + "[webpack-dev-middleware] Error: accept boom", + "[webpack-dev-middleware] Ignored an error while updating module ./app.js (accept-errored)", + "[webpack-dev-middleware] Hot updated 1 modules.", + "[webpack-dev-middleware] App is up to date.", +] +`; + exports[`update processing (browser) stays on the broken state when reload=false and an accept handler throws 1`] = ` [ "[webpack-dev-middleware] connected", diff --git a/test/e2e/client.test.js b/test/e2e/client.test.js index 73e86ebfd..73c675826 100644 --- a/test/e2e/client.test.js +++ b/test/e2e/client.test.js @@ -142,7 +142,7 @@ describe("hot client (browser)", () => { it("warns instead of reloading when reload=false", async () => { app = await createHotApp({ - query: "?reload=false", + query: "?apply=hmr-only", code: unacceptedApp("v1"), }); ({ page, browser } = await runBrowser()); @@ -164,7 +164,7 @@ describe("hot client (browser)", () => { it("reconnects after a server restart and syncs up on missed builds", async () => { app = await createHotApp({ - query: "?timeout=1000", + query: "?connect=%7B%22timeout%22%3A1000%7D", code: acceptedApp("v1"), // Heartbeats faster than the shortened timeout, so the inactivity // watchdog does not churn disconnect/reconnect cycles mid-test. @@ -197,7 +197,7 @@ describe("hot client (browser)", () => { it("watchdog-reconnects a silent connection and stays armed afterwards", async () => { app = await createHotApp({ - query: "?timeout=1000", + query: "?connect=%7B%22timeout%22%3A1000%7D", code: acceptedApp("v1"), // A heartbeat far beyond the client timeout leaves the connection open // but silent, so only the inactivity watchdog can trigger reconnects. @@ -379,7 +379,7 @@ describe("hot client (browser)", () => { it("keeps the page alive after instance.close()", async () => { app = await createHotApp({ - query: "?timeout=1000", + query: "?connect=%7B%22timeout%22%3A1000%7D", code: acceptedApp("v1"), }); ({ page, browser } = await runBrowser()); @@ -424,7 +424,7 @@ describe("hot client (browser)", () => { it("builds the SSE url from the runtime public path, slashes intact", async () => { app = await createHotApp({ - query: "?autoConnect=false", + query: "?connect=false", code: ` globalThis.hotClient = require(${JSON.stringify(CLIENT_ENTRY)}); globalThis.setPublicPath = (value) => { @@ -620,7 +620,7 @@ describe("hot client (browser)", () => { it("disconnect() during the reconnect window cancels the pending reconnect", async () => { app = await createHotApp({ - query: "?timeout=1000", + query: "?connect=%7B%22timeout%22%3A1000%7D", hot: { heartbeat: 300 }, code: ` globalThis.hotClient = require(${JSON.stringify(CLIENT_ENTRY)}); diff --git a/test/e2e/inject.test.js b/test/e2e/inject.test.js index 014af26f7..3c270bd66 100644 --- a/test/e2e/inject.test.js +++ b/test/e2e/inject.test.js @@ -169,7 +169,7 @@ describe("hot with nothing but the middleware (browser)", () => { code: acceptedApp("v1"), // Neither of these could be set in node before: `hot` and `liveReload` // were readable from the entry query only. - hot: { client: { hot: false, liveReload: false } }, + hot: { client: { apply: "nothing" } }, }); ({ page, browser } = await runBrowser()); @@ -204,7 +204,7 @@ describe("hot with nothing but the middleware (browser)", () => { }); ({ page, browser } = await runBrowser()); - await page.goto(`${hotApp.url}?my-server-hot=false`); + await page.goto(`${hotApp.url}?my-server-apply=nothing`); await waitForAppText(page, "v1"); await page.evaluate(() => { globalThis.notReloaded = true; diff --git a/test/e2e/live-reload.test.js b/test/e2e/live-reload.test.js index f73f24798..da66f178e 100644 --- a/test/e2e/live-reload.test.js +++ b/test/e2e/live-reload.test.js @@ -59,7 +59,7 @@ describe("live reload (browser)", () => { it("reloads the page on a build when hot is off", async () => { app = await createHotApp({ - query: "?hot=false", + query: "?apply=reload", hmrPlugin: false, code: acceptedApp("v1"), }); @@ -79,7 +79,7 @@ describe("live reload (browser)", () => { it("leaves the page alone when live reload is off as well", async () => { app = await createHotApp({ - query: "?hot=false&liveReload=false", + query: "?apply=nothing", hmrPlugin: false, code: acceptedApp("v1"), }); @@ -96,13 +96,14 @@ describe("live reload (browser)", () => { expect(await readReloadMarker(page)).toBe(true); }); - it("knows the option by one name only", async () => { - // `live-reload` was a second spelling of this. It is not one any more, so - // it reaches nothing and the page reloads as it would with no query at - // all — the failure a typo gets, which is what any other name gets too. + it("ignores a value that is not one of the modes", async () => { + // `live-reload` was a second spelling of an option this replaced. It is + // not one of the four modes, so it is ignored and the default stands — + // the failure a typo gets, which is what any other unrecognised value + // gets too. `hmr` being the default, the update is applied in place and + // the page is not loaded again. app = await createHotApp({ - query: "?hot=false&live-reload=false", - hmrPlugin: false, + query: "?apply=live-reload", code: acceptedApp("v1"), }); ({ page, browser } = await runBrowser()); @@ -114,12 +115,13 @@ describe("live reload (browser)", () => { app.edit(acceptedApp("v2")); await waitForAppText(page, "v2"); - expect(await readReloadMarker(page)).toBeUndefined(); + // Still there: nothing reloaded the page out from under it. + expect(await readReloadMarker(page)).toBe(true); }); it("does not reload for a build that changed nothing", async () => { app = await createHotApp({ - query: "?hot=false", + query: "?apply=reload", hmrPlugin: false, code: acceptedApp("v1"), }); @@ -141,17 +143,14 @@ describe("live reload (browser)", () => { it("lets one page opt out through its own url", async () => { app = await createHotApp({ - query: "?hot=false", + query: "?apply=reload", hmrPlugin: false, code: acceptedApp("v1"), }); ({ page, browser } = await runBrowser()); await page.goto( - `${app.url}?webpack-dev-middleware-liveReload=false`.replace( - /\/\?/, - "/?", - ), + `${app.url}?webpack-dev-middleware-apply=nothing`.replace(/\/\?/, "/?"), ); await waitForAppText(page, "v1"); await plantReloadMarker(page); @@ -166,7 +165,7 @@ describe("live reload (browser)", () => { it("reads the parameter, not the text of the url", async () => { app = await createHotApp({ - query: "?hot=false", + query: "?apply=reload", hmrPlugin: false, code: acceptedApp("v1"), }); @@ -174,9 +173,9 @@ describe("live reload (browser)", () => { // Both halves of the url say the words without saying the thing: the // first carries them inside another parameter's value, and the second is - // the parameter but with a value that only begins with "false". + // the parameter but with a value that only begins with a mode's name. await page.goto( - `${app.url}?note=webpack-dev-middleware-liveReload=false&webpack-dev-middleware-liveReload=falsehood`, + `${app.url}?note=webpack-dev-middleware-apply=nothing&webpack-dev-middleware-apply=nothingness`, ); await waitForAppText(page, "v1"); await plantReloadMarker(page); @@ -190,13 +189,13 @@ describe("live reload (browser)", () => { it("matches a prefix with capitals in it", async () => { app = await createHotApp({ - query: "?hot=false&urlPrefix=MyServer", + query: "?apply=reload&urlPrefix=MyServer", hmrPlugin: false, code: acceptedApp("v1"), }); ({ page, browser } = await runBrowser()); - await page.goto(`${app.url}?MyServer-liveReload=false`); + await page.goto(`${app.url}?MyServer-apply=nothing`); await waitForAppText(page, "v1"); await plantReloadMarker(page); @@ -213,7 +212,7 @@ describe("live reload (browser)", () => { app = await createHotApp({ code: acceptedApp("v1") }); ({ page, browser } = await runBrowser()); - await page.goto(`${app.url}?webpack-dev-middleware-hot=false`); + await page.goto(`${app.url}?webpack-dev-middleware-apply=reload`); await waitForAppText(page, "v1"); await plantReloadMarker(page); @@ -225,13 +224,13 @@ describe("live reload (browser)", () => { it("names those url parameters after whoever is serving", async () => { app = await createHotApp({ - query: "?hot=false&urlPrefix=my-server", + query: "?apply=reload&urlPrefix=my-server", hmrPlugin: false, code: acceptedApp("v1"), }); ({ page, browser } = await runBrowser()); - await page.goto(`${app.url}?my-server-liveReload=false`); + await page.goto(`${app.url}?my-server-apply=nothing`); await waitForAppText(page, "v1"); await plantReloadMarker(page); @@ -245,7 +244,7 @@ describe("live reload (browser)", () => { it("reloads when the server asks, whatever the build settings say", async () => { app = await createHotApp({ // Neither mechanism would reload this page on a build. - query: "?hot=false&liveReload=false", + query: "?apply=nothing", hmrPlugin: false, code: acceptedApp("v1"), }); diff --git a/test/e2e/process-update.test.js b/test/e2e/process-update.test.js index 813a02ccc..81d9e42e4 100644 --- a/test/e2e/process-update.test.js +++ b/test/e2e/process-update.test.js @@ -97,9 +97,9 @@ describe("update processing (browser)", () => { expect(await page.evaluate(() => globalThis.__notReloaded)).toBeUndefined(); }); - it("stays on the broken state when reload=false and an accept handler throws", async () => { + it("stays on the broken state when apply is hmr-only and an accept handler throws", async () => { app = await createHotApp({ - query: "?reload=false", + query: "?apply=hmr-only", code: throwingAcceptApp(), files: { "dep.js": "module.exports = 1;" }, }); diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 7b04d9b6f..4abc5f92f 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -953,7 +953,9 @@ describe("node and the query take the same names", () => { hotSource.match(/@typedef \{object\} HotClientOptions[\s\S]*?\n \*\//) ); const declared = [ - ...typedef.matchAll(/@property \{[^}]+\} ([A-Za-z]+)/g), + // One level of nesting, since a type can be an object literal — + // `{ retries?: number }` — and `[^}]+` would stop inside it. + ...typedef.matchAll(/@property \{(?:[^{}]|\{[^{}]*\})+\} ([A-Za-z]+)/g), ].map((found) => found[1]); expect(declared.toSorted()).toStrictEqual(takenInNode.toSorted()); diff --git a/types/client/index.d.ts b/types/client/index.d.ts index 087763734..48b4aeb2c 100644 --- a/types/client/index.d.ts +++ b/types/client/index.d.ts @@ -76,6 +76,28 @@ export type OverlayOptions = { */ id?: string | undefined; }; +/** + * What a build does to the page. One option rather than three booleans, + * because only four of their eight combinations differed: `liveReload` was + * read only when Hot Module Replacement was off, and `reload` only when it + * was on. + * + * - `"hmr"` — apply the update; reload if it cannot be applied + * - `"hmr-only"` — apply the update; say so and stop if it cannot be applied + * - `"reload"` — no Hot Module Replacement, reload on a build that changed something + * - `"nothing"` — leave the page alone until it is reloaded by hand + */ +export type ApplyMode = "hmr" | "hmr-only" | "reload" | "nothing"; +export type ConnectOptions = { + /** + * how many times to reconnect before giving up + */ + retries?: number | undefined; + /** + * how long silence is tolerated before reconnecting, in milliseconds — Server-Sent Events only + */ + timeout?: number | undefined; +}; export type ClientOptions = { /** * how the events are carried, matching the server's `hot.transport` @@ -86,27 +108,19 @@ export type ClientOptions = { */ path: string; /** - * reconnection timeout in milliseconds + * what a build does to the page */ - timeout: number; + apply: ApplyMode; /** - * enable the in-page error overlay (same value shape as webpack-dev-server's `client.overlay`) + * whether to connect when the entry runs, and how the connection is held open */ - overlay: boolean | OverlayOptions; + connect: boolean | ConnectOptions; /** - * apply a build through Hot Module Replacement - */ - hot: boolean; - /** - * reload the page on a build that changed something, when `hot` is off - */ - liveReload: boolean; - /** - * reload the page when HMR cannot apply the update + * enable the in-page error overlay (same value shape as webpack-dev-server's `client.overlay`) */ - reload: boolean; + overlay: boolean | OverlayOptions; /** - * prefix of the page-url parameters that turn `hot` and `liveReload` off for one page + * prefix of the page-url parameters that override `apply` for one page */ urlPrefix: string; /** @@ -121,14 +135,6 @@ export type ClientOptions = { * the secret the endpoint requires, when it requires one, put on the connection url — empty when it requires none */ token: string; - /** - * connect immediately when the entry runs - */ - autoConnect: boolean; - /** - * how many times to reconnect before giving up, unset to use the transport's default - */ - reconnect?: number | undefined; /** * show an indicator while a rebuild is in progress — `true` and `"circular"` a small badge, `"linear"` a thin bar across the top of the viewport */ diff --git a/types/client/utils/socket-options.d.ts b/types/client/utils/socket-options.d.ts index 8337613f0..8fbea1441 100644 --- a/types/client/utils/socket-options.d.ts +++ b/types/client/utils/socket-options.d.ts @@ -1,18 +1,22 @@ /** * How `createSocket` should hold the connection open, for the transport in use. - * @param {{ transport?: string, reconnect?: number, timeout?: number }} options the client options - * @returns {{ retries: (number | undefined), retryDelay: (() => number) | undefined, clientOptions: { timeout?: number } | undefined }} what `createSocket` takes + * @param {{ transport?: string, connect?: boolean | { retries?: number, timeout?: number } }} options the client options + * @returns {{ retries: (number | undefined), retryDelay: (() => number) | undefined, clientOptions: { timeout: number } | undefined }} what `createSocket` takes */ export default function socketOptions(options: { transport?: string; - reconnect?: number; - timeout?: number; + connect?: + | boolean + | { + retries?: number; + timeout?: number; + }; }): { retries: number | undefined; retryDelay: (() => number) | undefined; clientOptions: | { - timeout?: number; + timeout: number; } | undefined; }; diff --git a/types/hot.d.ts b/types/hot.d.ts index 5db6e6974..8c072a50c 100644 --- a/types/hot.d.ts +++ b/types/hot.d.ts @@ -203,19 +203,23 @@ type HotClientOptions = { */ progress?: (boolean | "circular" | "linear") | undefined; /** - * apply a build through Hot Module Replacement + * what a build does to the page — apply the update and reload if it cannot be applied, apply it and stop with a message if it cannot, load the page again on any build that changed something, or leave the page alone */ - hot?: boolean | undefined; + apply?: ("hmr" | "hmr-only" | "reload" | "nothing") | undefined; /** - * reload the page on a build that changed something, when `hot` is off + * whether to connect when the entry runs, and how the connection is held open */ - liveReload?: boolean | undefined; - /** - * reload the page when an update cannot be applied - */ - reload?: boolean | undefined; + connect?: + | ( + | boolean + | { + retries?: number; + timeout?: number; + } + ) + | undefined; /** - * name of the page-url parameters that turn `hot` and `liveReload` off for a single page + * prefix of the page-url parameter that overrides `apply` for a single page */ urlPrefix?: string | undefined; /** From cc6c9c674f6294de75d473317aa11de7b21f98fd Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:24:19 +0000 Subject: [PATCH 2/7] test(e2e): map the last opt-out fixture to the right mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `-hot=false` turned Hot Module Replacement off and left live reload on, so the build reached the page as a reload. I had mapped it to `-apply=nothing`, which is the one mode where the build reaches the page not at all — so the test waited thirty seconds for text that was never going to change. `reload` is the mode that behaviour had a name for. The equivalent fixture in `live-reload.test.js` was mapped correctly, which is what made the difference visible. Also drops the snapshot left behind by a renamed test. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .../process-update.test.js.snap.webpack5 | 13 ------------- test/e2e/inject.test.js | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 b/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 index ac75dce19..c90ec0908 100644 --- a/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/process-update.test.js.snap.webpack5 @@ -35,16 +35,3 @@ exports[`update processing (browser) stays on the broken state when apply is hmr "[webpack-dev-middleware] App is up to date.", ] `; - -exports[`update processing (browser) stays on the broken state when reload=false and an accept handler throws 1`] = ` -[ - "[webpack-dev-middleware] connected", - "[webpack-dev-middleware] bundle rebuilding ( changed)", - "[webpack-dev-middleware] bundle rebuilt in Xms", - "[webpack-dev-middleware] Checking for updates on the server...", - "[webpack-dev-middleware] Error: accept boom", - "[webpack-dev-middleware] Ignored an error while updating module ./app.js (accept-errored)", - "[webpack-dev-middleware] Hot updated 1 modules.", - "[webpack-dev-middleware] App is up to date.", -] -`; diff --git a/test/e2e/inject.test.js b/test/e2e/inject.test.js index 3c270bd66..bff22c48b 100644 --- a/test/e2e/inject.test.js +++ b/test/e2e/inject.test.js @@ -204,7 +204,7 @@ describe("hot with nothing but the middleware (browser)", () => { }); ({ page, browser } = await runBrowser()); - await page.goto(`${hotApp.url}?my-server-apply=nothing`); + await page.goto(`${hotApp.url}?my-server-apply=reload`); await waitForAppText(page, "v1"); await page.evaluate(() => { globalThis.notReloaded = true; From 8dd28419988ebeb6c243a5a6c89037f85561a326 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:19:49 +0000 Subject: [PATCH 3/7] feat(hot): keep the six names `apply` and `connect` replaced working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not a breaking change any more: `hot`, `liveReload`, `reload`, `autoConnect`, `reconnect` and `timeout` all still apply, and are folded into the two options that replaced them. hot: true, reload: true -> apply: "hmr" hot: true, reload: false -> apply: "hmr-only" hot: false, liveReload: true -> apply: "reload" hot: false, liveReload: false -> apply: "nothing" autoConnect: false -> connect: false reconnect, timeout -> connect: { retries, timeout } The new option wins when both are set: the other way round, a migration that sets it and leaves the old name behind would silently not apply. Each old name warns, and it has to warn in two places — node, when it is set on `hot.client`, and the browser, when it arrives on the query, since an entry somebody wired by hand never passes through the node side at all. The folding happens in the browser rather than in `clientQuery` for the same reason: a hand-written query has to work, and node cannot translate one it never sees. The symmetry test needed to learn about this. The deprecated names are read from a list rather than written out one by one, so `overrides.hot` does not appear in the source for it to find; it now collects them from `LEGACY_OPTIONS`, with an assertion on the length of that list so a regex that stops matching cannot quietly shrink the comparison. Five browser tests cover the folding, including the precedence rule and both warning channels. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/hot-client-apply-and-connect.md | 2 +- README.md | 32 +++---- client-src/index.js | 83 ++++++++++++++-- src/hot.js | 3 + src/options.check.js | 2 +- src/options.json | 32 +++++-- src/utils.js | 22 +++++ test/e2e/live-reload.test.js | 105 +++++++++++++++++++++ test/inject-client.test.js | 19 +++- types/hot.d.ts | 12 +++ 10 files changed, 275 insertions(+), 37 deletions(-) diff --git a/.changeset/hot-client-apply-and-connect.md b/.changeset/hot-client-apply-and-connect.md index 7c3a59aa0..7c54341d9 100644 --- a/.changeset/hot-client-apply-and-connect.md +++ b/.changeset/hot-client-apply-and-connect.md @@ -23,4 +23,4 @@ middleware(compiler, { }); ``` -Both options shipped after 8.3.0, so there is nothing released to keep working and no aliases are needed. +All six old names keep working. Each warns, in node when it is set on `hot.client` and in the browser when it arrives on the query — a hand-written entry has only the second channel — and the option that replaced it wins when both are given, so a migration that sets the new name and leaves the old one behind is not silently ignored. They are removed in the next major release. diff --git a/README.md b/README.md index 07775a502..04eaccab0 100644 --- a/README.md +++ b/README.md @@ -812,22 +812,22 @@ no aliases, in both places and in the page-url parameters below. `transport`, `p and `name` default to what the middleware resolved rather than to the value in the table, which is what they are when nothing else is serving them. -| Name | Type | Default | Description | -| :-----------------: | :---------------: | :------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `path`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | -| `path` | `string` | `/__webpack_hmr` | Where the runtime connects. Defaults to the server [`hot.path`](#hotpath); set it to an absolute url (`wss://dev.example.com/__webpack_hmr`) for an endpoint on another origin or behind a proxy. | -| `timeout` | `number` | `20000` | How long silence is tolerated before reconnecting, in milliseconds, and the interval between reconnections. `"sse"` only: its heartbeat arrives as data the client can see, while a `"ws"` heartbeat is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead. | -| `reconnect` | `number` | `Infinity` / `10` | How many times to reconnect before giving up. Unset, `"sse"` keeps trying for as long as the page is open — a dev server is expected to come back — while `"ws"` gives up after `10`. Setting it applies to both. | -| `overlay` | `boolean\|Object` | `true` | In-page overlay for problems: a boolean, or a JSON object — see [overlay options](#client-overlay-options). Same value shape as webpack-dev-server's [`client.overlay`](https://webpack.js.org/configuration/dev-server/#overlay), plus a few webpack-dev-middleware extensions. | -| `hot` | `boolean` | `true` | Apply a build through Hot Module Replacement. Set to `false` for a project without `HotModuleReplacementPlugin`, and the page is reloaded instead — see `liveReload`. | -| `liveReload` | `boolean` | `true` | Reload the page on a build that changed something, when `hot` is off. A build that changed nothing is left alone. Set both this and `hot` to `false` and a build reaches the page only when you reload it yourself. | -| `reload` | `boolean` | `true` | Fall back to a full page reload when an update cannot be applied through HMR (e.g. recovering from a broken build). Enabled by default, unlike webpack-hot-middleware; set to `false` to keep HMR-only. Unrelated to `liveReload`: this one is about an update that was tried. | -| `urlPrefix` | `string` | `"webpack-dev-middleware"` | Names the page-url parameters that turn `hot` and `liveReload` off for a single page — see [opting one page out](#opting-one-page-out). Change it if you are building a server of your own and want parameters named after it. | -| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | -| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | -| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. | -| `progress` | `boolean` | `true` | Show a small badge in the page while a rebuild is in progress (with the compilation percentage when the server enables `hot.progress`). Set to `false` to disable. | -| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. The leading slash of `path` is stripped and no other normalization is applied, so the public path should end with `/`. | +| Name | Type | Default | Description | +| :-----------------: | :---------------: | :------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `path`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | +| `path` | `string` | `/__webpack_hmr` | Where the runtime connects. Defaults to the server [`hot.path`](#hotpath); set it to an absolute url (`wss://dev.example.com/__webpack_hmr`) for an endpoint on another origin or behind a proxy. | +| `timeout` | `number` | `20000` | How long silence is tolerated before reconnecting, in milliseconds, and the interval between reconnections. `"sse"` only: its heartbeat arrives as data the client can see, while a `"ws"` heartbeat is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | +| `reconnect` | `number` | `Infinity` / `10` | How many times to reconnect before giving up. Unset, `"sse"` keeps trying for as long as the page is open — a dev server is expected to come back — while `"ws"` gives up after `10`. Setting it applies to both. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | +| `overlay` | `boolean\|Object` | `true` | In-page overlay for problems: a boolean, or a JSON object — see [overlay options](#client-overlay-options). Same value shape as webpack-dev-server's [`client.overlay`](https://webpack.js.org/configuration/dev-server/#overlay), plus a few webpack-dev-middleware extensions. | +| `hot` | `boolean` | `true` | Apply a build through Hot Module Replacement. Set to `false` for a project without `HotModuleReplacementPlugin`, and the page is reloaded instead — see `liveReload`. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `liveReload` | `boolean` | `true` | Reload the page on a build that changed something, when `hot` is off. A build that changed nothing is left alone. Set both this and `hot` to `false` and a build reaches the page only when you reload it yourself. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `reload` | `boolean` | `true` | Fall back to a full page reload when an update cannot be applied through HMR (e.g. recovering from a broken build). Enabled by default, unlike webpack-hot-middleware; set to `false` to keep HMR-only. Unrelated to `liveReload`: this one is about an update that was tried. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `urlPrefix` | `string` | `"webpack-dev-middleware"` | Names the page-url parameters that turn `hot` and `liveReload` off for a single page — see [opting one page out](#opting-one-page-out). Change it if you are building a server of your own and want parameters named after it. | +| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | +| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | +| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | +| `progress` | `boolean` | `true` | Show a small badge in the page while a rebuild is in progress (with the compilation percentage when the server enables `hot.progress`). Set to `false` to disable. | +| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. The leading slash of `path` is stripped and no other normalization is applied, so the public path should end with `/`. | #### A client of your own diff --git a/client-src/index.js b/client-src/index.js index b157d76aa..0725d5f09 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -145,6 +145,81 @@ function decodeOverlayOptions(overlayOptions) { setLogLevel(options.logging); +// The six names `apply` and `connect` replaced. Still read, still folded into +// the two that replaced them, and gone in the next major release. +// +// TODO in the next major release remove this and `LEGACY_OPTIONS`. +const LEGACY_OPTIONS = [ + "hot", + "liveReload", + "reload", + "autoConnect", + "reconnect", + "timeout", +]; + +/** + * Was it set at all, and if so is it anything but `"false"`? The reading every + * boolean on this query has always had. + * @param {string | undefined} value the raw value + * @param {boolean} fallback what it means when it was not set + * @returns {boolean} what it says + */ +function legacyBoolean(value, fallback) { + return value === undefined ? fallback : value !== "false"; +} + +/** + * Read the deprecated spellings and fold them into the options that replaced + * them. The new spelling wins when both are given, so a migration that sets it + * and leaves the old one behind is not silently ignored. + * @param {Record} overrides parsed query-string overrides + * @returns {void} + */ +function foldLegacyOptions(overrides) { + const used = LEGACY_OPTIONS.filter((name) => overrides[name] !== undefined); + + if (used.length === 0) { + return; + } + + log.warn( + `${used.join(", ")} ${used.length === 1 ? "is" : "are"} deprecated and will be removed in the next major release. Use 'apply' and 'connect' instead.`, + ); + + if (overrides.apply === undefined) { + // `hot` decided whether an update was applied in place; `reload` what + // happened when it could not be; `liveReload` what happened instead when + // `hot` was off. Four of their eight combinations differed, which is what + // the four modes are. + const hot = legacyBoolean(overrides.hot, true); + + options.apply = hot + ? legacyBoolean(overrides.reload, true) + ? "hmr" + : "hmr-only" + : legacyBoolean(overrides.liveReload, true) + ? "reload" + : "nothing"; + } + + if (overrides.connect === undefined) { + if (!legacyBoolean(overrides.autoConnect, true)) { + options.connect = false; + } else { + const retries = Number(overrides.reconnect); + const timeout = Number(overrides.timeout); + + options.connect = { + ...(overrides.reconnect !== undefined && retries >= 0 + ? { retries } + : {}), + ...(overrides.timeout !== undefined && timeout > 0 ? { timeout } : {}), + }; + } + } +} + /** @type {ApplyMode[]} */ const APPLY_MODES = ["hmr", "hmr-only", "reload", "nothing"]; @@ -216,12 +291,8 @@ function applyMode() { * @param {Record} overrides parsed query-string overrides */ function setOverrides(overrides) { - if (overrides.autoConnect) { - // `!== "false"` like every other boolean here, not `=== "true"`: the - // default is already on, so the only thing anyone writes this for is - // turning it off, and `?autoConnect=1` used to do that by accident. - options.autoConnect = overrides.autoConnect !== "false"; - } + // TODO in the next major release remove this, and the six names it reads. + foldLegacyOptions(overrides); if (overrides.transport === "sse" || overrides.transport === "ws") { options.transport = overrides.transport; } diff --git a/src/hot.js b/src/hot.js index d152731c8..36a90c66a 100644 --- a/src/hot.js +++ b/src/hot.js @@ -32,6 +32,9 @@ * @property {string=} token the secret the runtime puts on its connection url, `hot.token` by default * @property {(boolean | Record)=} overlay show build problems and uncaught runtime errors in an overlay * @property {(boolean | "circular" | "linear")=} progress show an indicator while a rebuild is in progress + * @property {boolean=} hot deprecated, removed in the next major release — use `apply` + * @property {boolean=} liveReload deprecated, removed in the next major release — use `apply` + * @property {boolean=} reload deprecated, removed in the next major release — use `apply` * @property {("hmr" | "hmr-only" | "reload" | "nothing")=} apply what a build does to the page — apply the update and reload if it cannot be applied, apply it and stop with a message if it cannot, load the page again on any build that changed something, or leave the page alone * @property {(boolean | { retries?: number, timeout?: number })=} connect whether to connect when the entry runs, and how the connection is held open * @property {string=} urlPrefix prefix of the page-url parameter that overrides `apply` for a single page diff --git a/src/options.check.js b/src/options.check.js index 536c4de72..a927d8599 100644 --- a/src/options.check.js +++ b/src/options.check.js @@ -2,4 +2,4 @@ // DO NOT MODIFY BY HAND. Run `npm run fix:schema-check` to update. /* eslint-disable */ // @ts-nocheck -"use strict";module.exports = validate10;module.exports.default = validate10;const schema11 = {"definitions":{"CorsOrigin":{"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]}},"type":"object","properties":{"cache":{"description":"How responses are cached.","link":"https://github.com/webpack/webpack-dev-middleware#cache","type":"object","additionalProperties":false,"properties":{"etag":{"description":"Enable or disable etag generation.","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","type":"boolean"},"control":{"description":"Enable or disable setting `Cache-Control` response header.","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"immutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","type":"boolean"}}},"mime":{"description":"How a file's media type is decided.","link":"https://github.com/webpack/webpack-dev-middleware#mime","type":"object","additionalProperties":false,"properties":{"types":{"description":"Allows a user to register custom mime types or extension mappings.","type":"object"},"default":{"description":"Allows a user to register a default mime type when we can't determine the content type.","type":"string"}}},"mimeTypes":{"description":"Allows a user to register custom mime types or extension mappings.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypes","type":"object"},"mimeTypeDefault":{"description":"Allows a user to register a default mime type when we can't determine the content type.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypedefault","type":"string"},"writeToDisk":{"description":"Allows to write generated files on disk.","link":"https://github.com/webpack/webpack-dev-middleware#writetodisk","anyOf":[{"type":"boolean"},{"instanceof":"Function"}]},"methods":{"description":"Allows to pass the list of HTTP request methods accepted by the middleware.","link":"https://github.com/webpack/webpack-dev-middleware#methods","type":"array","items":{"type":"string","minLength":1}},"headers":{"anyOf":[{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"key":{"description":"key of header.","type":"string"},"value":{"description":"value of header.","type":"string"}}},"minItems":1},{"type":"object"},{"instanceof":"Function"}],"description":"Allows to pass custom HTTP headers on each request","link":"https://github.com/webpack/webpack-dev-middleware#headers"},"publicPath":{"description":"The `publicPath` specifies the public URL address of the output files when referenced in a browser.","link":"https://github.com/webpack/webpack-dev-middleware#publicpath","anyOf":[{"enum":["auto"]},{"type":"string"},{"instanceof":"Function"}]},"stats":{"description":"Stats options object or preset name.","link":"https://github.com/webpack/webpack-dev-middleware#stats","anyOf":[{"enum":["none","summary","errors-only","errors-warnings","minimal","normal","detailed","verbose"]},{"type":"boolean"},{"type":"object","additionalProperties":true}]},"serverSideRender":{"description":"Instructs the module to enable or disable the server-side rendering mode.","link":"https://github.com/webpack/webpack-dev-middleware#serversiderender","type":"boolean"},"outputFileSystem":{"description":"Set the default file system which will be used by webpack as primary destination of generated files.","link":"https://github.com/webpack/webpack-dev-middleware#outputfilesystem","type":"object"},"index":{"description":"Allows to serve an index of the directory.","link":"https://github.com/webpack/webpack-dev-middleware#index","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"modifyResponseData":{"description":"Allows to set up a callback to change the response data.","link":"https://github.com/webpack/webpack-dev-middleware#modifyresponsedata","instanceof":"Function"},"etag":{"description":"Enable or disable etag generation.","link":"https://github.com/webpack/webpack-dev-middleware#etag","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","link":"https://github.com/webpack/webpack-dev-middleware#lastmodified","type":"boolean"},"cacheControl":{"description":"Enable or disable setting `Cache-Control` response header.","link":"https://github.com/webpack/webpack-dev-middleware#cachecontrol","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"cacheImmutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","link":"https://github.com/webpack/webpack-dev-middleware#cacheimmutable","type":"boolean"},"forwardError":{"description":"Enable or disable forwarding errors to next middleware.","link":"https://github.com/webpack/webpack-dev-middleware#forwarderrors","type":"boolean"},"hot":{"description":"Enable hot module replacement over a Server-Sent Events or WebSocket endpoint.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":false,"properties":{"transport":{"description":"How events reach the clients: `sse`, `ws` (needs the optional `ws` dependency and an HTTP server to answer upgrades on, given as `server` or through the middleware's `attach` method), or a function building a transport of your own.","anyOf":[{"enum":["sse","ws"]},{"instanceof":"Function"}]},"path":{"description":"The path the endpoint is served at. Must start with a slash and carry no query string or fragment.","type":"string","pattern":"^/[^?#]*$"},"heartbeat":{"description":"Heartbeat interval (in milliseconds) used to keep the connection alive.","type":"number","minimum":1},"server":{"description":"HTTP server the `ws` transport answers upgrades on, when it is already built. Otherwise hand it over later with the middleware's `attach` method.","type":"object","additionalProperties":true},"progress":{"description":"Publish compilation progress events to the clients.","type":"boolean"},"token":{"description":"A secret the injected client carries and the endpoint requires, so reaching the stream takes something a page has to have been given rather than a header the browser may not send. `true` mints one per run, a string uses that one — for a client of your own that has to build the url itself — and `false` requires none. Defaults to `false`, because requiring one would refuse a client this middleware did not inject; `true` in the next major release.","link":"https://github.com/webpack/webpack-dev-middleware#hottoken","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"cors":{"description":"Which origins may read the Server-Sent Events endpoint from a page on another one. Local origins only by default. `true` grants every origin, which lets any site the developer has open read the build's errors, including the source frames webpack puts in them.","link":"https://github.com/webpack/webpack-dev-middleware#hotcors","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"},{"type":"object","additionalProperties":false,"properties":{"origin":{"description":"Which origins may read it, as Vite and `expressjs/cors` are configured.","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"}]}}}]},"statsOptions":{"description":"Deprecated, do not use, will be removed in the next major release. Use the `stats` option instead, which decides whether a payload carries errors and warnings.","type":"object","additionalProperties":true},"inject":{"description":"Add the hot client entry and HotModuleReplacementPlugin to the compilation. Turn it off to wire them yourself.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"path":{"description":"Where the runtime connects. Defaults to the resolved `hot.path`; may be an absolute url for an endpoint reached on another origin or through a proxy.","type":"string","minLength":1},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","type":"string"},"token":{"description":"The secret the runtime puts on its connection url. The middleware sets this to whatever 'hot.token' resolved to, so it only needs setting for a client pointed at another endpoint that requires a different one.","type":"string"},"overlay":{"description":"Show build problems and uncaught runtime errors in an overlay.","anyOf":[{"type":"boolean"},{"type":"object"}]},"progress":{"description":"Show an indicator while a rebuild is in progress.","anyOf":[{"type":"boolean"},{"enum":["circular","linear"]}]},"hot":{"description":"Apply a build through Hot Module Replacement.","type":"boolean"},"liveReload":{"description":"Reload the page on a build that changed something, when `hot` is off.","type":"boolean"},"reload":{"description":"Reload the page when an update cannot be applied.","type":"boolean"},"urlPrefix":{"description":"Names the page-url parameters that turn `hot` and `liveReload` off for a single page.","type":"string","minLength":1},"logging":{"description":"How much the runtime logs to the browser console.","enum":["none","error","warn","info","log","verbose"]},"reconnect":{"description":"How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open — a dev server is expected to come back — while a WebSocket gives up after 10. Setting it applies to both.","type":"number","minimum":0},"timeout":{"description":"How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead.","type":"number","exclusiveMinimum":0},"autoConnect":{"description":"Connect as soon as the entry runs.","type":"boolean"},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}}}}]}},"additionalProperties":false};const schema12 = {"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]};const func2 = Object.prototype.hasOwnProperty;const pattern0 = new RegExp("^/[^?#]*$", "u");function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!(func2.call(schema11.properties, key0))){validate10.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.cache !== undefined){let data0 = data.cache;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){const _errs5 = errors;for(const key1 in data0){if(!((((key1 === "etag") || (key1 === "lastModified")) || (key1 === "control")) || (key1 === "immutable"))){validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}];return false;break;}}if(_errs5 === errors){if(data0.etag !== undefined){let data1 = data0.etag;const _errs6 = errors;if(!((data1 === "weak") || (data1 === "strong"))){validate10.errors = [{instancePath:instancePath+"/cache/etag",schemaPath:"#/properties/cache/properties/etag/enum",keyword:"enum",params:{allowedValues: schema11.properties.cache.properties.etag.enum},message:"must be equal to one of the allowed values"}];return false;}var valid1 = _errs6 === errors;}else {var valid1 = true;}if(valid1){if(data0.lastModified !== undefined){const _errs7 = errors;if(typeof data0.lastModified !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/lastModified",schemaPath:"#/properties/cache/properties/lastModified/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs7 === errors;}else {var valid1 = true;}if(valid1){if(data0.control !== undefined){let data3 = data0.control;const _errs9 = errors;const _errs10 = errors;let valid2 = false;const _errs11 = errors;if(typeof data3 !== "boolean"){const err0 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs13 = errors;if(!(typeof data3 == "number")){const err1 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}var _valid0 = _errs13 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs15 = errors;if(errors === _errs15){if(typeof data3 === "string"){if(data3.length < 1){const err2 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}var _valid0 = _errs15 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs17 = errors;if(errors === _errs17){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){const _errs19 = errors;for(const key2 in data3){if(!((key2 === "maxAge") || (key2 === "immutable"))){const err4 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;break;}}if(_errs19 === errors){if(data3.maxAge !== undefined){const _errs20 = errors;if(!(typeof data3.maxAge == "number")){const err5 = {instancePath:instancePath+"/cache/control/maxAge",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/maxAge/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}var valid3 = _errs20 === errors;}else {var valid3 = true;}if(valid3){if(data3.immutable !== undefined){const _errs22 = errors;if(typeof data3.immutable !== "boolean"){const err6 = {instancePath:instancePath+"/cache/control/immutable",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}var valid3 = _errs22 === errors;}else {var valid3 = true;}}}}else {const err7 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}var _valid0 = _errs17 === errors;valid2 = valid2 || _valid0;}}}if(!valid2){const err8 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}var valid1 = _errs9 === errors;}else {var valid1 = true;}if(valid1){if(data0.immutable !== undefined){const _errs24 = errors;if(typeof data0.immutable !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/immutable",schemaPath:"#/properties/cache/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs24 === errors;}else {var valid1 = true;}}}}}}else {validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.mime !== undefined){let data7 = data.mime;const _errs26 = errors;if(errors === _errs26){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){const _errs29 = errors;for(const key3 in data7){if(!((key3 === "types") || (key3 === "default"))){validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"}];return false;break;}}if(_errs29 === errors){if(data7.types !== undefined){let data8 = data7.types;const _errs30 = errors;if(!(data8 && typeof data8 == "object" && !Array.isArray(data8))){validate10.errors = [{instancePath:instancePath+"/mime/types",schemaPath:"#/properties/mime/properties/types/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid4 = _errs30 === errors;}else {var valid4 = true;}if(valid4){if(data7.default !== undefined){const _errs32 = errors;if(typeof data7.default !== "string"){validate10.errors = [{instancePath:instancePath+"/mime/default",schemaPath:"#/properties/mime/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs32 === errors;}else {var valid4 = true;}}}}else {validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypes !== undefined){let data10 = data.mimeTypes;const _errs34 = errors;if(!(data10 && typeof data10 == "object" && !Array.isArray(data10))){validate10.errors = [{instancePath:instancePath+"/mimeTypes",schemaPath:"#/properties/mimeTypes/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid0 = _errs34 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypeDefault !== undefined){const _errs37 = errors;if(typeof data.mimeTypeDefault !== "string"){validate10.errors = [{instancePath:instancePath+"/mimeTypeDefault",schemaPath:"#/properties/mimeTypeDefault/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid0 = _errs37 === errors;}else {var valid0 = true;}if(valid0){if(data.writeToDisk !== undefined){let data12 = data.writeToDisk;const _errs40 = errors;const _errs41 = errors;let valid5 = false;const _errs42 = errors;if(typeof data12 !== "boolean"){const err9 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}var _valid1 = _errs42 === errors;valid5 = valid5 || _valid1;if(!valid5){const _errs44 = errors;if(!(data12 instanceof Function)){const err10 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}var _valid1 = _errs44 === errors;valid5 = valid5 || _valid1;}if(!valid5){const err11 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs41;if(vErrors !== null){if(_errs41){vErrors.length = _errs41;}else {vErrors = null;}}}var valid0 = _errs40 === errors;}else {var valid0 = true;}if(valid0){if(data.methods !== undefined){let data13 = data.methods;const _errs46 = errors;if(errors === _errs46){if(Array.isArray(data13)){var valid6 = true;const len0 = data13.length;for(let i0=0; i0=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/hot/heartbeat",schemaPath:"#/properties/hot/anyOf/1/properties/heartbeat/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}var valid16 = _errs137 === errors;}else {var valid16 = true;}if(valid16){if(data32.server !== undefined){let data36 = data32.server;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/server",schemaPath:"#/properties/hot/anyOf/1/properties/server/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}var valid16 = _errs139 === errors;}else {var valid16 = true;}if(valid16){if(data32.progress !== undefined){const _errs142 = errors;if(typeof data32.progress !== "boolean"){const err52 = {instancePath:instancePath+"/hot/progress",schemaPath:"#/properties/hot/anyOf/1/properties/progress/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}var valid16 = _errs142 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs144 = errors;const _errs145 = errors;let valid18 = false;const _errs146 = errors;if(typeof data38 !== "boolean"){const err53 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid9 = _errs146 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs148 = errors;if(errors === _errs148){if(typeof data38 === "string"){if(data38.length < 1){const err54 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}else {const err55 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}var _valid9 = _errs148 === errors;valid18 = valid18 || _valid9;}if(!valid18){const err56 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}else {errors = _errs145;if(vErrors !== null){if(_errs145){vErrors.length = _errs145;}else {vErrors = null;}}}var valid16 = _errs144 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs151 = errors;const _errs152 = errors;let valid19 = false;const _errs153 = errors;if(typeof data39 !== "boolean"){const err57 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/properties/hot/anyOf/1/properties/cors/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}var _valid10 = _errs153 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs155 = errors;const _errs157 = errors;let valid21 = false;const _errs158 = errors;if(errors === _errs158){if(typeof data39 === "string"){if(data39.length < 1){const err58 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}var _valid11 = _errs158 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs160 = errors;if(!(data39 instanceof RegExp)){const err60 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}var _valid11 = _errs160 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = errors;if(errors === _errs161){if(Array.isArray(data39)){if(data39.length < 1){const err61 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/2/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}else {var valid22 = true;const len2 = data39.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/hot/client/reconnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reconnect/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}var valid30 = _errs228 === errors;}else {var valid30 = true;}if(valid30){if(data45.timeout !== undefined){let data58 = data45.timeout;const _errs230 = errors;if(errors === _errs230){if(typeof data58 == "number"){if(data58 <= 0 || isNaN(data58)){const err107 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}var valid30 = _errs230 === errors;}else {var valid30 = true;}if(valid30){if(data45.autoConnect !== undefined){const _errs232 = errors;if(typeof data45.autoConnect !== "boolean"){const err109 = {instancePath:instancePath+"/hot/client/autoConnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/autoConnect/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}var valid30 = _errs232 === errors;}else {var valid30 = true;}if(valid30){if(data45.dynamicPublicPath !== undefined){const _errs234 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err110 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}var valid30 = _errs234 === errors;}else {var valid30 = true;}}}}}}}}}}}}}}}}}else {const err111 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}var valid16 = _errs197 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err112 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err113 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs125;if(vErrors !== null){if(_errs125){vErrors.length = _errs125;}else {vErrors = null;}}}var valid0 = _errs124 === errors;}else {var valid0 = true;}}}}}}}}}}}}}}}}}}}}}else {validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate10.errors = vErrors;return errors === 0;} +"use strict";module.exports = validate10;module.exports.default = validate10;const schema11 = {"definitions":{"CorsOrigin":{"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]}},"type":"object","properties":{"cache":{"description":"How responses are cached.","link":"https://github.com/webpack/webpack-dev-middleware#cache","type":"object","additionalProperties":false,"properties":{"etag":{"description":"Enable or disable etag generation.","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","type":"boolean"},"control":{"description":"Enable or disable setting `Cache-Control` response header.","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"immutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","type":"boolean"}}},"mime":{"description":"How a file's media type is decided.","link":"https://github.com/webpack/webpack-dev-middleware#mime","type":"object","additionalProperties":false,"properties":{"types":{"description":"Allows a user to register custom mime types or extension mappings.","type":"object"},"default":{"description":"Allows a user to register a default mime type when we can't determine the content type.","type":"string"}}},"mimeTypes":{"description":"Allows a user to register custom mime types or extension mappings.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypes","type":"object"},"mimeTypeDefault":{"description":"Allows a user to register a default mime type when we can't determine the content type.","link":"https://github.com/webpack/webpack-dev-middleware#mimetypedefault","type":"string"},"writeToDisk":{"description":"Allows to write generated files on disk.","link":"https://github.com/webpack/webpack-dev-middleware#writetodisk","anyOf":[{"type":"boolean"},{"instanceof":"Function"}]},"methods":{"description":"Allows to pass the list of HTTP request methods accepted by the middleware.","link":"https://github.com/webpack/webpack-dev-middleware#methods","type":"array","items":{"type":"string","minLength":1}},"headers":{"anyOf":[{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"key":{"description":"key of header.","type":"string"},"value":{"description":"value of header.","type":"string"}}},"minItems":1},{"type":"object"},{"instanceof":"Function"}],"description":"Allows to pass custom HTTP headers on each request","link":"https://github.com/webpack/webpack-dev-middleware#headers"},"publicPath":{"description":"The `publicPath` specifies the public URL address of the output files when referenced in a browser.","link":"https://github.com/webpack/webpack-dev-middleware#publicpath","anyOf":[{"enum":["auto"]},{"type":"string"},{"instanceof":"Function"}]},"stats":{"description":"Stats options object or preset name.","link":"https://github.com/webpack/webpack-dev-middleware#stats","anyOf":[{"enum":["none","summary","errors-only","errors-warnings","minimal","normal","detailed","verbose"]},{"type":"boolean"},{"type":"object","additionalProperties":true}]},"serverSideRender":{"description":"Instructs the module to enable or disable the server-side rendering mode.","link":"https://github.com/webpack/webpack-dev-middleware#serversiderender","type":"boolean"},"outputFileSystem":{"description":"Set the default file system which will be used by webpack as primary destination of generated files.","link":"https://github.com/webpack/webpack-dev-middleware#outputfilesystem","type":"object"},"index":{"description":"Allows to serve an index of the directory.","link":"https://github.com/webpack/webpack-dev-middleware#index","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"modifyResponseData":{"description":"Allows to set up a callback to change the response data.","link":"https://github.com/webpack/webpack-dev-middleware#modifyresponsedata","instanceof":"Function"},"etag":{"description":"Enable or disable etag generation.","link":"https://github.com/webpack/webpack-dev-middleware#etag","enum":["weak","strong"]},"lastModified":{"description":"Enable or disable `Last-Modified` header. Uses the file system's last modified value.","link":"https://github.com/webpack/webpack-dev-middleware#lastmodified","type":"boolean"},"cacheControl":{"description":"Enable or disable setting `Cache-Control` response header.","link":"https://github.com/webpack/webpack-dev-middleware#cachecontrol","anyOf":[{"type":"boolean"},{"type":"number"},{"type":"string","minLength":1},{"type":"object","properties":{"maxAge":{"type":"number"},"immutable":{"type":"boolean"}},"additionalProperties":false}]},"cacheImmutable":{"description":"Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).","link":"https://github.com/webpack/webpack-dev-middleware#cacheimmutable","type":"boolean"},"forwardError":{"description":"Enable or disable forwarding errors to next middleware.","link":"https://github.com/webpack/webpack-dev-middleware#forwarderrors","type":"boolean"},"hot":{"description":"Enable hot module replacement over a Server-Sent Events or WebSocket endpoint.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":false,"properties":{"transport":{"description":"How events reach the clients: `sse`, `ws` (needs the optional `ws` dependency and an HTTP server to answer upgrades on, given as `server` or through the middleware's `attach` method), or a function building a transport of your own.","anyOf":[{"enum":["sse","ws"]},{"instanceof":"Function"}]},"path":{"description":"The path the endpoint is served at. Must start with a slash and carry no query string or fragment.","type":"string","pattern":"^/[^?#]*$"},"heartbeat":{"description":"Heartbeat interval (in milliseconds) used to keep the connection alive.","type":"number","minimum":1},"server":{"description":"HTTP server the `ws` transport answers upgrades on, when it is already built. Otherwise hand it over later with the middleware's `attach` method.","type":"object","additionalProperties":true},"progress":{"description":"Publish compilation progress events to the clients.","type":"boolean"},"token":{"description":"A secret the injected client carries and the endpoint requires, so reaching the stream takes something a page has to have been given rather than a header the browser may not send. `true` mints one per run, a string uses that one — for a client of your own that has to build the url itself — and `false` requires none. Defaults to `false`, because requiring one would refuse a client this middleware did not inject; `true` in the next major release.","link":"https://github.com/webpack/webpack-dev-middleware#hottoken","anyOf":[{"type":"boolean"},{"type":"string","minLength":1}]},"cors":{"description":"Which origins may read the Server-Sent Events endpoint from a page on another one. Local origins only by default. `true` grants every origin, which lets any site the developer has open read the build's errors, including the source frames webpack puts in them.","link":"https://github.com/webpack/webpack-dev-middleware#hotcors","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"},{"type":"object","additionalProperties":false,"properties":{"origin":{"description":"Which origins may read it, as Vite and `expressjs/cors` are configured.","anyOf":[{"type":"boolean"},{"$ref":"#/definitions/CorsOrigin"}]}}}]},"statsOptions":{"description":"Deprecated, do not use, will be removed in the next major release. Use the `stats` option instead, which decides whether a payload carries errors and warnings.","type":"object","additionalProperties":true},"inject":{"description":"Add the hot client entry and HotModuleReplacementPlugin to the compilation. Turn it off to wire them yourself.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"path":{"description":"Where the runtime connects. Defaults to the resolved `hot.path`; may be an absolute url for an endpoint reached on another origin or through a proxy.","type":"string","minLength":1},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","type":"string"},"token":{"description":"The secret the runtime puts on its connection url. The middleware sets this to whatever 'hot.token' resolved to, so it only needs setting for a client pointed at another endpoint that requires a different one.","type":"string"},"overlay":{"description":"Show build problems and uncaught runtime errors in an overlay.","anyOf":[{"type":"boolean"},{"type":"object"}]},"progress":{"description":"Show an indicator while a rebuild is in progress.","anyOf":[{"type":"boolean"},{"enum":["circular","linear"]}]},"apply":{"description":"What a build does to the page: `hmr` applies the update and reloads if it cannot be applied, `hmr-only` applies it and stops with a message if it cannot, `reload` loads the page again on a build that changed something, and `nothing` leaves the page alone until you reload it.","enum":["hmr","hmr-only","reload","nothing"]},"connect":{"description":"Whether to connect when the entry runs, and how the connection is held open. `false` does not connect; an object sets `retries` and `timeout`.","anyOf":[{"type":"boolean"},{"type":"object","additionalProperties":false,"properties":{"retries":{"description":"How many times to reconnect before giving up. Server-Sent Events keep trying for as long as the page is open unless this is set, since a dev server is expected to come back.","type":"number","minimum":0},"timeout":{"description":"How long silence is tolerated before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript.","type":"number","minimum":1}}}]},"urlPrefix":{"description":"Prefix of the page-url parameter that overrides `apply` for a single page.","type":"string","minLength":1},"logging":{"description":"How much the runtime logs to the browser console.","enum":["none","error","warn","info","log","verbose"]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"},"hot":{"description":"Apply a build through Hot Module Replacement. Deprecated, and removed in the next major release: use `apply`.","type":"boolean"},"liveReload":{"description":"Reload the page on a build that changed something, when `hot` is off. Deprecated, and removed in the next major release: use `apply`.","type":"boolean"},"reload":{"description":"Reload the page when an update cannot be applied. Deprecated, and removed in the next major release: use `apply`.","type":"boolean"},"autoConnect":{"description":"Connect as soon as the entry runs. Deprecated, and removed in the next major release: use `connect`.","type":"boolean"},"reconnect":{"description":"How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open — a dev server is expected to come back — while a WebSocket gives up after 10. Setting it applies to both. Deprecated, and removed in the next major release: use `connect`.","type":"number","minimum":0},"timeout":{"description":"How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead. Deprecated, and removed in the next major release: use `connect`.","type":"number","exclusiveMinimum":0}}}}}]}},"additionalProperties":false};const schema12 = {"description":"An origin, written as a browser sends it (scheme, host and port, no trailing slash), several of them, a pattern, or a function asked about each.","anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"},{"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"instanceof":"RegExp"}]},"minItems":1},{"instanceof":"Function"}]};const func2 = Object.prototype.hasOwnProperty;const pattern0 = new RegExp("^/[^?#]*$", "u");function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!(func2.call(schema11.properties, key0))){validate10.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.cache !== undefined){let data0 = data.cache;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){const _errs5 = errors;for(const key1 in data0){if(!((((key1 === "etag") || (key1 === "lastModified")) || (key1 === "control")) || (key1 === "immutable"))){validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}];return false;break;}}if(_errs5 === errors){if(data0.etag !== undefined){let data1 = data0.etag;const _errs6 = errors;if(!((data1 === "weak") || (data1 === "strong"))){validate10.errors = [{instancePath:instancePath+"/cache/etag",schemaPath:"#/properties/cache/properties/etag/enum",keyword:"enum",params:{allowedValues: schema11.properties.cache.properties.etag.enum},message:"must be equal to one of the allowed values"}];return false;}var valid1 = _errs6 === errors;}else {var valid1 = true;}if(valid1){if(data0.lastModified !== undefined){const _errs7 = errors;if(typeof data0.lastModified !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/lastModified",schemaPath:"#/properties/cache/properties/lastModified/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs7 === errors;}else {var valid1 = true;}if(valid1){if(data0.control !== undefined){let data3 = data0.control;const _errs9 = errors;const _errs10 = errors;let valid2 = false;const _errs11 = errors;if(typeof data3 !== "boolean"){const err0 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs11 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs13 = errors;if(!(typeof data3 == "number")){const err1 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}var _valid0 = _errs13 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs15 = errors;if(errors === _errs15){if(typeof data3 === "string"){if(data3.length < 1){const err2 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/2/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}var _valid0 = _errs15 === errors;valid2 = valid2 || _valid0;if(!valid2){const _errs17 = errors;if(errors === _errs17){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){const _errs19 = errors;for(const key2 in data3){if(!((key2 === "maxAge") || (key2 === "immutable"))){const err4 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;break;}}if(_errs19 === errors){if(data3.maxAge !== undefined){const _errs20 = errors;if(!(typeof data3.maxAge == "number")){const err5 = {instancePath:instancePath+"/cache/control/maxAge",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/maxAge/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}var valid3 = _errs20 === errors;}else {var valid3 = true;}if(valid3){if(data3.immutable !== undefined){const _errs22 = errors;if(typeof data3.immutable !== "boolean"){const err6 = {instancePath:instancePath+"/cache/control/immutable",schemaPath:"#/properties/cache/properties/control/anyOf/3/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}var valid3 = _errs22 === errors;}else {var valid3 = true;}}}}else {const err7 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf/3/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}var _valid0 = _errs17 === errors;valid2 = valid2 || _valid0;}}}if(!valid2){const err8 = {instancePath:instancePath+"/cache/control",schemaPath:"#/properties/cache/properties/control/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs10;if(vErrors !== null){if(_errs10){vErrors.length = _errs10;}else {vErrors = null;}}}var valid1 = _errs9 === errors;}else {var valid1 = true;}if(valid1){if(data0.immutable !== undefined){const _errs24 = errors;if(typeof data0.immutable !== "boolean"){validate10.errors = [{instancePath:instancePath+"/cache/immutable",schemaPath:"#/properties/cache/properties/immutable/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid1 = _errs24 === errors;}else {var valid1 = true;}}}}}}else {validate10.errors = [{instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.mime !== undefined){let data7 = data.mime;const _errs26 = errors;if(errors === _errs26){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){const _errs29 = errors;for(const key3 in data7){if(!((key3 === "types") || (key3 === "default"))){validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"}];return false;break;}}if(_errs29 === errors){if(data7.types !== undefined){let data8 = data7.types;const _errs30 = errors;if(!(data8 && typeof data8 == "object" && !Array.isArray(data8))){validate10.errors = [{instancePath:instancePath+"/mime/types",schemaPath:"#/properties/mime/properties/types/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid4 = _errs30 === errors;}else {var valid4 = true;}if(valid4){if(data7.default !== undefined){const _errs32 = errors;if(typeof data7.default !== "string"){validate10.errors = [{instancePath:instancePath+"/mime/default",schemaPath:"#/properties/mime/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs32 === errors;}else {var valid4 = true;}}}}else {validate10.errors = [{instancePath:instancePath+"/mime",schemaPath:"#/properties/mime/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypes !== undefined){let data10 = data.mimeTypes;const _errs34 = errors;if(!(data10 && typeof data10 == "object" && !Array.isArray(data10))){validate10.errors = [{instancePath:instancePath+"/mimeTypes",schemaPath:"#/properties/mimeTypes/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}var valid0 = _errs34 === errors;}else {var valid0 = true;}if(valid0){if(data.mimeTypeDefault !== undefined){const _errs37 = errors;if(typeof data.mimeTypeDefault !== "string"){validate10.errors = [{instancePath:instancePath+"/mimeTypeDefault",schemaPath:"#/properties/mimeTypeDefault/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid0 = _errs37 === errors;}else {var valid0 = true;}if(valid0){if(data.writeToDisk !== undefined){let data12 = data.writeToDisk;const _errs40 = errors;const _errs41 = errors;let valid5 = false;const _errs42 = errors;if(typeof data12 !== "boolean"){const err9 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}var _valid1 = _errs42 === errors;valid5 = valid5 || _valid1;if(!valid5){const _errs44 = errors;if(!(data12 instanceof Function)){const err10 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}var _valid1 = _errs44 === errors;valid5 = valid5 || _valid1;}if(!valid5){const err11 = {instancePath:instancePath+"/writeToDisk",schemaPath:"#/properties/writeToDisk/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs41;if(vErrors !== null){if(_errs41){vErrors.length = _errs41;}else {vErrors = null;}}}var valid0 = _errs40 === errors;}else {var valid0 = true;}if(valid0){if(data.methods !== undefined){let data13 = data.methods;const _errs46 = errors;if(errors === _errs46){if(Array.isArray(data13)){var valid6 = true;const len0 = data13.length;for(let i0=0; i0=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}else {const err50 = {instancePath:instancePath+"/hot/heartbeat",schemaPath:"#/properties/hot/anyOf/1/properties/heartbeat/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}var valid16 = _errs137 === errors;}else {var valid16 = true;}if(valid16){if(data32.server !== undefined){let data36 = data32.server;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/server",schemaPath:"#/properties/hot/anyOf/1/properties/server/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}var valid16 = _errs139 === errors;}else {var valid16 = true;}if(valid16){if(data32.progress !== undefined){const _errs142 = errors;if(typeof data32.progress !== "boolean"){const err52 = {instancePath:instancePath+"/hot/progress",schemaPath:"#/properties/hot/anyOf/1/properties/progress/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}var valid16 = _errs142 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs144 = errors;const _errs145 = errors;let valid18 = false;const _errs146 = errors;if(typeof data38 !== "boolean"){const err53 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}var _valid9 = _errs146 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs148 = errors;if(errors === _errs148){if(typeof data38 === "string"){if(data38.length < 1){const err54 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}else {const err55 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf/1/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}var _valid9 = _errs148 === errors;valid18 = valid18 || _valid9;}if(!valid18){const err56 = {instancePath:instancePath+"/hot/token",schemaPath:"#/properties/hot/anyOf/1/properties/token/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}else {errors = _errs145;if(vErrors !== null){if(_errs145){vErrors.length = _errs145;}else {vErrors = null;}}}var valid16 = _errs144 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs151 = errors;const _errs152 = errors;let valid19 = false;const _errs153 = errors;if(typeof data39 !== "boolean"){const err57 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/properties/hot/anyOf/1/properties/cors/anyOf/0/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}var _valid10 = _errs153 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs155 = errors;const _errs157 = errors;let valid21 = false;const _errs158 = errors;if(errors === _errs158){if(typeof data39 === "string"){if(data39.length < 1){const err58 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}var _valid11 = _errs158 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs160 = errors;if(!(data39 instanceof RegExp)){const err60 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/1/instanceof",keyword:"instanceof",params:{},message:"must pass \"instanceof\" keyword validation"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}var _valid11 = _errs160 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = errors;if(errors === _errs161){if(Array.isArray(data39)){if(data39.length < 1){const err61 = {instancePath:instancePath+"/hot/cors",schemaPath:"#/definitions/CorsOrigin/anyOf/2/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}else {var valid22 = true;const len2 = data39.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}else {const err103 = {instancePath:instancePath+"/hot/client/connect/retries",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/connect/anyOf/1/properties/retries/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}var valid34 = _errs227 === errors;}else {var valid34 = true;}if(valid34){if(data53.timeout !== undefined){let data55 = data53.timeout;const _errs229 = errors;if(errors === _errs229){if(typeof data55 == "number"){if(data55 < 1 || isNaN(data55)){const err104 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/connect/anyOf/1/properties/timeout/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}else {const err105 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/connect/anyOf/1/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}var valid34 = _errs229 === errors;}else {var valid34 = true;}}}}else {const err106 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/connect/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}var _valid18 = _errs224 === errors;valid33 = valid33 || _valid18;}if(!valid33){const err107 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/connect/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}else {errors = _errs221;if(vErrors !== null){if(_errs221){vErrors.length = _errs221;}else {vErrors = null;}}}var valid30 = _errs220 === errors;}else {var valid30 = true;}if(valid30){if(data45.urlPrefix !== undefined){let data56 = data45.urlPrefix;const _errs231 = errors;if(errors === _errs231){if(typeof data56 === "string"){if(data56.length < 1){const err108 = {instancePath:instancePath+"/hot/client/urlPrefix",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/urlPrefix/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}else {const err109 = {instancePath:instancePath+"/hot/client/urlPrefix",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/urlPrefix/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}var valid30 = _errs231 === errors;}else {var valid30 = true;}if(valid30){if(data45.logging !== undefined){let data57 = data45.logging;const _errs233 = errors;if(!((((((data57 === "none") || (data57 === "error")) || (data57 === "warn")) || (data57 === "info")) || (data57 === "log")) || (data57 === "verbose"))){const err110 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/logging/enum",keyword:"enum",params:{allowedValues: schema11.properties.hot.anyOf[1].properties.client.properties.logging.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}var valid30 = _errs233 === errors;}else {var valid30 = true;}if(valid30){if(data45.dynamicPublicPath !== undefined){const _errs234 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err111 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}var valid30 = _errs234 === errors;}else {var valid30 = true;}if(valid30){if(data45.hot !== undefined){const _errs236 = errors;if(typeof data45.hot !== "boolean"){const err112 = {instancePath:instancePath+"/hot/client/hot",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/hot/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}var valid30 = _errs236 === errors;}else {var valid30 = true;}if(valid30){if(data45.liveReload !== undefined){const _errs238 = errors;if(typeof data45.liveReload !== "boolean"){const err113 = {instancePath:instancePath+"/hot/client/liveReload",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/liveReload/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}var valid30 = _errs238 === errors;}else {var valid30 = true;}if(valid30){if(data45.reload !== undefined){const _errs240 = errors;if(typeof data45.reload !== "boolean"){const err114 = {instancePath:instancePath+"/hot/client/reload",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reload/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}var valid30 = _errs240 === errors;}else {var valid30 = true;}if(valid30){if(data45.autoConnect !== undefined){const _errs242 = errors;if(typeof data45.autoConnect !== "boolean"){const err115 = {instancePath:instancePath+"/hot/client/autoConnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/autoConnect/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}var valid30 = _errs242 === errors;}else {var valid30 = true;}if(valid30){if(data45.reconnect !== undefined){let data63 = data45.reconnect;const _errs244 = errors;if(errors === _errs244){if(typeof data63 == "number"){if(data63 < 0 || isNaN(data63)){const err116 = {instancePath:instancePath+"/hot/client/reconnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reconnect/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}else {const err117 = {instancePath:instancePath+"/hot/client/reconnect",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/reconnect/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}var valid30 = _errs244 === errors;}else {var valid30 = true;}if(valid30){if(data45.timeout !== undefined){let data64 = data45.timeout;const _errs246 = errors;if(errors === _errs246){if(typeof data64 == "number"){if(data64 <= 0 || isNaN(data64)){const err118 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}else {const err119 = {instancePath:instancePath+"/hot/client/timeout",schemaPath:"#/properties/hot/anyOf/1/properties/client/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}var valid30 = _errs246 === errors;}else {var valid30 = true;}}}}}}}}}}}}}}}}}}}else {const err120 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}var valid16 = _errs197 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err121 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err122 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;validate10.errors = vErrors;return false;}else {errors = _errs125;if(vErrors !== null){if(_errs125){vErrors.length = _errs125;}else {vErrors = null;}}}var valid0 = _errs124 === errors;}else {var valid0 = true;}}}}}}}}}}}}}}}}}}}}}else {validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate10.errors = vErrors;return errors === 0;} \ No newline at end of file diff --git a/src/options.json b/src/options.json index 8d2c138e6..84a9a9f7f 100644 --- a/src/options.json +++ b/src/options.json @@ -448,23 +448,35 @@ "description": "How much the runtime logs to the browser console.", "enum": ["none", "error", "warn", "info", "log", "verbose"] }, + "dynamicPublicPath": { + "description": "Prefix the endpoint path with the bundle's public path at runtime.", + "type": "boolean" + }, + "hot": { + "description": "Apply a build through Hot Module Replacement. Deprecated, and removed in the next major release: use `apply`.", + "type": "boolean" + }, + "liveReload": { + "description": "Reload the page on a build that changed something, when `hot` is off. Deprecated, and removed in the next major release: use `apply`.", + "type": "boolean" + }, + "reload": { + "description": "Reload the page when an update cannot be applied. Deprecated, and removed in the next major release: use `apply`.", + "type": "boolean" + }, + "autoConnect": { + "description": "Connect as soon as the entry runs. Deprecated, and removed in the next major release: use `connect`.", + "type": "boolean" + }, "reconnect": { - "description": "How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open \u2014 a dev server is expected to come back \u2014 while a WebSocket gives up after 10. Setting it applies to both.", + "description": "How many times to reconnect before giving up. Unset, Server-Sent Events keep trying for as long as the page is open \u2014 a dev server is expected to come back \u2014 while a WebSocket gives up after 10. Setting it applies to both. Deprecated, and removed in the next major release: use `connect`.", "type": "number", "minimum": 0 }, "timeout": { - "description": "How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript \u2014 a half-open socket there is terminated by the server instead.", + "description": "How long the runtime tolerates silence before reconnecting, in milliseconds. Server-Sent Events only: their heartbeat arrives as data the client can see, whereas a WebSocket's is a protocol ping the browser answers without telling JavaScript \u2014 a half-open socket there is terminated by the server instead. Deprecated, and removed in the next major release: use `connect`.", "type": "number", "exclusiveMinimum": 0 - }, - "autoConnect": { - "description": "Connect as soon as the entry runs.", - "type": "boolean" - }, - "dynamicPublicPath": { - "description": "Prefix the endpoint path with the bundle's public path at runtime.", - "type": "boolean" } } } diff --git a/src/utils.js b/src/utils.js index db61d8570..4b70051ce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1457,6 +1457,17 @@ function filterSource(option, filter) { ); } +// The six browser options `apply` and `connect` replaced. Accepted and folded +// in by the client until the next major release. +const LEGACY_CLIENT_OPTIONS = [ + "hot", + "liveReload", + "reload", + "autoConnect", + "reconnect", + "timeout", +]; + /** * The browser options, as the client reads them from its resource query. * @param {EXPECTED_ANY} client the `hot.client` option @@ -1521,6 +1532,17 @@ function injectHotClient(compilers, options, logger) { return; } + // TODO in the next major release remove this warning and `LEGACY_CLIENT_OPTIONS` + const deprecated = LEGACY_CLIENT_OPTIONS.filter((name) => + Object.hasOwn(options.client || {}, name), + ); + + if (deprecated.length > 0) { + logger.warn( + `${deprecated.map((name) => `'hot.client.${name}'`).join(", ")} ${deprecated.length === 1 ? "is" : "are"} deprecated and will be removed in the next major release. 'hot.client.apply' replaces 'hot', 'liveReload' and 'reload'; 'hot.client.connect' replaces 'autoConnect', 'reconnect' and 'timeout'. Until then these still apply, and the option that replaced them wins when both are set.`, + ); + } + let warned = false; // A token only reaches the browser on the entry added below, so a required // one with nothing added would refuse every client. diff --git a/test/e2e/live-reload.test.js b/test/e2e/live-reload.test.js index da66f178e..6a9cd6da5 100644 --- a/test/e2e/live-reload.test.js +++ b/test/e2e/live-reload.test.js @@ -273,3 +273,108 @@ describe("live reload (browser)", () => { ); }); }); + +// The six names `apply` and `connect` replaced still work, and are folded into +// them, until the next major release. Driven through the real client in a +// browser, since the folding happens where the query is read. +describe("the deprecated browser options (browser)", () => { + let app; + let browser; + let page; + + afterEach(async () => { + ({ browser, app } = await closeE2e(browser, app)); + }); + + it("takes `hot=false` as asking for a reload", async () => { + // `hot: false` left `liveReload` on, so a build reached the page by + // loading it again — `apply: "reload"` by another name. + app = await createHotApp({ query: "?hot=false", code: acceptedApp("v1") }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + await plantReloadMarker(page); + + app.edit(acceptedApp("v2")); + await waitForAppText(page, "v2"); + + // Gone, so the page was loaded again rather than updated in place. + expect(await readReloadMarker(page)).toBeUndefined(); + expect(console_.messages.join("\n")).toContain("deprecated"); + }); + + it("takes `hot=false&liveReload=false` as asking for nothing", async () => { + app = await createHotApp({ + query: "?hot=false&liveReload=false", + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + await plantReloadMarker(page); + + app.edit(acceptedApp("v2")); + await settle(); + + // Neither applied nor reloaded. + expect(await appText(page)).toBe("v1"); + expect(await readReloadMarker(page)).toBe(true); + }); + + it("lets `apply` win when both spellings are given", async () => { + // The other way round, a migration that sets the new name and leaves the + // old one behind would silently not apply. + app = await createHotApp({ + query: "?hot=false&apply=hmr", + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + await plantReloadMarker(page); + + app.edit(acceptedApp("v2")); + await waitForAppText(page, "v2"); + + // `apply: "hmr"` updated in place, so the marker survived. + expect(await readReloadMarker(page)).toBe(true); + }); + + it("takes `autoConnect=false` as not connecting", async () => { + app = await createHotApp({ + query: "?autoConnect=false", + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + + expect(console_.messages.join("\n")).not.toContain("connected"); + expect(console_.messages.join("\n")).toContain("deprecated"); + }); + + it("says which deprecated names it found", async () => { + app = await createHotApp({ + query: "?reconnect=3&timeout=5000", + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + await console_.waitFor("deprecated"); + + const said = console_.messages.join("\n"); + + expect(said).toContain("reconnect"); + expect(said).toContain("timeout"); + expect(said).toContain("'apply' and 'connect'"); + }); +}); diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 4abc5f92f..94d936682 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -914,15 +914,25 @@ describe("node and the query take the same names", () => { "utf8", ); + // The deprecated spellings are read by name from a list rather than written + // out one by one, so they have to be collected from the list. + const [, legacyList] = /** @type {RegExpMatchArray} */ ( + clientSource.match(/const LEGACY_OPTIONS = \[([\s\S]*?)\];/) + ); + const legacy = [...legacyList.matchAll(/"([A-Za-z]+)"/g)].map( + (found) => found[1], + ); + /** @type {string[]} every name the client acts on from its query */ const readByClient = [ - ...new Set( - [ + ...new Set([ + ...[ .../** @type {RegExpMatchArray} */ ( clientSource.match(/function setOverrides\([\s\S]*?\n\}/) )[0].matchAll(/overrides(?:\.([A-Za-z]+)|\["([^"]+)"\])/g), ].map((found) => found[1] || found[2]), - ), + ...legacy, + ]), ]; /** @type {string[]} every name `hot.client` accepts */ @@ -934,6 +944,9 @@ describe("node and the query take the same names", () => { // The extraction above is regex over source; if it ever stops matching it // would compare two empty lists and pass while saying nothing. expect(readByClient.length).toBeGreaterThan(10); + // If the list extraction ever stops matching, the deprecated names would + // silently drop out of the comparison. + expect(legacy).toHaveLength(6); }); it("is one set of names, with nothing on one side only", () => { diff --git a/types/hot.d.ts b/types/hot.d.ts index 8c072a50c..9e6d5d523 100644 --- a/types/hot.d.ts +++ b/types/hot.d.ts @@ -202,6 +202,18 @@ type HotClientOptions = { * show an indicator while a rebuild is in progress */ progress?: (boolean | "circular" | "linear") | undefined; + /** + * deprecated, removed in the next major release — use `apply` + */ + hot?: boolean | undefined; + /** + * deprecated, removed in the next major release — use `apply` + */ + liveReload?: boolean | undefined; + /** + * deprecated, removed in the next major release — use `apply` + */ + reload?: boolean | undefined; /** * what a build does to the page — apply the update and reload if it cannot be applied, apply it and stop with a message if it cannot, load the page again on any build that changed something, or leave the page alone */ From fc5195e76033228288685cfd9bf53639826bf8ac Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:57:18 +0000 Subject: [PATCH 4/7] docs(hot): document `apply` and `connect` in the client options table Both are listed as the replacement the six deprecated rows point to, but the table had no row for either, so following those links left you without their values, default or object shape. The `urlPrefix` row still described the parameter as turning `hot` and `liveReload` off, which is the shape it had before `apply` replaced them. Rebasing onto main resolved every conflicting hunk to the legacy side, which also dropped content that happened to share those hunks: these rows, the `urlPrefix` wording, and three tests covering the shapes `connect` takes. Restored, and the table now reads live options first with the six deprecated names together at the end. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- README.md | 44 ++++++++++++++++-------------- test/client-socket-options.test.js | 25 +++++++++++++++++ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 04eaccab0..0a24c7ab2 100644 --- a/README.md +++ b/README.md @@ -792,7 +792,7 @@ the entry path is the only source — and it works either way: ```js entry: [ - "webpack-dev-middleware/client?reload=false&overlay=false", + "webpack-dev-middleware/client?apply=hmr-only&overlay=false", "./src/app.js", ]; ``` @@ -807,27 +807,31 @@ as well. ### Client options Each of these is set either on the middleware as `hot.client.` or on the -entry's query as `=`, with the same effect. Each has one name and -no aliases, in both places and in the page-url parameters below. `transport`, `path` +entry's query as `=`, with the same effect, in both places and in +the page-url parameters below. The last six are the names `apply` and `connect` +replaced; they still work, folded into those two, and go away in the next major +release. `transport`, `path` and `name` default to what the middleware resolved rather than to the value in the table, which is what they are when nothing else is serving them. -| Name | Type | Default | Description | -| :-----------------: | :---------------: | :------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `path`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | -| `path` | `string` | `/__webpack_hmr` | Where the runtime connects. Defaults to the server [`hot.path`](#hotpath); set it to an absolute url (`wss://dev.example.com/__webpack_hmr`) for an endpoint on another origin or behind a proxy. | -| `timeout` | `number` | `20000` | How long silence is tolerated before reconnecting, in milliseconds, and the interval between reconnections. `"sse"` only: its heartbeat arrives as data the client can see, while a `"ws"` heartbeat is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | -| `reconnect` | `number` | `Infinity` / `10` | How many times to reconnect before giving up. Unset, `"sse"` keeps trying for as long as the page is open — a dev server is expected to come back — while `"ws"` gives up after `10`. Setting it applies to both. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | -| `overlay` | `boolean\|Object` | `true` | In-page overlay for problems: a boolean, or a JSON object — see [overlay options](#client-overlay-options). Same value shape as webpack-dev-server's [`client.overlay`](https://webpack.js.org/configuration/dev-server/#overlay), plus a few webpack-dev-middleware extensions. | -| `hot` | `boolean` | `true` | Apply a build through Hot Module Replacement. Set to `false` for a project without `HotModuleReplacementPlugin`, and the page is reloaded instead — see `liveReload`. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | -| `liveReload` | `boolean` | `true` | Reload the page on a build that changed something, when `hot` is off. A build that changed nothing is left alone. Set both this and `hot` to `false` and a build reaches the page only when you reload it yourself. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | -| `reload` | `boolean` | `true` | Fall back to a full page reload when an update cannot be applied through HMR (e.g. recovering from a broken build). Enabled by default, unlike webpack-hot-middleware; set to `false` to keep HMR-only. Unrelated to `liveReload`: this one is about an update that was tried. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | -| `urlPrefix` | `string` | `"webpack-dev-middleware"` | Names the page-url parameters that turn `hot` and `liveReload` off for a single page — see [opting one page out](#opting-one-page-out). Change it if you are building a server of your own and want parameters named after it. | -| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | -| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | -| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | -| `progress` | `boolean` | `true` | Show a small badge in the page while a rebuild is in progress (with the compilation percentage when the server enables `hot.progress`). Set to `false` to disable. | -| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. The leading slash of `path` is stripped and no other normalization is applied, so the public path should end with `/`. | +| Name | Type | Default | Description | +| :-----------------: | :--------------------------------------: | :------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `path`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | +| `path` | `string` | `/__webpack_hmr` | Where the runtime connects. Defaults to the server [`hot.path`](#hotpath); set it to an absolute url (`wss://dev.example.com/__webpack_hmr`) for an endpoint on another origin or behind a proxy. | +| `apply` | `"hmr"\|"hmr-only"\|"reload"\|"nothing"` | `"hmr"` | What a build does to the page. `"hmr"` applies the update and loads the page again if it cannot be applied; `"hmr-only"` applies it and stops with a message if it cannot; `"reload"` skips HMR and loads the page again on any build that changed something; `"nothing"` leaves the page alone until you reload it. One option rather than three booleans, since only four of their eight combinations differed. | +| `connect` | `boolean\|{ retries, timeout }` | `true` | Whether to connect when the entry runs, and how the connection is held open. `false` does not connect — call `setOptionsAndConnect()` yourself. `retries` is how many times to reconnect before giving up; unset, `"sse"` keeps trying for as long as the page is open while `"ws"` gives up after `10`. `timeout` is how long silence is tolerated before reconnecting, in milliseconds, and the interval between reconnections — `"sse"` only, since a `"ws"` heartbeat is a protocol ping the browser answers without telling JavaScript. | +| `overlay` | `boolean\|Object` | `true` | In-page overlay for problems: a boolean, or a JSON object — see [overlay options](#client-overlay-options). Same value shape as webpack-dev-server's [`client.overlay`](https://webpack.js.org/configuration/dev-server/#overlay), plus a few webpack-dev-middleware extensions. | +| `urlPrefix` | `string` | `"webpack-dev-middleware"` | Names the page-url parameter (`-apply`) that overrides [`apply`](#client-options) for a single page — see [opting one page out](#opting-one-page-out). Change it if you are building a server of your own and want parameters named after it. | +| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | +| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | +| `progress` | `boolean` | `true` | Show a small badge in the page while a rebuild is in progress (with the compilation percentage when the server enables `hot.progress`). Set to `false` to disable. | +| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. The leading slash of `path` is stripped and no other normalization is applied, so the public path should end with `/`. | +| `hot` | `boolean` | `true` | Apply a build through Hot Module Replacement. Set to `false` for a project without `HotModuleReplacementPlugin`, and the page is reloaded instead — see `liveReload`. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `liveReload` | `boolean` | `true` | Reload the page on a build that changed something, when `hot` is off. A build that changed nothing is left alone. Set both this and `hot` to `false` and a build reaches the page only when you reload it yourself. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `reload` | `boolean` | `true` | Fall back to a full page reload when an update cannot be applied through HMR (e.g. recovering from a broken build). Enabled by default, unlike webpack-hot-middleware; set to `false` to keep HMR-only. Unrelated to `liveReload`: this one is about an update that was tried. **Deprecated**, removed in the next major release — use [`apply`](#client-options). | +| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | +| `reconnect` | `number` | `Infinity` / `10` | How many times to reconnect before giving up. Unset, `"sse"` keeps trying for as long as the page is open — a dev server is expected to come back — while `"ws"` gives up after `10`. Setting it applies to both. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | +| `timeout` | `number` | `20000` | How long silence is tolerated before reconnecting, in milliseconds, and the interval between reconnections. `"sse"` only: its heartbeat arrives as data the client can see, while a `"ws"` heartbeat is a protocol ping the browser answers without telling JavaScript — a half-open socket there is terminated by the server instead. **Deprecated**, removed in the next major release — use [`connect`](#client-options). | #### A client of your own @@ -926,7 +930,7 @@ hotClient.useCustomOverlay({ }, }); -// Connect manually when `autoConnect=false`. Accepts the same option keys as +// Connect manually when `connect=false`. Accepts the same option keys as // the query-string API above. hotClient.setOptionsAndConnect({ path: "/__hmr" }); diff --git a/test/client-socket-options.test.js b/test/client-socket-options.test.js index 76a470f2e..4486c4366 100644 --- a/test/client-socket-options.test.js +++ b/test/client-socket-options.test.js @@ -74,6 +74,31 @@ describe("what each transport makes of reconnect and timeout", () => { expect(createSocketDefaultRetries()).toBe(10); }); + describe("the shapes `connect` takes", () => { + it("uses the defaults when it is `true`", () => { + expect(socketOptions({ connect: true })).toStrictEqual({ + retries: Infinity, + retryDelay: expect.any(Function), + clientOptions: { timeout: 20_000 }, + }); + }); + + it("uses them when it is `false` as well", () => { + // `false` says not to connect on load, which the entry decides; if + // something connects later it should still hold the line the same way. + expect(socketOptions({ connect: false }).retries).toBe(Infinity); + }); + + it("fills in the half that was left out", () => { + const { retryDelay, retries } = socketOptions({ + connect: { retries: 2 }, + }); + + expect(retries).toBe(2); + expect(/** @type {() => number} */ (retryDelay)()).toBe(20_000); + }); + }); + describe("a WebSocket", () => { it("takes the number of attempts as given", () => { expect( From 1f37f40d81a7174576a6997bdce31d7e96bcf077 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:58:38 +0000 Subject: [PATCH 5/7] docs(changeset): name the current options in the earlier entries All of these land in one release, so the changelog is read by someone who only ever sees the final names. Three entries described options and page-url parameters as they were partway through: the retry count and silence timeout under their replaced names, a `-liveReload=false` parameter that no longer exists under any name, and a claim that every option has exactly one spelling, which the six kept for a release contradict. The replaced names are noted where they were the subject, since they still work. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/client-live-reload.md | 4 ++-- .changeset/client-options-both-ways.md | 8 ++++---- .changeset/reconnect-and-timeout-per-transport.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.changeset/client-live-reload.md b/.changeset/client-live-reload.md index 37fcf41ee..c6e127ae7 100644 --- a/.changeset/client-live-reload.md +++ b/.changeset/client-live-reload.md @@ -14,8 +14,8 @@ Added a `reload` action, for a change no compilation knows about: publish used to have you write by hand. Added `urlPrefix` (default `"webpack-dev-middleware"`), which names the page-url -parameters that turn `hot` and `liveReload` off for a single page — -`?webpack-dev-middleware-liveReload=false`. +parameter that overrides what a build does to a single page — +`?webpack-dev-middleware-apply=nothing`. `live-reload` used to be accepted as another spelling of `reload`. The two are different — `reload` is the fallback for an update that was tried and could not diff --git a/.changeset/client-options-both-ways.md b/.changeset/client-options-both-ways.md index f67368240..eb6ff7b0d 100644 --- a/.changeset/client-options-both-ways.md +++ b/.changeset/client-options-both-ways.md @@ -36,8 +36,8 @@ A `hot.transport` of your own now gets a client too, when `hot.client.transport` says which of the two built-in protocols yours carries. Without it the client is still yours to add, as before. -Every option has one name and no aliases, in `hot.client`, in the entry query -and in the page-url parameters alike. The two second spellings the query had +Every option is spelled one way in `hot.client`, in the entry query and in the +page-url parameters alike, apart from the six names kept working for a release +after `apply` and `connect` replaced them. The two second spellings the query had picked up from webpack-dev-server — `webSocketURL` for `path` and `live-reload` -for `liveReload` — are gone, and so is the `-live-reload` page parameter, which -is now `-liveReload`. Neither alias was ever released. +for `liveReload` — are gone. Neither alias was ever released. diff --git a/.changeset/reconnect-and-timeout-per-transport.md b/.changeset/reconnect-and-timeout-per-transport.md index bd0005235..420a32be3 100644 --- a/.changeset/reconnect-and-timeout-per-transport.md +++ b/.changeset/reconnect-and-timeout-per-transport.md @@ -2,8 +2,8 @@ "webpack-dev-middleware": patch --- -`hot.client.reconnect` now applies to Server-Sent Events as well. It was overridden to `Infinity` there, so asking for a bounded number of attempts over the default transport did nothing. Unset still means "keep trying for as long as the page is open", since a dev server is expected to come back and a tab left open across a restart has to find it again. +`hot.client.connect.retries` (`hot.client.reconnect` before it was replaced, still accepted) now applies to Server-Sent Events as well. It was overridden to `Infinity` there, so asking for a bounded number of attempts over the default transport did nothing. Unset still means "keep trying for as long as the page is open", since a dev server is expected to come back and a tab left open across a restart has to find it again. -`hot.client.timeout` is documented as Server-Sent Events only, which is what it always was: that transport sends its heartbeat as data the client can see, while a WebSocket sends a protocol ping the browser answers without telling JavaScript — a silence watchdog there would fire on a healthy idle connection, and the half-open case it would catch is handled by the server, which terminates a socket that stops answering. It is no longer handed to a WebSocket client whose constructor takes no options. +`hot.client.connect.timeout` (`hot.client.timeout` before it was replaced, still accepted) is documented as Server-Sent Events only, which is what it always was: that transport sends its heartbeat as data the client can see, while a WebSocket sends a protocol ping the browser answers without telling JavaScript — a silence watchdog there would fire on a healthy idle connection, and the half-open case it would catch is handled by the server, which terminates a socket that stops answering. It is no longer handed to a WebSocket client whose constructor takes no options. -Both decisions now live in one place, `client-src/utils/socket-options.js`, with tests for each transport. The documented default for `reconnect` was wrong for the default transport while it was being ignored there: unset, Server-Sent Events keep trying for as long as the page is open and a WebSocket gives up after 10. +Both decisions now live in one place, `client-src/utils/socket-options.js`, with tests for each transport. The documented default for the retry count was wrong for the default transport while it was being ignored there: unset, Server-Sent Events keep trying for as long as the page is open and a WebSocket gives up after 10. From e92977b709f5f7af5bb800f19fab4e3a424366fa Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:05:27 +0000 Subject: [PATCH 6/7] fix(hot): send an object-valued browser option as json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `clientQuery` special-cased `overlay` and ran `String(value)` over everything else, which was true of every other option until `connect` took an object too. `{ retries: 3, timeout: 5000 }` became the text `"[object Object]"`; the client parses `connect` as JSON, the parse threw, and the fallback read the text as a boolean — so both fields were dropped in silence and the connection ran on the defaults. The rule is now the value's shape rather than the option's name: an object goes over as JSON, `overlay` keeping the encoding its filter functions need. The e2e tests set `connect` through a hand-written entry query, which skips this path entirely, so the new one sets it on the middleware and watches for the shortened watchdog interval actually arriving — it fails on `String(value)`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/connect-object-from-node.md | 5 ++++ src/utils.js | 33 +++++++++++++------------- test/e2e/client.test.js | 30 +++++++++++++++++++++++ test/inject-client.test.js | 26 ++++++++++++++++++++ 4 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 .changeset/connect-object-from-node.md diff --git a/.changeset/connect-object-from-node.md b/.changeset/connect-object-from-node.md new file mode 100644 index 000000000..2bb2ddb64 --- /dev/null +++ b/.changeset/connect-object-from-node.md @@ -0,0 +1,5 @@ +--- +"webpack-dev-middleware": patch +--- + +Fixed `hot.client.connect` set as an object on the middleware being dropped. The browser options travel to the runtime as its entry's query, where every value but `overlay` went through `String(value)` — so `{ retries: 3, timeout: 5000 }` arrived as the text `"[object Object]"`, which the client failed to parse and then read as the boolean `true`. Both fields were lost without a warning, and the connection used the defaults. Any value that is an object is now serialized as JSON, which is the shape the client parses it out of, so an option that grows an object form later needs no change here. diff --git a/src/utils.js b/src/utils.js index 4b70051ce..842b1dbfa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1486,27 +1486,28 @@ function clientQuery(client) { continue; } - if (key !== "overlay") { - query[key] = String(value); - continue; - } + const isObject = typeof value === "object" && value !== null; - if (typeof value !== "object" || value === null) { - query.overlay = String(value); - continue; - } + if (key === "overlay" && isObject) { + /** @type {Record} */ + const overlay = {}; - /** @type {Record} */ - const overlay = {}; + for (const [option, setting] of Object.entries(value)) { + overlay[option] = + OVERLAY_FILTERS.includes(option) && typeof setting === "function" + ? encodeURIComponent(filterSource(option, setting)) + : setting; + } - for (const [option, setting] of Object.entries(value)) { - overlay[option] = - OVERLAY_FILTERS.includes(option) && typeof setting === "function" - ? encodeURIComponent(filterSource(option, setting)) - : setting; + query.overlay = JSON.stringify(overlay); + continue; } - query.overlay = JSON.stringify(overlay); + // An option that may be an object goes over as JSON, which is the shape + // the client parses it back out of. `String(value)` would hand it + // `"[object Object]"`, which parses as nothing and is then read as a + // boolean, so the object's contents would be dropped in silence. + query[key] = isObject ? JSON.stringify(value) : String(value); } return query; diff --git a/test/e2e/client.test.js b/test/e2e/client.test.js index 73c675826..b512c13aa 100644 --- a/test/e2e/client.test.js +++ b/test/e2e/client.test.js @@ -229,6 +229,36 @@ describe("hot client (browser)", () => { ).toBe("v2"); }); + // The same watchdog, with the object set on the middleware instead of + // written into the query by hand. Everything between the two is new ground: + // an object option is serialized for the entry query, and `String(value)` + // there would hand the client `"[object Object]"` — which fails to parse, + // is read as the boolean `true`, and so connects with the default timeout + // rather than this one. The watchdog never firing is how that looks. + it("takes a connect object set on the middleware", async () => { + app = await createHotApp({ + bare: true, + hot: { heartbeat: 3600000, client: { connect: { timeout: 1000 } } }, + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(app.url); + await waitForAppText(page, "v1"); + + // Three connects inside the test's own timeout only happens on a 1000ms + // watchdog; on the 20000ms default this waits until the suite gives up. + await console_.waitForCount("connected", 3); + + app.edit(acceptedApp("v2")); + await waitForAppText(page, "v2"); + + expect( + await page.evaluate(() => document.getElementById("app").textContent), + ).toBe("v2"); + }); + it("reconnects manually with setOptionsAndConnect() after disconnect()", async () => { app = await createHotApp({ code: ` diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 94d936682..1d9eeebe3 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -579,6 +579,32 @@ describe("the browser options as a query", () => { expect(rebuilt({ text: "boom" })).toBe(true); expect(rebuilt({ text: "" })).toBe(false); }); + + // `connect` is the second option that may be an object, and its contents + // only reach the client if they travel as json: `String(value)` would make + // it `"[object Object]"`, which the client fails to parse and then reads as + // the boolean `true`, dropping `retries` and `timeout` without a word. + it("sends a connect object as json", () => { + expect( + clientQuery({ connect: { retries: 3, timeout: 5000 } }), + ).toStrictEqual({ connect: '{"retries":3,"timeout":5000}' }); + }); + + it("keeps a boolean connect a boolean", () => { + expect(clientQuery({ connect: false })).toStrictEqual({ connect: "false" }); + }); + + // The rule is the value's shape rather than the option's name, so an option + // that grows an object form later travels without a change here. + it("sends any object-valued option as json", () => { + const query = clientQuery({ connect: { retries: 1 }, logging: "warn" }); + + for (const [key, value] of Object.entries(query)) { + expect(value).not.toContain("[object Object]"); + expect(typeof value).toBe("string"); + expect(key).toBeTruthy(); + } + }); }); describe("what injectHotClient leaves alone", () => { From 86cb4f3b881253ee38d654494137c4660b79f2d7 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:03:09 +0000 Subject: [PATCH 7/7] docs(hot): say that only `apply` can be set from a page's url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client options section read as though every option could be set in three places. Two is right: the middleware and the entry query, with the same effect and the same spelling. A page's own url reaches exactly one option, `apply`, so `?webpack-dev-middleware-connect=false` does not stop a page connecting — it is not read at all. The `urlPrefix` row and the section it links to were already singular; this is the paragraph above them, and the same sentence in an earlier changeset. Pinned with a test rather than left to the prose, since being wrong about `connect` in particular would be a page that quietly stops receiving builds. It fails if the parameter is swapped for `-apply=nothing`, so it is asserting the boundary and not just that the page works. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/client-options-both-ways.md | 8 +++++--- README.md | 15 +++++++++------ test/e2e/live-reload.test.js | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.changeset/client-options-both-ways.md b/.changeset/client-options-both-ways.md index eb6ff7b0d..c39bea0ab 100644 --- a/.changeset/client-options-both-ways.md +++ b/.changeset/client-options-both-ways.md @@ -36,8 +36,10 @@ A `hot.transport` of your own now gets a client too, when `hot.client.transport` says which of the two built-in protocols yours carries. Without it the client is still yours to add, as before. -Every option is spelled one way in `hot.client`, in the entry query and in the -page-url parameters alike, apart from the six names kept working for a release -after `apply` and `connect` replaced them. The two second spellings the query had +Every option is spelled one way in `hot.client` and in the entry query alike, +apart from the six names kept working for a release after `apply` and `connect` +replaced them. One option, `apply`, can also be set for a single page through a +url parameter (`?webpack-dev-middleware-apply=nothing`); the rest are the +middleware's and the entry's to set. The two second spellings the query had picked up from webpack-dev-server — `webSocketURL` for `path` and `live-reload` for `liveReload` — are gone. Neither alias was ever released. diff --git a/README.md b/README.md index 0a24c7ab2..d190b8a56 100644 --- a/README.md +++ b/README.md @@ -807,12 +807,15 @@ as well. ### Client options Each of these is set either on the middleware as `hot.client.` or on the -entry's query as `=`, with the same effect, in both places and in -the page-url parameters below. The last six are the names `apply` and `connect` -replaced; they still work, folded into those two, and go away in the next major -release. `transport`, `path` -and `name` default to what the middleware resolved rather than to the value in -the table, which is what they are when nothing else is serving them. +entry's query as `=`, with the same effect and the same spelling in +both. The last six are the names `apply` and `connect` replaced; they still +work, folded into those two, and go away in the next major release. +`transport`, `path` and `name` default to what the middleware resolved rather +than to the value in the table, which is what they are when nothing else is +serving them. + +One of them, `apply`, can also be set per page through a url parameter, which +the rest cannot — see [opting one page out](#opting-one-page-out). | Name | Type | Default | Description | | :-----------------: | :--------------------------------------: | :------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/test/e2e/live-reload.test.js b/test/e2e/live-reload.test.js index 6a9cd6da5..64b3c0db6 100644 --- a/test/e2e/live-reload.test.js +++ b/test/e2e/live-reload.test.js @@ -163,6 +163,31 @@ describe("live reload (browser)", () => { expect(await readReloadMarker(page)).toBe(true); }); + // `apply` is the only option a page's own url can set, which the README says + // and this holds it to: a parameter naming any other one is not read, so a + // page cannot turn off the connection, point the client somewhere else or + // change the overlay for itself. Asserted on `connect`, where being wrong + // would be a page that quietly stops receiving builds. + it("reads no option but `apply` off the page's url", async () => { + app = await createHotApp({ + query: "?apply=reload", + hmrPlugin: false, + code: acceptedApp("v1"), + }); + ({ page, browser } = await runBrowser()); + + await page.goto(`${app.url}?webpack-dev-middleware-connect=false`); + await waitForAppText(page, "v1"); + await plantReloadMarker(page); + + app.edit(acceptedApp("v2")); + await waitForAppText(page, "v2"); + + // Connected regardless of what the url asked for, so the build arrived — + // and as a reload, which is what `apply` is set to. + expect(await readReloadMarker(page)).toBeUndefined(); + }); + it("reads the parameter, not the text of the url", async () => { app = await createHotApp({ query: "?apply=reload",