From dcd76fc69ffbcc9dab80ec831b363e15c38e55b3 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:53:05 +0000 Subject: [PATCH 1/4] refactor: give a client module its own option and one switch to each half `hot.client.transport` names only the wire now; a module exporting a client class of your own goes in the new `hot.client.module`. `hot.inject` switches only `HotModuleReplacementPlugin`, and `hot.client: false` only the runtime in the page, so the two no longer overlap; both off adds nothing, as `inject: false` alone did before. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/finish-dev-server-migration.md | 2 +- .changeset/hot-inject.md | 2 +- README.md | 48 ++++++---- src/hot.js | 13 +-- src/options.check.js | 2 +- src/options.json | 21 ++--- src/utils.js | 64 ++++++------- .../validation-options.test.js.snap.webpack5 | 31 +++++-- test/e2e/inject.test.js | 51 +++++++++- test/e2e/messages.test.js | 2 +- test/e2e/transport.test.js | 2 +- test/inject-client.test.js | 93 +++++++++++++++---- test/universal-target.test.js | 2 +- test/validation-options.test.js | 5 + types/hot.d.ts | 12 ++- 15 files changed, 240 insertions(+), 110 deletions(-) diff --git a/.changeset/finish-dev-server-migration.md b/.changeset/finish-dev-server-migration.md index a107d00a7..96e1571dd 100644 --- a/.changeset/finish-dev-server-migration.md +++ b/.changeset/finish-dev-server-migration.md @@ -2,4 +2,4 @@ "webpack-dev-middleware": minor --- -`hot.client: false` adds no runtime to the page while still applying `HotModuleReplacementPlugin`, which now goes to every compilation the middleware serves, including server bundles that hot-reload through `webpack/hot/poll`. `hot.client.transport` also accepts a module exporting a client class of your own, and the new `hot.ws` option is passed to the `ws` server (compression, `verifyClient`, or a `port` or `server` of its own), with `hot.cors` and `hot.token` still checked. The connection the runtime holds is exported as `webpack-dev-middleware/client/socket` for tooling that listens alongside it. +`hot.client: false` adds no runtime to the page while still applying `HotModuleReplacementPlugin`, which now goes to every compilation the middleware serves, including server bundles that hot-reload through `webpack/hot/poll`. `hot.client.module` names a module exporting a client class of your own, and the new `hot.ws` option is passed to the `ws` server (compression, `verifyClient`, or a `port` or `server` of its own), with `hot.cors` and `hot.token` still checked. The connection the runtime holds is exported as `webpack-dev-middleware/client/socket` for tooling that listens alongside it. diff --git a/.changeset/hot-inject.md b/.changeset/hot-inject.md index 360605d49..918f42c51 100644 --- a/.changeset/hot-inject.md +++ b/.changeset/hot-inject.md @@ -4,4 +4,4 @@ pr: #2430 -`hot` now adds the client and `HotModuleReplacementPlugin` to the compilation itself, so enabling it is all a webpack configuration needs; `hot.inject: false` turns this off for anyone wiring it by hand. Web workers get a client too, nothing is injected into a non-browser target, and the HMR plugin is left out when `hot.client.apply` is `reload` or `nothing`. +`hot` now adds the client and `HotModuleReplacementPlugin` to the compilation itself, so enabling it is all a webpack configuration needs; `hot.client: false` leaves the client out and `hot.inject: false` the plugin, for anyone wiring either by hand. Web workers get a client too, nothing is injected into a non-browser target, and the HMR plugin is left out when `hot.client.apply` is `reload` or `nothing`. diff --git a/README.md b/README.md index e784c949d..8886550f9 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ The object form accepts these options: | **[`progress`](#hotprogress)** | `boolean` | `false` | Publish compilation progress events to the clients. | | **[`cors`](#hotcors)** | `boolean \| string \| string[] \| RegExp \| function \| object` | see below | Which origins may reach the endpoint from a page on another one, over either transport. | | **[`token`](#hottoken)** | `boolean \| string` | see below | A secret the injected client carries and the endpoint requires, over either transport. | -| **[`inject`](#hotinject)** | `boolean` | `true` | Add the client entry and `HotModuleReplacementPlugin`. | +| **[`inject`](#hotinject)** | `boolean` | `true` | Add `HotModuleReplacementPlugin`. | | **[`statsOptions`](#hotstatsoptions)** | `object` | `undefined` | Deprecated — do not use; see [`stats`](#stats). | #### `hot.transport` @@ -615,7 +615,7 @@ client entry was added to hand one over, so every client will be refused. > > **What a token does not protect.** The client reads it from its entry query, so it is a string in the bundle. Anything that can already read your bundle cross-origin can read the token out of it — and over plain `http` to a non-`localhost` address, nothing stops that unless your server sends `Cross-Origin-Resource-Policy`. The token hardens every case where the bundle is not readable; where it is, your source has already gone and the stream is the smaller loss. Closing that needs the response header and a `Host` allowlist, which are [your server's](#security) to set. -**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.inject: false` turns the requirement off — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. +**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.client: false` turns the requirement off — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. A configuration that already lists the client as an entry is the other half of that: it is built before the middleware exists, so it cannot carry a token minted per run. Give it one of your own instead, which both sides can know in advance: @@ -648,9 +648,15 @@ app.get("/my-client-config.json", (_req, res) => { Type: `Boolean` Default: `true` -Add the client entry and `HotModuleReplacementPlugin` to the compilation. Set it to `false` to wire both yourself — see [Hot Module Replacement client](#hot-module-replacement-client). To keep the plugin and wire only the client yourself, set [`hot.client`](#client-options) to `false` instead. +Add `HotModuleReplacementPlugin` to the compilation. Set it to `false` to apply the plugin yourself, or to leave the HMR runtime out of the bundle. -Turn it off when you have a client of your own that the middleware will not recognize as one (anything other than `webpack-dev-middleware/client`), or when you do not want the hot runtime in your bundle at all and are using the endpoint through [`subscribe`](#custom-events) instead. +It is one of two switches, one for each half: `hot.inject` for the compiler, [`hot.client`](#client-options) for the page. + +| You want | Set | +| :------------------------------------------------------------------------------------------------------------------- | :------------------------------- | +| a client of your own in the page — one the middleware does not recognize as its own — and the plugin applied for you | `client: false` | +| the client added for you, and the plugin applied yourself | `inject: false` | +| nothing added at all — both wired yourself, or the endpoint used only through [`subscribe`](#custom-events) | `inject: false`, `client: false` | #### `hot.statsOptions` @@ -682,7 +688,7 @@ app.use(middleware(compiler, { hot: true })); The runtime is told the endpoint and the transport the middleware resolved, so the two agree without the same value being written in two places. -Set `hot.inject` to `false` to wire it yourself instead — the runtime is published under the `./client` subpath: +Set `hot.client` to `false` to wire it yourself instead (and `hot.inject` to `false` as well to apply the plugin yourself) — the runtime is published under the `./client` subpath: ```js const webpack = require("webpack"); @@ -693,9 +699,9 @@ module.exports = { }; ``` -An entry point that already has the client is left alone, so this keeps working without `hot.inject: false`. It is decided per entry point rather than per compilation: in a build with `landing` and `dashboard` where only `landing` has the client, `dashboard` still gets one, because they are separate pages and it would otherwise connect to nothing. +An entry point that already has the client is left alone, so this keeps working without `hot.client: false`. It is decided per entry point rather than per compilation: in a build with `landing` and `dashboard` where only `landing` has the client, `dashboard` still gets one, because they are separate pages and it would otherwise connect to nothing. -The client is recognized as `webpack-dev-middleware/client` (with or without a query) or as the path that resolves to, so your own `./src/client/index.js` is your own file. It is a best effort over the `entry` shapes it can read: a function is computed per build and cannot be read, and a request can reach the client through an alias or a loader. Missing one costs a duplicate entry, not a broken build, and `hot.inject: false` is the way out. +The client is recognized as `webpack-dev-middleware/client` (with or without a query) or as the path that resolves to, so your own `./src/client/index.js` is your own file. It is a best effort over the `entry` shapes it can read: a function is computed per build and cannot be read, and a request can reach the client through an alias or a loader. Missing one costs a duplicate entry, not a broken build, and `hot.client: false` is the way out. #### Which compilations get the runtime @@ -728,10 +734,10 @@ Nothing has to change, and both pieces are recognized rather than duplicated: - An entry that is already `webpack-dev-middleware/client` (with or without a query) is left as it is. - `HotModuleReplacementPlugin` already in `plugins` is not applied twice. It is now redundant, and the middleware says so once per build so you can drop it. -Two things do change, and `hot.inject: false` turns both off: +Two things do change: -- **A client the middleware does not recognize** — anything other than `webpack-dev-middleware/client`, such as another package's hot client — is not detected, so a second client is added and both connect. -- **A project with no `HotModuleReplacementPlugin` on purpose** now gets one, which puts the HMR runtime in the bundle and changes its output. If you only wanted the endpoint to listen to through [`subscribe`](#custom-events), set `hot.inject: false`. A project that sets [`hot.client.apply`](#client-options) to `reload` or `nothing` does not: those modes never use the HMR runtime, so it is left out, and a plugin you wrote into the configuration yourself stays. A page whose url asks for `hmr` or `hmr-only` against such a bundle has no runtime to apply an update with, so `hmr` loads the page instead, and `hmr-only` says so and stops. (A runtime that is missing from a project whose own mode wanted it is a mistake, and is reported once instead of reloading.) +- **A client the middleware does not recognize** — anything other than `webpack-dev-middleware/client`, such as another package's hot client — is not detected, so a second client is added and both connect. `hot.client: false` turns that off. +- **A project with no `HotModuleReplacementPlugin` on purpose** now gets one, which puts the HMR runtime in the bundle and changes its output. `hot.inject: false` turns that off; if you only wanted the endpoint to listen to through [`subscribe`](#custom-events), set `hot.client: false` as well. A project that sets [`hot.client.apply`](#client-options) to `reload` or `nothing` does not: those modes never use the HMR runtime, so it is left out, and a plugin you wrote into the configuration yourself stays. A page whose url asks for `hmr` or `hmr-only` against such a bundle has no runtime to apply an update with, so `hmr` loads the page instead, and `hmr-only` says so and stops. (A runtime that is missing from a project whose own mode wanted it is a mistake, and is reported once instead of reloading.) A package of your own can stand in for the client by re-exporting it. A query written on that module belongs to it rather than to the client, so leave it where the client looks, before requiring the client: @@ -744,7 +750,7 @@ module.exports = require("webpack-dev-middleware/client"); One caveat: if the compiler was already watching before the middleware was created, the runtime appears from the next build onwards rather than the first one. Create the middleware before starting the watch to avoid it. -No client is added when [`hot.transport`](#hottransport) is a function either — the built-in one speaks Server-Sent Events and WebSocket, and a transport of your own carries whatever protocol you wrote it to carry, so the client that speaks it is yours to add unless [`hot.client.transport`](#client-options) says which of the two yours speaks. `HotModuleReplacementPlugin` is still applied for you. `hot.inject: false` silences the reminder, and turns that off as well — apply the plugin yourself if you use it. +No client is added when [`hot.transport`](#hottransport) is a function either — the built-in one speaks Server-Sent Events and WebSocket, and a transport of your own carries whatever protocol you wrote it to carry, so the client that speaks it is yours to add unless [`hot.client.transport`](#client-options) says which of the two yours speaks. `HotModuleReplacementPlugin` is still applied for you. `hot.client: false` silences the reminder. The [client options](#client-options) below are set on the middleware, next to the rest of the hot configuration, and the injected entry carries them to the @@ -809,10 +815,10 @@ app.use( ``` `hot.client: false` adds no runtime to the page and still applies -`HotModuleReplacementPlugin`, for a page that wires a client of its own and -still wants its updates applied. +`HotModuleReplacementPlugin` (unless `hot.inject` is `false`), for a page that +wires a client of its own and still wants its updates applied. -`hot.client` is read only when the client is injected. With `hot.inject: false`, +`hot.client` is read only when the client is injected. With `hot.client: false`, or for a client the configuration already has as an entry, the query string on the entry path is the only source — and it works either way: @@ -835,7 +841,7 @@ as well. Each of these is set either on the middleware as `hot.client.` or on the entry's query as `=`, with the same effect and the same spelling in both — except `url`, which the query also reads as `path`, the name it had -there first. The query also still reads the six names `apply` and `connect` +there first, and `module`, which only node has. The query also still reads the six names `apply` and `connect` replaced (`hot`, `liveReload`, `reload`, `autoConnect`, `reconnect`, `timeout`), folded into those two, until the next major release; `hot.client` does not take them. @@ -850,7 +856,8 @@ narrow the mode in force. | 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 `url`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. Any other string is a module exporting [a client of your own](#a-client-of-your-own). | +| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `url`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | +| `module` | `string` | | A module exporting [a client of your own](#a-client-of-your-own), used in place of the built-in one. Node only: it reaches the runtime as code, not on the query. | | `url` | `string\|Object` | `/__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, or to an object saying only the parts that differ — see [a url in parts](#a-url-in-parts). | | `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, and `Infinity` never does; 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. | @@ -933,7 +940,7 @@ import EventSourceClient from "webpack-dev-middleware/client/sse"; import WebSocketClient from "webpack-dev-middleware/client/ws"; ``` -Name it in [`hot.client.transport`](#client-options) — a path, or a package +Name it in [`hot.client.module`](#client-options) — a path, or a package name resolved from the compilation's context — and the injected runtime uses it in place of the built-in one: @@ -942,7 +949,7 @@ app.use( middleware(compiler, { hot: { transport: "ws", - client: { transport: require.resolve("./my-client.js") }, + client: { module: require.resolve("./my-client.js") }, }, }), ); @@ -950,8 +957,9 @@ app.use( It reaches the runtime as `__webpack_dev_server_client__`, which is how webpack-dev-server's `client.webSocketTransport` option has always worked; a -module exporting the class as `default` is unwrapped. The query carries the -endpoint's own transport, which the runtime still builds the url's scheme from. +module exporting the class as `default` is unwrapped. The runtime still builds +the url's scheme from [`hot.client.transport`](#client-options), which defaults +to the endpoint's own — set it when your client speaks the other one. #### Listening alongside the runtime diff --git a/src/hot.js b/src/hot.js index 3fbaa9d20..77e3e32df 100644 --- a/src/hot.js +++ b/src/hot.js @@ -40,7 +40,8 @@ * and the compilation's name. Setting one here replaces that, which is what a * page reaching the endpoint through a proxy or another origin needs. * @typedef {object} HotClientOptions - * @property {("sse" | "ws" | string)=} transport which transport the runtime speaks, `hot.transport` by default; any other string is a module exporting a client class of your own, used in place of the built-in one + * @property {("sse" | "ws")=} transport which transport the runtime speaks, `hot.transport` by default + * @property {string=} module a module exporting a client class of your own, used in place of the built-in one; it is handed the url and speaks `transport` * @property {(string | PathSpec)=} url where the runtime connects, `hot.path` by default: a path, an absolute url for an endpoint on another origin, or the parts that differ with the rest resolved in the page * @property {string=} name limit the runtime to one compilation's builds, the compilation's own name by default * @property {(boolean | Record)=} overlay show build problems and uncaught runtime errors in an overlay @@ -62,8 +63,8 @@ * @property {boolean=} progress publish compilation progress events to the clients * @property {CorsOption=} cors which origins may reach the endpoint from a page on another one; the local ones by default * @property {(boolean | string)=} token a secret the injected client carries and the endpoint requires; `true` mints one per run, a string uses that one, `false` requires none. Defaults to `false` on both transports; `true` in the next major release - * @property {boolean=} inject add the hot client entry and `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to wire them yourself - * @property {(HotClientOptions | false)=} client options handed to the browser runtime through its entry query; `false` adds no runtime to the page and still applies `HotModuleReplacementPlugin` + * @property {boolean=} inject add `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to apply it yourself + * @property {(HotClientOptions | false)=} client options handed to the browser runtime through its entry query; `false` adds no runtime to the page; `HotModuleReplacementPlugin` is still applied unless `inject` is `false` */ /** @@ -489,12 +490,12 @@ function createHot(compiler, userOptions, statsOption) { const transport = options.transport || HOT_DEFAULT_TRANSPORT; const { cors } = options; const { statsOptions } = options; - // `inject: false` turns it off: the token reaches the browser through the - // entry this middleware adds, so with nothing injected there is no way to + // `client: false` turns it off: the token reaches the browser through the + // entry this middleware adds, so with no client added there is no way to // hand one over, and requiring it would refuse a client the developer wired // correctly. Off by default either way — see `HOT_DEFAULT_TOKEN`. const token = resolveToken( - options.inject === false ? (options.token ?? false) : options.token, + options.client === false ? (options.token ?? false) : options.token, ); const logger = compiler.getInfrastructureLogger("webpack-dev-middleware"); diff --git a/src/options.check.js b/src/options.check.js index 474c7f9d3..20a5a074c 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":{"HotClient":{"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.","anyOf":[{"enum":["sse","ws"]},{"type":"string","minLength":1}]},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}},"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 the upgrades of an HTTP server: hand the server to the middleware's `attach(server)`, or pass each upgrade to `handleUpgrade(req, socket, head)`), 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},"ws":{"description":"Options for the `ws` server behind the `ws` transport: compression, payload limits, `verifyClient`, or a `port` or a `server` of its own to listen on instead of answering the upgrades it is handed. `path`, `noServer` and `clientTracking` are the middleware's and are ignored here.","link":"https://github.com/webpack/webpack-dev-middleware#hot","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. `false` adds no runtime to the page while still applying `HotModuleReplacementPlugin`, for a page that wires a client of its own.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"enum":[false]},{"$ref":"#/definitions/HotClient"}]}}}]}},"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 schema14 = {"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.","anyOf":[{"enum":["sse","ws"]},{"type":"string","minLength":1}]},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}};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.ws !== undefined){let data36 = data32.ws;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/ws",schemaPath:"#/properties/hot/anyOf/1/properties/ws/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 _errs143 = 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 = _errs143 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs145 = errors;const _errs146 = errors;let valid18 = false;const _errs147 = 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 = _errs147 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs149 = errors;if(errors === _errs149){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 = _errs149 === 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 = _errs146;if(vErrors !== null){if(_errs146){vErrors.length = _errs146;}else {vErrors = null;}}}var valid16 = _errs145 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs152 = errors;const _errs153 = errors;let valid19 = false;const _errs154 = 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 = _errs154 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs156 = errors;const _errs158 = errors;let valid21 = false;const _errs159 = errors;if(errors === _errs159){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 = _errs159 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = 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 = _errs161 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs162 = errors;if(errors === _errs162){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 = [err121];}else {vErrors.push(err121);}errors++;}}else {const err122 = {instancePath:instancePath+"/hot/client/connect/retries",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/retries/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}var valid41 = _errs257 === errors;}else {var valid41 = true;}if(valid41){if(data58.timeout !== undefined){let data60 = data58.timeout;const _errs259 = errors;if(errors === _errs259){if(typeof data60 == "number"){if(data60 < 1 || isNaN(data60)){const err123 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}var valid41 = _errs259 === errors;}else {var valid41 = true;}}}}else {const err125 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}var _valid23 = _errs254 === errors;valid40 = valid40 || _valid23;}if(!valid40){const err126 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}else {errors = _errs251;if(vErrors !== null){if(_errs251){vErrors.length = _errs251;}else {vErrors = null;}}}var valid32 = _errs250 === errors;}else {var valid32 = true;}if(valid32){if(data45.pageParamPrefix !== undefined){let data61 = data45.pageParamPrefix;const _errs261 = errors;if(errors === _errs261){if(typeof data61 === "string"){if(data61.length < 1){const err127 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}else {const err128 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}var valid32 = _errs261 === errors;}else {var valid32 = true;}if(valid32){if(data45.logging !== undefined){let data62 = data45.logging;const _errs263 = errors;const _errs264 = errors;let valid42 = false;const _errs265 = errors;if(!((((((data62 === "none") || (data62 === "error")) || (data62 === "warn")) || (data62 === "info")) || (data62 === "log")) || (data62 === "verbose"))){const err129 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[0].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}var _valid24 = _errs265 === errors;valid42 = valid42 || _valid24;if(!valid42){const _errs266 = errors;if(errors === _errs266){if(data62 && typeof data62 == "object" && !Array.isArray(data62)){const _errs268 = errors;for(const key11 in data62){if(!((key11 === "level") || (key11 === "name"))){const err130 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;break;}}if(_errs268 === errors){if(data62.level !== undefined){let data63 = data62.level;const _errs269 = errors;if(!((((((data63 === "none") || (data63 === "error")) || (data63 === "warn")) || (data63 === "info")) || (data63 === "log")) || (data63 === "verbose"))){const err131 = {instancePath:instancePath+"/hot/client/logging/level",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/level/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[1].properties.level.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}var valid43 = _errs269 === errors;}else {var valid43 = true;}if(valid43){if(data62.name !== undefined){let data64 = data62.name;const _errs270 = errors;if(errors === _errs270){if(typeof data64 === "string"){if(data64.length < 1){const err132 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}else {const err133 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}var valid43 = _errs270 === errors;}else {var valid43 = true;}}}}else {const err134 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}var _valid24 = _errs266 === errors;valid42 = valid42 || _valid24;}if(!valid42){const err135 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}else {errors = _errs264;if(vErrors !== null){if(_errs264){vErrors.length = _errs264;}else {vErrors = null;}}}var valid32 = _errs263 === errors;}else {var valid32 = true;}if(valid32){if(data45.dynamicPublicPath !== undefined){const _errs272 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err136 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/definitions/HotClient/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}var valid32 = _errs272 === errors;}else {var valid32 = true;}}}}}}}}}}}}else {const err137 = {instancePath:instancePath+"/hot/client",schemaPath:"#/definitions/HotClient/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}var _valid16 = _errs201 === errors;valid30 = valid30 || _valid16;}if(!valid30){const err138 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}else {errors = _errs199;if(vErrors !== null){if(_errs199){vErrors.length = _errs199;}else {vErrors = null;}}}var valid16 = _errs198 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err139 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err140 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}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":{"HotClient":{"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"module":{"description":"A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.","type":"string","minLength":1},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}},"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 the upgrades of an HTTP server: hand the server to the middleware's `attach(server)`, or pass each upgrade to `handleUpgrade(req, socket, head)`), 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},"ws":{"description":"Options for the `ws` server behind the `ws` transport: compression, payload limits, `verifyClient`, or a `port` or a `server` of its own to listen on instead of answering the upgrades it is handed. `path`, `noServer` and `clientTracking` are the middleware's and are ignored here.","link":"https://github.com/webpack/webpack-dev-middleware#hot","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 HotModuleReplacementPlugin to the compilation. Turn it off to apply it yourself; `client: false` is what keeps the runtime out of the page.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query. `false` adds no runtime to the page, for a page that wires a client of its own; `HotModuleReplacementPlugin` is still applied unless `inject` is `false`.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"enum":[false]},{"$ref":"#/definitions/HotClient"}]}}}]}},"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 schema14 = {"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"module":{"description":"A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.","type":"string","minLength":1},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}};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.ws !== undefined){let data36 = data32.ws;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/ws",schemaPath:"#/properties/hot/anyOf/1/properties/ws/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 _errs143 = 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 = _errs143 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs145 = errors;const _errs146 = errors;let valid18 = false;const _errs147 = 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 = _errs147 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs149 = errors;if(errors === _errs149){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 = _errs149 === 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 = _errs146;if(vErrors !== null){if(_errs146){vErrors.length = _errs146;}else {vErrors = null;}}}var valid16 = _errs145 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs152 = errors;const _errs153 = errors;let valid19 = false;const _errs154 = 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 = _errs154 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs156 = errors;const _errs158 = errors;let valid21 = false;const _errs159 = errors;if(errors === _errs159){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 = _errs159 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = 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 = _errs161 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs162 = errors;if(errors === _errs162){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 = [err120];}else {vErrors.push(err120);}errors++;}}else {const err121 = {instancePath:instancePath+"/hot/client/connect/retries",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/retries/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}var valid40 = _errs255 === errors;}else {var valid40 = true;}if(valid40){if(data59.timeout !== undefined){let data61 = data59.timeout;const _errs257 = errors;if(errors === _errs257){if(typeof data61 == "number"){if(data61 < 1 || isNaN(data61)){const err122 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}else {const err123 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}var valid40 = _errs257 === errors;}else {var valid40 = true;}}}}else {const err124 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}var _valid22 = _errs252 === errors;valid39 = valid39 || _valid22;}if(!valid39){const err125 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}else {errors = _errs249;if(vErrors !== null){if(_errs249){vErrors.length = _errs249;}else {vErrors = null;}}}var valid32 = _errs248 === errors;}else {var valid32 = true;}if(valid32){if(data45.pageParamPrefix !== undefined){let data62 = data45.pageParamPrefix;const _errs259 = errors;if(errors === _errs259){if(typeof data62 === "string"){if(data62.length < 1){const err126 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}var valid32 = _errs259 === errors;}else {var valid32 = true;}if(valid32){if(data45.logging !== undefined){let data63 = data45.logging;const _errs261 = errors;const _errs262 = errors;let valid41 = false;const _errs263 = errors;if(!((((((data63 === "none") || (data63 === "error")) || (data63 === "warn")) || (data63 === "info")) || (data63 === "log")) || (data63 === "verbose"))){const err128 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[0].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}var _valid23 = _errs263 === errors;valid41 = valid41 || _valid23;if(!valid41){const _errs264 = errors;if(errors === _errs264){if(data63 && typeof data63 == "object" && !Array.isArray(data63)){const _errs266 = errors;for(const key11 in data63){if(!((key11 === "level") || (key11 === "name"))){const err129 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;break;}}if(_errs266 === errors){if(data63.level !== undefined){let data64 = data63.level;const _errs267 = errors;if(!((((((data64 === "none") || (data64 === "error")) || (data64 === "warn")) || (data64 === "info")) || (data64 === "log")) || (data64 === "verbose"))){const err130 = {instancePath:instancePath+"/hot/client/logging/level",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/level/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[1].properties.level.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var valid42 = _errs267 === errors;}else {var valid42 = true;}if(valid42){if(data63.name !== undefined){let data65 = data63.name;const _errs268 = errors;if(errors === _errs268){if(typeof data65 === "string"){if(data65.length < 1){const err131 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}else {const err132 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}var valid42 = _errs268 === errors;}else {var valid42 = true;}}}}else {const err133 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}var _valid23 = _errs264 === errors;valid41 = valid41 || _valid23;}if(!valid41){const err134 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}else {errors = _errs262;if(vErrors !== null){if(_errs262){vErrors.length = _errs262;}else {vErrors = null;}}}var valid32 = _errs261 === errors;}else {var valid32 = true;}if(valid32){if(data45.dynamicPublicPath !== undefined){const _errs270 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err135 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/definitions/HotClient/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}var valid32 = _errs270 === errors;}else {var valid32 = true;}}}}}}}}}}}}}else {const err136 = {instancePath:instancePath+"/hot/client",schemaPath:"#/definitions/HotClient/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}var _valid16 = _errs201 === errors;valid30 = valid30 || _valid16;}if(!valid30){const err137 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}else {errors = _errs199;if(vErrors !== null){if(_errs199){vErrors.length = _errs199;}else {vErrors = null;}}}var valid16 = _errs198 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err138 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err139 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}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 93e88603d..eee651f02 100644 --- a/src/options.json +++ b/src/options.json @@ -6,16 +6,13 @@ "additionalProperties": false, "properties": { "transport": { - "description": "Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.", - "anyOf": [ - { - "enum": ["sse", "ws"] - }, - { - "type": "string", - "minLength": 1 - } - ] + "description": "Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.", + "enum": ["sse", "ws"] + }, + "module": { + "description": "A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.", + "type": "string", + "minLength": 1 }, "url": { "description": "Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.", @@ -523,12 +520,12 @@ "additionalProperties": true }, "inject": { - "description": "Add the hot client entry and HotModuleReplacementPlugin to the compilation. Turn it off to wire them yourself.", + "description": "Add HotModuleReplacementPlugin to the compilation. Turn it off to apply it yourself; `client: false` is what keeps the runtime out of the page.", "link": "https://github.com/webpack/webpack-dev-middleware#hot", "type": "boolean" }, "client": { - "description": "Options handed to the browser runtime through its entry query. `false` adds no runtime to the page while still applying `HotModuleReplacementPlugin`, for a page that wires a client of its own.", + "description": "Options handed to the browser runtime through its entry query. `false` adds no runtime to the page, for a page that wires a client of its own; `HotModuleReplacementPlugin` is still applied unless `inject` is `false`.", "link": "https://github.com/webpack/webpack-dev-middleware#hot", "anyOf": [ { diff --git a/src/utils.js b/src/utils.js index 2fd168038..ce4726aa5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1361,7 +1361,7 @@ function isClientRequest(request, context) { * * Best effort by design: `entry` can be a function, and a request can reach the * client through an alias or a loader. Missing one of those costs a duplicate - * entry, not a broken build, and `hot.inject: false` is the way out. + * entry, not a broken build, and `hot.client: false` is the way out. * @param {Compiler} compiler compiler * @returns {string[] | null} the names that need one, or null when they all do */ @@ -1472,6 +1472,11 @@ function clientQuery(client, resolvedPath) { } for (let [key, value] of Object.entries(client)) { + // A module is handed to the runtime as code, not as text on its query. + if (key === "module") { + continue; + } + // A url given in parts replaces the resolved path in the query outright, // so the part it leaves out has to be carried over rather than left to the // client's own default — which is the same path only until someone sets @@ -1536,17 +1541,10 @@ function clientQuery(client, resolvedPath) { } /** - * The two transports the bundled client speaks. Anything else given as - * `hot.client.transport` names a module of someone else's that speaks for it. - * @type {readonly string[]} - */ -const BUILT_IN_CLIENT_TRANSPORTS = ["sse", "ws"]; - -/** - * Where a client transport of someone else's lives. Looked up from the + * Where a client module of someone else's lives. Looked up from the * project first, where a bare package name is installed, and then from here, * so an absolute path or a package this one can see works as well. - * @param {string} request what `hot.client.transport` named + * @param {string} request what `hot.client.module` named * @param {string} context the compilation's context * @returns {string} the resolved module */ @@ -1558,7 +1556,7 @@ function resolveClientTransport(request, context) { return require.resolve(request); } catch { throw new Error( - `'hot.client.transport' must be 'sse', 'ws', or a module that exports a client class, but '${request}' could not be resolved from '${context}'.`, + `'hot.client.module' must be a module that exports a client class, but '${request}' could not be resolved from '${context}'.`, ); } } @@ -1580,10 +1578,10 @@ function resolveClientTransport(request, context) { * @param {Logger} logger logger */ function injectHotClient(compilers, options, logger) { - if (options.inject === false) { - return; - } - + // The two halves are switched separately: `client: false` keeps the runtime + // out of the page, `inject: false` keeps `HotModuleReplacementPlugin` out of + // the compilation, and both together add nothing at all. + const addsPlugin = options.inject !== false; // `client: false` is no runtime in the page while the compilation still // gets everything else — the plugin, for a page that wires a client of its // own and still wants its updates. @@ -1601,18 +1599,17 @@ function injectHotClient(compilers, options, logger) { // runtime and the one written by hand is the one that counts. const client = clientQuery(clientOptions, options.path); - // A client transport of someone else's: a module exporting a class with the - // same shape as the built-in two, handed to the runtime through + // A client of someone else's: a module exporting a class with the same + // shape as the built-in two, handed to the runtime through // `__webpack_dev_server_client__`, which it uses in place of its own. What - // goes on the query is the wire the endpoint serves, since the runtime still - // works out the connection url and how to hold it open from that. + // goes on the query is still a wire, since the runtime works out the + // connection url and how to hold it open from that: the one given, else the + // endpoint's, else a WebSocket — the wire webpack-dev-server has always + // built a custom client's url for. /** @type {string | undefined} */ - const customClientTransport = - client.transport && !BUILT_IN_CLIENT_TRANSPORTS.includes(client.transport) - ? client.transport - : undefined; + const clientModule = clientOptions ? clientOptions.module : undefined; - if (customClientTransport) { + if (clientModule && !client.transport) { client.transport = typeof options.transport === "string" ? options.transport : "ws"; } @@ -1635,7 +1632,7 @@ function injectHotClient(compilers, options, logger) { // itself what it speaks. if ( typeof options.transport === "string" && - !customClientTransport && + !clientModule && client.transport && client.transport !== options.transport && !client.url @@ -1654,10 +1651,10 @@ function injectHotClient(compilers, options, logger) { // `hot` to apply it has no plugin of its own to fall back to. const isWeb = addsClient && isWebTarget(compiler); - if (isWeb && customClientTransport) { + if (isWeb && clientModule) { new webpack.ProvidePlugin({ __webpack_dev_server_client__: resolveClientTransport( - customClientTransport, + clientModule, compiler.context, ), }).apply(compiler); @@ -1670,7 +1667,7 @@ function injectHotClient(compilers, options, logger) { if (!warned) { warned = true; logger.warn( - "'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it — 'HotModuleReplacementPlugin' is still applied for you, and 'hot.inject: false' silences this.", + "'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it — 'HotModuleReplacementPlugin' is still applied for you, and 'hot.client: false' silences this.", ); } } else { @@ -1715,11 +1712,14 @@ function injectHotClient(compilers, options, logger) { // Applying a build to the page by reloading it, or not at all, has no use // for the HMR runtime, which would only add to the bundle. It is left out - // when `hot.client.apply` says so; a plugin written into the configuration - // is the developer's and stays. + // when `hot.client.apply` says so, and `hot.inject: false` leaves it out + // whatever the mode; a plugin written into the configuration is the + // developer's and stays. const needsHmr = - !clientOptions || - (clientOptions.apply !== "reload" && clientOptions.apply !== "nothing"); + addsPlugin && + (!clientOptions || + (clientOptions.apply !== "reload" && + clientOptions.apply !== "nothing")); const hmrPluginExists = compiler.options.plugins.some( (plugin) => diff --git a/test/__snapshots__/validation-options.test.js.snap.webpack5 b/test/__snapshots__/validation-options.test.js.snap.webpack5 index 63b7b06f9..7019ccdce 100644 --- a/test/__snapshots__/validation-options.test.js.snap.webpack5 +++ b/test/__snapshots__/validation-options.test.js.snap.webpack5 @@ -78,63 +78,76 @@ exports[`validation should throw an error on the "headers" option with "true" va exports[`validation should throw an error on the "hot" option with "{"client":{"autoConnect":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'autoConnect'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"hot":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'hot'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"liveReload":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'liveReload'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; +exports[`validation should throw an error on the "hot" option with "{"client":{"module":""}}" value 1`] = ` +"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. + - options.hot.client.module should be a non-empty string. + -> A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks \`transport\`." +`; + exports[`validation should throw an error on the "hot" option with "{"client":{"path":"/__hmr"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'path'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"reconnect":3}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'reconnect'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"reload":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'reload'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"timeout":5000}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'timeout'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"token":"a-token"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'token'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; +exports[`validation should throw an error on the "hot" option with "{"client":{"transport":"./my-client.js"}}" value 1`] = ` +"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. + - options.hot.client.transport should be one of these: + "sse" | "ws" + -> Which transport the runtime speaks: \`sse\` or \`ws\`. Defaults to the resolved \`hot.transport\`." +`; + exports[`validation should throw an error on the "hot" option with "{"client":{"urlPrefix":"my-server"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'urlPrefix'. These properties are valid: - object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; diff --git a/test/e2e/inject.test.js b/test/e2e/inject.test.js index 6cc80e8c3..245d66df3 100644 --- a/test/e2e/inject.test.js +++ b/test/e2e/inject.test.js @@ -62,11 +62,11 @@ describe("hot with nothing but the middleware (browser)", () => { ); }); - it("leaves the compilation alone when injection is off", async () => { + it("leaves the compilation alone when injection and the client are off", async () => { hotApp = await createHotApp({ bare: true, code: acceptedApp("v1"), - hot: { inject: false }, + hot: { inject: false, client: false }, }); ({ page, browser } = await runBrowser()); const console_ = collectConsole(page); @@ -83,6 +83,53 @@ describe("hot with nothing but the middleware (browser)", () => { expect(console_.messages.join("\n")).not.toContain("connected"); }); + it("adds the client but leaves the plugin to you when injection is off", async () => { + hotApp = await createHotApp({ + code: acceptedApp("v1"), + // The configuration's own plugin and client entry, as a project that + // applies the plugin itself has them. + hot: { inject: false }, + }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(hotApp.url); + await waitForAppText(page, "v1"); + await console_.waitFor("connected"); + await page.evaluate(() => { + globalThis.notReloaded = true; + }); + + hotApp.edit(acceptedApp("v2")); + await waitForAppText(page, "v2"); + + // Applied in place by the plugin the configuration has, which the + // middleware neither duplicated nor asked to be dropped. + expect(await page.evaluate(() => globalThis.notReloaded)).toBe(true); + }); + + it("adds the client with no plugin when injection is off and there is none", async () => { + hotApp = await createHotApp({ + bare: true, + code: ` + document.getElementById("app").textContent = "v1"; + globalThis.hasHotRuntime = Boolean(module.hot); + `, + hot: { inject: false }, + }); + ({ page, browser } = await runBrowser()); + const console_ = collectConsole(page); + + await page.goto(hotApp.url); + await waitForAppText(page, "v1"); + + // The client is the middleware's half and is still added; the HMR + // runtime is not in the bundle. + await console_.waitFor("connected"); + + expect(await page.evaluate(() => globalThis.hasHotRuntime)).toBe(false); + }); + it("hands the browser options to the runtime", async () => { hotApp = await createHotApp({ bare: true, diff --git a/test/e2e/messages.test.js b/test/e2e/messages.test.js index ecf179e4a..2e23dbc8b 100644 --- a/test/e2e/messages.test.js +++ b/test/e2e/messages.test.js @@ -322,7 +322,7 @@ describe("a module standing in for the client (browser)", () => { hotApp = await createHotApp({ bare: true, - hot: { inject: false, path: "/stand-in-hmr" }, + hot: { inject: false, client: false, path: "/stand-in-hmr" }, files: { "stand-in.js": ` globalThis.__webpack_dev_middleware_client_query__ = "?path=/stand-in-hmr"; diff --git a/test/e2e/transport.test.js b/test/e2e/transport.test.js index 07fb81fc6..1fe93aecf 100644 --- a/test/e2e/transport.test.js +++ b/test/e2e/transport.test.js @@ -206,7 +206,7 @@ describe("a client transport of someone else's (browser)", () => { bare: true, hot: { client: { - transport: path.resolve( + module: path.resolve( __dirname, "../fixtures/custom-client-transport.js", ), diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 432ccbdb2..1ca576191 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -757,7 +757,8 @@ describe("what injectHotClient leaves alone", () => { return created; } - it("adds nothing at all when inject is off", () => { + // One switch for each half: `inject` the compiler's, `client` the page's. + it("adds the client but not the plugin when inject is off", () => { const instance = compiler(); const before = entryCount(instance); @@ -767,10 +768,45 @@ describe("what injectHotClient leaves alone", () => { logger, ); + expect(entryCount(instance)).toBe(before + 1); + expect(hasHmrPlugin(instance)).toBe(false); + }); + + it("adds nothing at all when inject and the client are both off", () => { + const instance = compiler(); + const before = entryCount(instance); + + injectHotClient( + [instance], + { + path: "/__webpack_hmr", + transport: "sse", + inject: false, + client: false, + }, + logger, + ); + expect(entryCount(instance)).toBe(before); expect(hasHmrPlugin(instance)).toBe(false); }); + it("does not ask for a plugin of your own to be dropped when inject is off", () => { + const instance = compiler({ + plugins: [new webpack.HotModuleReplacementPlugin()], + }); + /** @type {string[]} */ + const said = []; + + injectHotClient( + [instance], + { path: "/__webpack_hmr", transport: "sse", inject: false }, + { warn: (message) => said.push(message), log: () => {} }, + ); + + expect(said).toStrictEqual([]); + }); + it("gives a compilation a browser does not run the plugin, not the client", () => { // A server bundle hot-reloads itself through `module.hot` too — with // `webpack/hot/poll` or `webpack/hot/signal` — and webpack-dev-server has @@ -1032,11 +1068,10 @@ describe("the entry query the client is given", () => { }); }); -// `hot.client.transport` naming a module rather than a wire: a client class of -// someone else's, which the runtime uses in place of the built-in one through -// `__webpack_dev_server_client__`. webpack-dev-server's -// `client.webSocketTransport` has always worked this way. -describe("a client transport of someone else's", () => { +// `hot.client.module`: a client class of someone else's, which the runtime +// uses in place of the built-in one through `__webpack_dev_server_client__`. +// webpack-dev-server's `client.webSocketTransport` has always worked this way. +describe("a client module of someone else's", () => { const CUSTOM = path.resolve(__dirname, "fixtures/custom-client-transport.js"); /** @type {EXPECTED_OBJECT[]} */ @@ -1123,7 +1158,7 @@ describe("a client transport of someone else's", () => { const { provided } = inject({ path: "/__webpack_hmr", transport: "ws", - client: { transport: CUSTOM }, + client: { module: CUSTOM }, }); expect(provided).toStrictEqual([{ __webpack_dev_server_client__: CUSTOM }]); @@ -1134,7 +1169,7 @@ describe("a client transport of someone else's", () => { { path: "/__webpack_hmr", transport: "ws", - client: { transport: "./custom-client-transport.js" }, + client: { module: "./custom-client-transport.js" }, }, { context: path.resolve(__dirname, "fixtures") }, ); @@ -1148,7 +1183,7 @@ describe("a client transport of someone else's", () => { const { queries } = inject({ path: "/__webpack_hmr", transport: "sse", - client: { transport: CUSTOM }, + client: { module: CUSTOM }, }); expect(queries).toStrictEqual([ @@ -1160,7 +1195,7 @@ describe("a client transport of someone else's", () => { inject({ path: "/__webpack_hmr", transport: "ws", - client: { transport: CUSTOM }, + client: { module: CUSTOM }, }); expect(warnings).toStrictEqual([]); @@ -1177,7 +1212,7 @@ describe("a client transport of someone else's", () => { publish: () => {}, publishTo: () => {}, }), - client: { transport: CUSTOM }, + client: { module: CUSTOM }, }); expect(queries).toStrictEqual([{ url: "/ws", transport: "ws" }]); @@ -1200,7 +1235,7 @@ describe("a client transport of someone else's", () => { { path: "/__webpack_hmr", transport: "ws", - client: { transport: CUSTOM }, + client: { module: CUSTOM }, }, { target: "node" }, ); @@ -1213,19 +1248,33 @@ describe("a client transport of someone else's", () => { inject({ path: "/__webpack_hmr", transport: "ws", - client: { transport: "./no-such-client.js" }, + client: { module: "./no-such-client.js" }, }), ).toThrow( - /'hot\.client\.transport'.*'\.\/no-such-client\.js' could not be resolved/, + /'hot\.client\.module'.*'\.\/no-such-client\.js' could not be resolved/, ); }); - it("is accepted by the schema", () => { + it("is accepted by the schema, apart from the wire", () => { const client = schema.definitions.HotClient; - expect(client.properties.transport.anyOf).toStrictEqual([ - { enum: ["sse", "ws"] }, - { type: "string", minLength: 1 }, + expect(client.properties.module).toMatchObject({ + type: "string", + minLength: 1, + }); + // A module is no longer taken where a wire is asked for. + expect(client.properties.transport.enum).toStrictEqual(["sse", "ws"]); + }); + + it("speaks the wire it is told to, rather than the endpoint's", () => { + const { queries } = inject({ + path: "/__webpack_hmr", + transport: "sse", + client: { module: CUSTOM, transport: "ws", url: "wss://elsewhere/ws" }, + }); + + expect(queries).toStrictEqual([ + { url: "wss://elsewhere/ws", transport: "ws" }, ]); }); }); @@ -1325,6 +1374,10 @@ describe("node and the query take the same names", () => { ]), ]; + // The one name only node has: `module` reaches the runtime as code, through + // `__webpack_dev_server_client__`, not as text on its query. + const nodeOnly = ["module"]; + /** @type {string[]} every name `hot.client` accepts */ const takenInNode = Object.keys(schema.definitions.HotClient.properties); @@ -1346,7 +1399,9 @@ describe("node and the query take the same names", () => { it("is one set of names, with nothing on one side only", () => { expect( readByClient.filter((name) => !queryOnly.includes(name)).toSorted(), - ).toStrictEqual(takenInNode.toSorted()); + ).toStrictEqual( + takenInNode.filter((name) => !nodeOnly.includes(name)).toSorted(), + ); expect( takenInNode.filter((name) => queryOnly.includes(name)), ).toStrictEqual([]); diff --git a/test/universal-target.test.js b/test/universal-target.test.js index b140697e9..cf87fa3cc 100644 --- a/test/universal-target.test.js +++ b/test/universal-target.test.js @@ -114,7 +114,7 @@ const CLIENTS = [ ], [ "the client through a re-export of it", - () => ({ inject: false }), + () => ({ inject: false, client: false }), (dir) => [`${path.join(dir, "stand-in.js")}?path=/hmr`, "./app.js"], /stand-in\.js\?/, ], diff --git a/test/validation-options.test.js b/test/validation-options.test.js index d33e86a39..c8fc37df1 100644 --- a/test/validation-options.test.js +++ b/test/validation-options.test.js @@ -112,6 +112,8 @@ describe("validation", () => { { client: { url: "wss://dev.example.com/__hmr" } }, { client: { url: { port: 8080 } } }, { client: { pageParamPrefix: "my-server" } }, + { client: { module: "./custom-client-transport.js", transport: "ws" } }, + { inject: false, client: false }, { client: { apply: "reload", connect: { retries: 3, timeout: 5000 } } }, ], failure: [ @@ -145,6 +147,9 @@ describe("validation", () => { { client: { autoConnect: false } }, { client: { reconnect: 3 } }, { client: { timeout: 5000 } }, + // A module goes in `module`, not where a wire is asked for. + { client: { transport: "./my-client.js" } }, + { client: { module: "" } }, ], }, }; diff --git a/types/hot.d.ts b/types/hot.d.ts index 7a2b30685..38250458f 100644 --- a/types/hot.d.ts +++ b/types/hot.d.ts @@ -212,9 +212,13 @@ type PathSpec = { */ type HotClientOptions = { /** - * which transport the runtime speaks, `hot.transport` by default; any other string is a module exporting a client class of your own, used in place of the built-in one + * which transport the runtime speaks, `hot.transport` by default */ - transport?: ("sse" | "ws" | string) | undefined; + transport?: ("sse" | "ws") | undefined; + /** + * a module exporting a client class of your own, used in place of the built-in one; it is handed the url and speaks `transport` + */ + module?: string | undefined; /** * where the runtime connects, `hot.path` by default: a path, an absolute url for an endpoint on another origin, or the parts that differ with the rest resolved in the page */ @@ -302,11 +306,11 @@ type HotOptions = { */ token?: (boolean | string) | undefined; /** - * add the hot client entry and `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to wire them yourself + * add `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to apply it yourself */ inject?: boolean | undefined; /** - * options handed to the browser runtime through its entry query; `false` adds no runtime to the page and still applies `HotModuleReplacementPlugin` + * options handed to the browser runtime through its entry query; `false` adds no runtime to the page; `HotModuleReplacementPlugin` is still applied unless `inject` is `false` */ client?: (HotClientOptions | false) | undefined; }; From 06c0cf81606e0d87e56bd0300f7e8d17e07db9ed Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:25:40 +0000 Subject: [PATCH 2/4] refactor: one hot.inject enum for what is added to the compilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the `hot.client.module` split, so `hot.client.transport` names a wire or a client module as before. `hot.inject` becomes "all" | "plugin" | "client" | "none" — the runtime (client and overlay), HotModuleReplacementPlugin, both or neither — replacing the boolean and `hot.client: false`, so one option says what goes in. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- .changeset/finish-dev-server-migration.md | 2 +- .changeset/hot-inject.md | 2 +- README.md | 75 +++++----- src/hot.js | 17 +-- src/options.check.js | 2 +- src/options.json | 34 ++--- src/utils.js | 78 +++++----- .../validation-options.test.js.snap.webpack5 | 68 +++++---- test/e2e/inject.test.js | 16 +-- test/e2e/messages.test.js | 2 +- test/e2e/process-update.test.js | 2 +- test/e2e/transport.test.js | 2 +- test/inject-client.test.js | 133 +++++++----------- test/universal-target.test.js | 2 +- test/validation-options.test.js | 14 +- types/hot.d.ts | 16 +-- types/utils.d.ts | 6 +- 17 files changed, 225 insertions(+), 246 deletions(-) diff --git a/.changeset/finish-dev-server-migration.md b/.changeset/finish-dev-server-migration.md index 96e1571dd..33056b140 100644 --- a/.changeset/finish-dev-server-migration.md +++ b/.changeset/finish-dev-server-migration.md @@ -2,4 +2,4 @@ "webpack-dev-middleware": minor --- -`hot.client: false` adds no runtime to the page while still applying `HotModuleReplacementPlugin`, which now goes to every compilation the middleware serves, including server bundles that hot-reload through `webpack/hot/poll`. `hot.client.module` names a module exporting a client class of your own, and the new `hot.ws` option is passed to the `ws` server (compression, `verifyClient`, or a `port` or `server` of its own), with `hot.cors` and `hot.token` still checked. The connection the runtime holds is exported as `webpack-dev-middleware/client/socket` for tooling that listens alongside it. +`HotModuleReplacementPlugin` now goes to every compilation the middleware serves, including server bundles that hot-reload through `webpack/hot/poll`. `hot.client.transport` also accepts a module exporting a client class of your own, and the new `hot.ws` option is passed to the `ws` server (compression, `verifyClient`, or a `port` or `server` of its own), with `hot.cors` and `hot.token` still checked. The connection the runtime holds is exported as `webpack-dev-middleware/client/socket` for tooling that listens alongside it. diff --git a/.changeset/hot-inject.md b/.changeset/hot-inject.md index 918f42c51..d588bc34d 100644 --- a/.changeset/hot-inject.md +++ b/.changeset/hot-inject.md @@ -4,4 +4,4 @@ pr: #2430 -`hot` now adds the client and `HotModuleReplacementPlugin` to the compilation itself, so enabling it is all a webpack configuration needs; `hot.client: false` leaves the client out and `hot.inject: false` the plugin, for anyone wiring either by hand. Web workers get a client too, nothing is injected into a non-browser target, and the HMR plugin is left out when `hot.client.apply` is `reload` or `nothing`. +`hot` now adds the client and `HotModuleReplacementPlugin` to the compilation itself, so enabling it is all a webpack configuration needs. `hot.inject` says what is added: `"all"` (the default), `"plugin"` for a page with a client of its own, `"client"` for a configuration that applies the plugin itself, or `"none"`. Web workers get a client too, nothing is injected into a non-browser target, and the HMR plugin is left out when `hot.client.apply` is `reload` or `nothing`. diff --git a/README.md b/README.md index 8886550f9..a3e34f8fa 100644 --- a/README.md +++ b/README.md @@ -361,16 +361,16 @@ See [Hot Module Replacement client](#hot-module-replacement-client) for which co The object form accepts these options: -| Name | Type | Default | Description | -| :------------------------------------: | :-------------------------------------------------------------: | :----------------: | :-------------------------------------------------------------------------------------- | -| **[`transport`](#hottransport)** | `string \| function` | `'sse'` | How events reach the clients. | -| **[`path`](#hotpath)** | `string` | `'/__webpack_hmr'` | Path the endpoint is served at. | -| **[`heartbeat`](#hotheartbeat)** | `number` | `10000` | Interval (in milliseconds) between keep-alive frames. | -| **[`progress`](#hotprogress)** | `boolean` | `false` | Publish compilation progress events to the clients. | -| **[`cors`](#hotcors)** | `boolean \| string \| string[] \| RegExp \| function \| object` | see below | Which origins may reach the endpoint from a page on another one, over either transport. | -| **[`token`](#hottoken)** | `boolean \| string` | see below | A secret the injected client carries and the endpoint requires, over either transport. | -| **[`inject`](#hotinject)** | `boolean` | `true` | Add `HotModuleReplacementPlugin`. | -| **[`statsOptions`](#hotstatsoptions)** | `object` | `undefined` | Deprecated — do not use; see [`stats`](#stats). | +| Name | Type | Default | Description | +| :------------------------------------: | :-------------------------------------------------------------: | :----------------: | :-------------------------------------------------------------------------------------------- | +| **[`transport`](#hottransport)** | `string \| function` | `'sse'` | How events reach the clients. | +| **[`path`](#hotpath)** | `string` | `'/__webpack_hmr'` | Path the endpoint is served at. | +| **[`heartbeat`](#hotheartbeat)** | `number` | `10000` | Interval (in milliseconds) between keep-alive frames. | +| **[`progress`](#hotprogress)** | `boolean` | `false` | Publish compilation progress events to the clients. | +| **[`cors`](#hotcors)** | `boolean \| string \| string[] \| RegExp \| function \| object` | see below | Which origins may reach the endpoint from a page on another one, over either transport. | +| **[`token`](#hottoken)** | `boolean \| string` | see below | A secret the injected client carries and the endpoint requires, over either transport. | +| **[`inject`](#hotinject)** | `"all" \| "plugin" \| "client" \| "none"` | `"all"` | What is added to the compilation: the runtime, `HotModuleReplacementPlugin`, both or neither. | +| **[`statsOptions`](#hotstatsoptions)** | `object` | `undefined` | Deprecated — do not use; see [`stats`](#stats). | #### `hot.transport` @@ -615,7 +615,7 @@ client entry was added to hand one over, so every client will be refused. > > **What a token does not protect.** The client reads it from its entry query, so it is a string in the bundle. Anything that can already read your bundle cross-origin can read the token out of it — and over plain `http` to a non-`localhost` address, nothing stops that unless your server sends `Cross-Origin-Resource-Policy`. The token hardens every case where the bundle is not readable; where it is, your source has already gone and the stream is the smaller loss. Closing that needs the response header and a `Host` allowlist, which are [your server's](#security) to set. -**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.client: false` turns the requirement off — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. +**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.inject: "plugin"` or `"none"` turns the requirement off — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. A configuration that already lists the client as an entry is the other half of that: it is built before the middleware exists, so it cannot carry a token minted per run. Give it one of your own instead, which both sides can know in advance: @@ -645,18 +645,17 @@ app.get("/my-client-config.json", (_req, res) => { #### `hot.inject` -Type: `Boolean` -Default: `true` - -Add `HotModuleReplacementPlugin` to the compilation. Set it to `false` to apply the plugin yourself, or to leave the HMR runtime out of the bundle. +Type: `"all" | "plugin" | "client" | "none"` +Default: `"all"` -It is one of two switches, one for each half: `hot.inject` for the compiler, [`hot.client`](#client-options) for the page. +What the middleware adds to the compilation. There are two pieces: the runtime in the page — this package's client, with its overlay and building indicator — and `HotModuleReplacementPlugin`, which is webpack's own hot logic. -| You want | Set | -| :------------------------------------------------------------------------------------------------------------------- | :------------------------------- | -| a client of your own in the page — one the middleware does not recognize as its own — and the plugin applied for you | `client: false` | -| the client added for you, and the plugin applied yourself | `inject: false` | -| nothing added at all — both wired yourself, or the endpoint used only through [`subscribe`](#custom-events) | `inject: false`, `client: false` | +| Value | Runtime | Plugin | For | +| :--------- | :-----: | :----: | :--------------------------------------------------------------------------------------------------------------- | +| `"all"` | ✓ | ✓ | the default: nothing to add to the webpack configuration | +| `"plugin"` | | ✓ | a page that wires a client of its own — one the middleware does not recognize as its own | +| `"client"` | ✓ | | a configuration that applies `HotModuleReplacementPlugin` itself, or wants the page reloaded without the runtime | +| `"none"` | | | wiring both yourself, or using the endpoint only through [`subscribe`](#custom-events) | #### `hot.statsOptions` @@ -688,7 +687,7 @@ app.use(middleware(compiler, { hot: true })); The runtime is told the endpoint and the transport the middleware resolved, so the two agree without the same value being written in two places. -Set `hot.client` to `false` to wire it yourself instead (and `hot.inject` to `false` as well to apply the plugin yourself) — the runtime is published under the `./client` subpath: +Set `hot.inject` to `"plugin"` to wire it yourself instead (or `"none"` to apply the plugin yourself as well) — the runtime is published under the `./client` subpath: ```js const webpack = require("webpack"); @@ -699,9 +698,9 @@ module.exports = { }; ``` -An entry point that already has the client is left alone, so this keeps working without `hot.client: false`. It is decided per entry point rather than per compilation: in a build with `landing` and `dashboard` where only `landing` has the client, `dashboard` still gets one, because they are separate pages and it would otherwise connect to nothing. +An entry point that already has the client is left alone, so this keeps working with the default `hot.inject`. It is decided per entry point rather than per compilation: in a build with `landing` and `dashboard` where only `landing` has the client, `dashboard` still gets one, because they are separate pages and it would otherwise connect to nothing. -The client is recognized as `webpack-dev-middleware/client` (with or without a query) or as the path that resolves to, so your own `./src/client/index.js` is your own file. It is a best effort over the `entry` shapes it can read: a function is computed per build and cannot be read, and a request can reach the client through an alias or a loader. Missing one costs a duplicate entry, not a broken build, and `hot.client: false` is the way out. +The client is recognized as `webpack-dev-middleware/client` (with or without a query) or as the path that resolves to, so your own `./src/client/index.js` is your own file. It is a best effort over the `entry` shapes it can read: a function is computed per build and cannot be read, and a request can reach the client through an alias or a loader. Missing one costs a duplicate entry, not a broken build, and `hot.inject: "plugin"` is the way out. #### Which compilations get the runtime @@ -736,8 +735,8 @@ Nothing has to change, and both pieces are recognized rather than duplicated: Two things do change: -- **A client the middleware does not recognize** — anything other than `webpack-dev-middleware/client`, such as another package's hot client — is not detected, so a second client is added and both connect. `hot.client: false` turns that off. -- **A project with no `HotModuleReplacementPlugin` on purpose** now gets one, which puts the HMR runtime in the bundle and changes its output. `hot.inject: false` turns that off; if you only wanted the endpoint to listen to through [`subscribe`](#custom-events), set `hot.client: false` as well. A project that sets [`hot.client.apply`](#client-options) to `reload` or `nothing` does not: those modes never use the HMR runtime, so it is left out, and a plugin you wrote into the configuration yourself stays. A page whose url asks for `hmr` or `hmr-only` against such a bundle has no runtime to apply an update with, so `hmr` loads the page instead, and `hmr-only` says so and stops. (A runtime that is missing from a project whose own mode wanted it is a mistake, and is reported once instead of reloading.) +- **A client the middleware does not recognize** — anything other than `webpack-dev-middleware/client`, such as another package's hot client — is not detected, so a second client is added and both connect. `hot.inject: "plugin"` turns that off. +- **A project with no `HotModuleReplacementPlugin` on purpose** now gets one, which puts the HMR runtime in the bundle and changes its output. `hot.inject: "client"` turns that off; if you only wanted the endpoint to listen to through [`subscribe`](#custom-events), set `hot.inject: "none"`. A project that sets [`hot.client.apply`](#client-options) to `reload` or `nothing` does not: those modes never use the HMR runtime, so it is left out, and a plugin you wrote into the configuration yourself stays. A page whose url asks for `hmr` or `hmr-only` against such a bundle has no runtime to apply an update with, so `hmr` loads the page instead, and `hmr-only` says so and stops. (A runtime that is missing from a project whose own mode wanted it is a mistake, and is reported once instead of reloading.) A package of your own can stand in for the client by re-exporting it. A query written on that module belongs to it rather than to the client, so leave it where the client looks, before requiring the client: @@ -750,7 +749,7 @@ module.exports = require("webpack-dev-middleware/client"); One caveat: if the compiler was already watching before the middleware was created, the runtime appears from the next build onwards rather than the first one. Create the middleware before starting the watch to avoid it. -No client is added when [`hot.transport`](#hottransport) is a function either — the built-in one speaks Server-Sent Events and WebSocket, and a transport of your own carries whatever protocol you wrote it to carry, so the client that speaks it is yours to add unless [`hot.client.transport`](#client-options) says which of the two yours speaks. `HotModuleReplacementPlugin` is still applied for you. `hot.client: false` silences the reminder. +No client is added when [`hot.transport`](#hottransport) is a function either — the built-in one speaks Server-Sent Events and WebSocket, and a transport of your own carries whatever protocol you wrote it to carry, so the client that speaks it is yours to add unless [`hot.client.transport`](#client-options) says which of the two yours speaks. `HotModuleReplacementPlugin` is still applied for you. `hot.inject: "plugin"` silences the reminder. The [client options](#client-options) below are set on the middleware, next to the rest of the hot configuration, and the injected entry carries them to the @@ -814,12 +813,8 @@ app.use( ); ``` -`hot.client: false` adds no runtime to the page and still applies -`HotModuleReplacementPlugin` (unless `hot.inject` is `false`), for a page that -wires a client of its own and still wants its updates applied. - -`hot.client` is read only when the client is injected. With `hot.client: false`, -or for a client the configuration already has as an entry, the query string on +`hot.client` is read only when the client is injected. With +[`hot.inject`](#hotinject) set to `"plugin"` or `"none"`, or for a client the configuration already has as an entry, the query string on the entry path is the only source — and it works either way: ```js @@ -841,7 +836,7 @@ as well. Each of these is set either on the middleware as `hot.client.` or on the entry's query as `=`, with the same effect and the same spelling in both — except `url`, which the query also reads as `path`, the name it had -there first, and `module`, which only node has. The query also still reads the six names `apply` and `connect` +there first. The query also still reads the six names `apply` and `connect` replaced (`hot`, `liveReload`, `reload`, `autoConnect`, `reconnect`, `timeout`), folded into those two, until the next major release; `hot.client` does not take them. @@ -856,8 +851,7 @@ narrow the mode in force. | 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 `url`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. | -| `module` | `string` | | A module exporting [a client of your own](#a-client-of-your-own), used in place of the built-in one. Node only: it reaches the runtime as code, not on the query. | +| `transport` | `string` | `"sse"` | How the events are carried: `"sse"` or `"ws"`. Defaults to the server [`hot.transport`](#hottransport); override it only together with `url`, since a client asking this endpoint for a protocol it does not serve never connects — the middleware warns when it sees that. Any other string is a module exporting [a client of your own](#a-client-of-your-own). | | `url` | `string\|Object` | `/__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, or to an object saying only the parts that differ — see [a url in parts](#a-url-in-parts). | | `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, and `Infinity` never does; 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. | @@ -940,7 +934,7 @@ import EventSourceClient from "webpack-dev-middleware/client/sse"; import WebSocketClient from "webpack-dev-middleware/client/ws"; ``` -Name it in [`hot.client.module`](#client-options) — a path, or a package +Name it in [`hot.client.transport`](#client-options) — a path, or a package name resolved from the compilation's context — and the injected runtime uses it in place of the built-in one: @@ -949,7 +943,7 @@ app.use( middleware(compiler, { hot: { transport: "ws", - client: { module: require.resolve("./my-client.js") }, + client: { transport: require.resolve("./my-client.js") }, }, }), ); @@ -957,9 +951,8 @@ app.use( It reaches the runtime as `__webpack_dev_server_client__`, which is how webpack-dev-server's `client.webSocketTransport` option has always worked; a -module exporting the class as `default` is unwrapped. The runtime still builds -the url's scheme from [`hot.client.transport`](#client-options), which defaults -to the endpoint's own — set it when your client speaks the other one. +module exporting the class as `default` is unwrapped. The query carries the +endpoint's own transport, which the runtime still builds the url's scheme from. #### Listening alongside the runtime diff --git a/src/hot.js b/src/hot.js index 77e3e32df..7c0574b40 100644 --- a/src/hot.js +++ b/src/hot.js @@ -40,8 +40,7 @@ * and the compilation's name. Setting one here replaces that, which is what a * page reaching the endpoint through a proxy or another origin needs. * @typedef {object} HotClientOptions - * @property {("sse" | "ws")=} transport which transport the runtime speaks, `hot.transport` by default - * @property {string=} module a module exporting a client class of your own, used in place of the built-in one; it is handed the url and speaks `transport` + * @property {("sse" | "ws" | string)=} transport which transport the runtime speaks, `hot.transport` by default; any other string is a module exporting a client class of your own, used in place of the built-in one * @property {(string | PathSpec)=} url where the runtime connects, `hot.path` by default: a path, an absolute url for an endpoint on another origin, or the parts that differ with the rest resolved in the page * @property {string=} name limit the runtime to one compilation's builds, the compilation's own name by default * @property {(boolean | Record)=} overlay show build problems and uncaught runtime errors in an overlay @@ -63,8 +62,8 @@ * @property {boolean=} progress publish compilation progress events to the clients * @property {CorsOption=} cors which origins may reach the endpoint from a page on another one; the local ones by default * @property {(boolean | string)=} token a secret the injected client carries and the endpoint requires; `true` mints one per run, a string uses that one, `false` requires none. Defaults to `false` on both transports; `true` in the next major release - * @property {boolean=} inject add `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to apply it yourself - * @property {(HotClientOptions | false)=} client options handed to the browser runtime through its entry query; `false` adds no runtime to the page; `HotModuleReplacementPlugin` is still applied unless `inject` is `false` + * @property {("all" | "plugin" | "client" | "none")=} inject what is added to the compilation: the runtime and `HotModuleReplacementPlugin` (`"all"`, the default), the plugin alone for a page that wires a client of its own, the runtime alone for a configuration that applies the plugin itself, or nothing + * @property {HotClientOptions=} client options handed to the browser runtime through its entry query */ /** @@ -490,12 +489,14 @@ function createHot(compiler, userOptions, statsOption) { const transport = options.transport || HOT_DEFAULT_TRANSPORT; const { cors } = options; const { statsOptions } = options; - // `client: false` turns it off: the token reaches the browser through the - // entry this middleware adds, so with no client added there is no way to - // hand one over, and requiring it would refuse a client the developer wired + // Off when no client is injected: the token reaches the browser through the + // entry this middleware adds, so with none added there is no way to hand one + // over, and requiring it would refuse a client the developer wired // correctly. Off by default either way — see `HOT_DEFAULT_TOKEN`. + const injectsClient = + !options.inject || options.inject === "all" || options.inject === "client"; const token = resolveToken( - options.client === false ? (options.token ?? false) : options.token, + injectsClient ? options.token : (options.token ?? false), ); const logger = compiler.getInfrastructureLogger("webpack-dev-middleware"); diff --git a/src/options.check.js b/src/options.check.js index 20a5a074c..cfbae472b 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":{"HotClient":{"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"module":{"description":"A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.","type":"string","minLength":1},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}},"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 the upgrades of an HTTP server: hand the server to the middleware's `attach(server)`, or pass each upgrade to `handleUpgrade(req, socket, head)`), 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},"ws":{"description":"Options for the `ws` server behind the `ws` transport: compression, payload limits, `verifyClient`, or a `port` or a `server` of its own to listen on instead of answering the upgrades it is handed. `path`, `noServer` and `clientTracking` are the middleware's and are ignored here.","link":"https://github.com/webpack/webpack-dev-middleware#hot","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 HotModuleReplacementPlugin to the compilation. Turn it off to apply it yourself; `client: false` is what keeps the runtime out of the page.","link":"https://github.com/webpack/webpack-dev-middleware#hot","type":"boolean"},"client":{"description":"Options handed to the browser runtime through its entry query. `false` adds no runtime to the page, for a page that wires a client of its own; `HotModuleReplacementPlugin` is still applied unless `inject` is `false`.","link":"https://github.com/webpack/webpack-dev-middleware#hot","anyOf":[{"enum":[false]},{"$ref":"#/definitions/HotClient"}]}}}]}},"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 schema14 = {"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.","enum":["sse","ws"]},"module":{"description":"A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.","type":"string","minLength":1},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}};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.ws !== undefined){let data36 = data32.ws;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/ws",schemaPath:"#/properties/hot/anyOf/1/properties/ws/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 _errs143 = 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 = _errs143 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs145 = errors;const _errs146 = errors;let valid18 = false;const _errs147 = 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 = _errs147 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs149 = errors;if(errors === _errs149){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 = _errs149 === 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 = _errs146;if(vErrors !== null){if(_errs146){vErrors.length = _errs146;}else {vErrors = null;}}}var valid16 = _errs145 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs152 = errors;const _errs153 = errors;let valid19 = false;const _errs154 = 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 = _errs154 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs156 = errors;const _errs158 = errors;let valid21 = false;const _errs159 = errors;if(errors === _errs159){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 = _errs159 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = 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 = _errs161 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs162 = errors;if(errors === _errs162){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 = [err120];}else {vErrors.push(err120);}errors++;}}else {const err121 = {instancePath:instancePath+"/hot/client/connect/retries",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/retries/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}var valid40 = _errs255 === errors;}else {var valid40 = true;}if(valid40){if(data59.timeout !== undefined){let data61 = data59.timeout;const _errs257 = errors;if(errors === _errs257){if(typeof data61 == "number"){if(data61 < 1 || isNaN(data61)){const err122 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}else {const err123 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}var valid40 = _errs257 === errors;}else {var valid40 = true;}}}}else {const err124 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}var _valid22 = _errs252 === errors;valid39 = valid39 || _valid22;}if(!valid39){const err125 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}else {errors = _errs249;if(vErrors !== null){if(_errs249){vErrors.length = _errs249;}else {vErrors = null;}}}var valid32 = _errs248 === errors;}else {var valid32 = true;}if(valid32){if(data45.pageParamPrefix !== undefined){let data62 = data45.pageParamPrefix;const _errs259 = errors;if(errors === _errs259){if(typeof data62 === "string"){if(data62.length < 1){const err126 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}var valid32 = _errs259 === errors;}else {var valid32 = true;}if(valid32){if(data45.logging !== undefined){let data63 = data45.logging;const _errs261 = errors;const _errs262 = errors;let valid41 = false;const _errs263 = errors;if(!((((((data63 === "none") || (data63 === "error")) || (data63 === "warn")) || (data63 === "info")) || (data63 === "log")) || (data63 === "verbose"))){const err128 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[0].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}var _valid23 = _errs263 === errors;valid41 = valid41 || _valid23;if(!valid41){const _errs264 = errors;if(errors === _errs264){if(data63 && typeof data63 == "object" && !Array.isArray(data63)){const _errs266 = errors;for(const key11 in data63){if(!((key11 === "level") || (key11 === "name"))){const err129 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;break;}}if(_errs266 === errors){if(data63.level !== undefined){let data64 = data63.level;const _errs267 = errors;if(!((((((data64 === "none") || (data64 === "error")) || (data64 === "warn")) || (data64 === "info")) || (data64 === "log")) || (data64 === "verbose"))){const err130 = {instancePath:instancePath+"/hot/client/logging/level",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/level/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[1].properties.level.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var valid42 = _errs267 === errors;}else {var valid42 = true;}if(valid42){if(data63.name !== undefined){let data65 = data63.name;const _errs268 = errors;if(errors === _errs268){if(typeof data65 === "string"){if(data65.length < 1){const err131 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}else {const err132 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}var valid42 = _errs268 === errors;}else {var valid42 = true;}}}}else {const err133 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}var _valid23 = _errs264 === errors;valid41 = valid41 || _valid23;}if(!valid41){const err134 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}else {errors = _errs262;if(vErrors !== null){if(_errs262){vErrors.length = _errs262;}else {vErrors = null;}}}var valid32 = _errs261 === errors;}else {var valid32 = true;}if(valid32){if(data45.dynamicPublicPath !== undefined){const _errs270 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err135 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/definitions/HotClient/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}var valid32 = _errs270 === errors;}else {var valid32 = true;}}}}}}}}}}}}}else {const err136 = {instancePath:instancePath+"/hot/client",schemaPath:"#/definitions/HotClient/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}var _valid16 = _errs201 === errors;valid30 = valid30 || _valid16;}if(!valid30){const err137 = {instancePath:instancePath+"/hot/client",schemaPath:"#/properties/hot/anyOf/1/properties/client/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}else {errors = _errs199;if(vErrors !== null){if(_errs199){vErrors.length = _errs199;}else {vErrors = null;}}}var valid16 = _errs198 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err138 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err139 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}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":{"HotClient":{"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.","anyOf":[{"enum":["sse","ws"]},{"type":"string","minLength":1}]},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}},"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 the upgrades of an HTTP server: hand the server to the middleware's `attach(server)`, or pass each upgrade to `handleUpgrade(req, socket, head)`), 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},"ws":{"description":"Options for the `ws` server behind the `ws` transport: compression, payload limits, `verifyClient`, or a `port` or a `server` of its own to listen on instead of answering the upgrades it is handed. `path`, `noServer` and `clientTracking` are the middleware's and are ignored here.","link":"https://github.com/webpack/webpack-dev-middleware#hot","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":"What is added to the compilation: `all` (the default) adds the runtime — this package's client and overlay — and HotModuleReplacementPlugin; `plugin` adds only the plugin, for a page that wires a client of its own; `client` adds only the runtime, for a configuration that applies the plugin itself; `none` adds nothing.","link":"https://github.com/webpack/webpack-dev-middleware#hotinject","enum":["all","plugin","client","none"]},"client":{"description":"Options handed to the browser runtime through its entry query.","link":"https://github.com/webpack/webpack-dev-middleware#hot","$ref":"#/definitions/HotClient"}}}]}},"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 schema14 = {"description":"Options handed to the browser runtime through its entry query.","type":"object","additionalProperties":false,"properties":{"transport":{"description":"Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.","anyOf":[{"enum":["sse","ws"]},{"type":"string","minLength":1}]},"url":{"description":"Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.","anyOf":[{"type":"string","minLength":1},{"type":"object","additionalProperties":false,"properties":{"protocol":{"description":"The scheme the runtime connects over. Unset, or `auto`, follows the page — and a page served over TLS then gets a secure connection, because a browser refuses a plaintext one from it. Set explicitly, it is honoured as given; the one exception is a `hostname` naming every interface, which is resolved to the page's host and takes the page's scheme with it. Only whether the scheme is the secure one carries over, since the two transports do not share one.","anyOf":[{"enum":["auto"]},{"type":"string","minLength":1}]},"hostname":{"description":"The host the runtime connects to. An address a server listens on to mean every interface (`0.0.0.0`, `::`) is not somewhere a page can connect, so it resolves to the page's own host.","type":"string","minLength":1},"port":{"description":"The port the runtime connects to. `0` means a port the server picked, which resolves to the page's own.","anyOf":[{"type":"number"},{"type":"string","minLength":1}]},"pathname":{"description":"The path the runtime connects to. Defaults to the resolved `hot.path`.","type":"string"},"username":{"description":"The username the runtime authenticates with.","type":"string"},"password":{"description":"The password the runtime authenticates with. Sent only alongside a username, since basic authentication has no empty one.","type":"string"}}}]},"name":{"description":"Limit the runtime to one compilation's builds. Defaults to the compilation's own name.","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}}}]},"pageParamPrefix":{"description":"Prefix of the page-url parameters that override `apply` for a single page, as in `?webpack-dev-middleware-hot=false`.","type":"string","minLength":1},"logging":{"description":"Logger level in the browser, or an object carrying the level and the name every message is labelled with in the console — a package embedding this runtime is the one a developer would report a problem to, so it can say its own name.","anyOf":[{"enum":["none","error","warn","info","log","verbose"]},{"type":"object","additionalProperties":false,"properties":{"level":{"enum":["none","error","warn","info","log","verbose"]},"name":{"description":"What every message is labelled with in the console. Defaults to this package's name.","type":"string","minLength":1}}}]},"dynamicPublicPath":{"description":"Prefix the endpoint path with the bundle's public path at runtime.","type":"boolean"}}};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.ws !== undefined){let data36 = data32.ws;const _errs139 = errors;if(errors === _errs139){if(data36 && typeof data36 == "object" && !Array.isArray(data36)){}else {const err51 = {instancePath:instancePath+"/hot/ws",schemaPath:"#/properties/hot/anyOf/1/properties/ws/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 _errs143 = 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 = _errs143 === errors;}else {var valid16 = true;}if(valid16){if(data32.token !== undefined){let data38 = data32.token;const _errs145 = errors;const _errs146 = errors;let valid18 = false;const _errs147 = 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 = _errs147 === errors;valid18 = valid18 || _valid9;if(!valid18){const _errs149 = errors;if(errors === _errs149){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 = _errs149 === 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 = _errs146;if(vErrors !== null){if(_errs146){vErrors.length = _errs146;}else {vErrors = null;}}}var valid16 = _errs145 === errors;}else {var valid16 = true;}if(valid16){if(data32.cors !== undefined){let data39 = data32.cors;const _errs152 = errors;const _errs153 = errors;let valid19 = false;const _errs154 = 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 = _errs154 === errors;valid19 = valid19 || _valid10;if(!valid19){const _errs156 = errors;const _errs158 = errors;let valid21 = false;const _errs159 = errors;if(errors === _errs159){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 = _errs159 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs161 = 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 = _errs161 === errors;valid21 = valid21 || _valid11;if(!valid21){const _errs162 = errors;if(errors === _errs162){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 = [err120];}else {vErrors.push(err120);}errors++;}}else {const err121 = {instancePath:instancePath+"/hot/client/connect/retries",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/retries/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}var valid40 = _errs253 === errors;}else {var valid40 = true;}if(valid40){if(data58.timeout !== undefined){let data60 = data58.timeout;const _errs255 = errors;if(errors === _errs255){if(typeof data60 == "number"){if(data60 < 1 || isNaN(data60)){const err122 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}else {const err123 = {instancePath:instancePath+"/hot/client/connect/timeout",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/properties/timeout/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}var valid40 = _errs255 === errors;}else {var valid40 = true;}}}}else {const err124 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}var _valid22 = _errs250 === errors;valid39 = valid39 || _valid22;}if(!valid39){const err125 = {instancePath:instancePath+"/hot/client/connect",schemaPath:"#/definitions/HotClient/properties/connect/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}else {errors = _errs247;if(vErrors !== null){if(_errs247){vErrors.length = _errs247;}else {vErrors = null;}}}var valid31 = _errs246 === errors;}else {var valid31 = true;}if(valid31){if(data45.pageParamPrefix !== undefined){let data61 = data45.pageParamPrefix;const _errs257 = errors;if(errors === _errs257){if(typeof data61 === "string"){if(data61.length < 1){const err126 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/hot/client/pageParamPrefix",schemaPath:"#/definitions/HotClient/properties/pageParamPrefix/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}var valid31 = _errs257 === errors;}else {var valid31 = true;}if(valid31){if(data45.logging !== undefined){let data62 = data45.logging;const _errs259 = errors;const _errs260 = errors;let valid41 = false;const _errs261 = errors;if(!((((((data62 === "none") || (data62 === "error")) || (data62 === "warn")) || (data62 === "info")) || (data62 === "log")) || (data62 === "verbose"))){const err128 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/0/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[0].enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}var _valid23 = _errs261 === errors;valid41 = valid41 || _valid23;if(!valid41){const _errs262 = errors;if(errors === _errs262){if(data62 && typeof data62 == "object" && !Array.isArray(data62)){const _errs264 = errors;for(const key11 in data62){if(!((key11 === "level") || (key11 === "name"))){const err129 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;break;}}if(_errs264 === errors){if(data62.level !== undefined){let data63 = data62.level;const _errs265 = errors;if(!((((((data63 === "none") || (data63 === "error")) || (data63 === "warn")) || (data63 === "info")) || (data63 === "log")) || (data63 === "verbose"))){const err130 = {instancePath:instancePath+"/hot/client/logging/level",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/level/enum",keyword:"enum",params:{allowedValues: schema14.properties.logging.anyOf[1].properties.level.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var valid42 = _errs265 === errors;}else {var valid42 = true;}if(valid42){if(data62.name !== undefined){let data64 = data62.name;const _errs266 = errors;if(errors === _errs266){if(typeof data64 === "string"){if(data64.length < 1){const err131 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}else {const err132 = {instancePath:instancePath+"/hot/client/logging/name",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}var valid42 = _errs266 === errors;}else {var valid42 = true;}}}}else {const err133 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}var _valid23 = _errs262 === errors;valid41 = valid41 || _valid23;}if(!valid41){const err134 = {instancePath:instancePath+"/hot/client/logging",schemaPath:"#/definitions/HotClient/properties/logging/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}else {errors = _errs260;if(vErrors !== null){if(_errs260){vErrors.length = _errs260;}else {vErrors = null;}}}var valid31 = _errs259 === errors;}else {var valid31 = true;}if(valid31){if(data45.dynamicPublicPath !== undefined){const _errs268 = errors;if(typeof data45.dynamicPublicPath !== "boolean"){const err135 = {instancePath:instancePath+"/hot/client/dynamicPublicPath",schemaPath:"#/definitions/HotClient/properties/dynamicPublicPath/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}var valid31 = _errs268 === errors;}else {var valid31 = true;}}}}}}}}}}}}else {const err136 = {instancePath:instancePath+"/hot/client",schemaPath:"#/definitions/HotClient/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}var valid16 = _errs197 === errors;}else {var valid16 = true;}}}}}}}}}}}}else {const err137 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}var _valid7 = _errs128 === errors;valid15 = valid15 || _valid7;}if(!valid15){const err138 = {instancePath:instancePath+"/hot",schemaPath:"#/properties/hot/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}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 eee651f02..0bf78c07c 100644 --- a/src/options.json +++ b/src/options.json @@ -6,13 +6,16 @@ "additionalProperties": false, "properties": { "transport": { - "description": "Which transport the runtime speaks: `sse` or `ws`. Defaults to the resolved `hot.transport`.", - "enum": ["sse", "ws"] - }, - "module": { - "description": "A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks `transport`.", - "type": "string", - "minLength": 1 + "description": "Which transport the runtime speaks: `sse` or `ws`, or a module that exports a client class of your own, which is used in place of the built-in one. Defaults to the resolved `hot.transport`.", + "anyOf": [ + { + "enum": ["sse", "ws"] + }, + { + "type": "string", + "minLength": 1 + } + ] }, "url": { "description": "Where the runtime connects: a path, an absolute url for an endpoint reached on another origin or through a proxy, or the parts of one that differ, with the rest resolved in the page, which is the only place the rest is known. Defaults to the resolved `hot.path`.", @@ -520,21 +523,14 @@ "additionalProperties": true }, "inject": { - "description": "Add HotModuleReplacementPlugin to the compilation. Turn it off to apply it yourself; `client: false` is what keeps the runtime out of the page.", - "link": "https://github.com/webpack/webpack-dev-middleware#hot", - "type": "boolean" + "description": "What is added to the compilation: `all` (the default) adds the runtime — this package's client and overlay — and HotModuleReplacementPlugin; `plugin` adds only the plugin, for a page that wires a client of its own; `client` adds only the runtime, for a configuration that applies the plugin itself; `none` adds nothing.", + "link": "https://github.com/webpack/webpack-dev-middleware#hotinject", + "enum": ["all", "plugin", "client", "none"] }, "client": { - "description": "Options handed to the browser runtime through its entry query. `false` adds no runtime to the page, for a page that wires a client of its own; `HotModuleReplacementPlugin` is still applied unless `inject` is `false`.", + "description": "Options handed to the browser runtime through its entry query.", "link": "https://github.com/webpack/webpack-dev-middleware#hot", - "anyOf": [ - { - "enum": [false] - }, - { - "$ref": "#/definitions/HotClient" - } - ] + "$ref": "#/definitions/HotClient" } } } diff --git a/src/utils.js b/src/utils.js index ce4726aa5..cb97f3a9c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1361,7 +1361,7 @@ function isClientRequest(request, context) { * * Best effort by design: `entry` can be a function, and a request can reach the * client through an alias or a loader. Missing one of those costs a duplicate - * entry, not a broken build, and `hot.client: false` is the way out. + * entry, not a broken build, and `hot.inject: "plugin"` is the way out. * @param {Compiler} compiler compiler * @returns {string[] | null} the names that need one, or null when they all do */ @@ -1472,11 +1472,6 @@ function clientQuery(client, resolvedPath) { } for (let [key, value] of Object.entries(client)) { - // A module is handed to the runtime as code, not as text on its query. - if (key === "module") { - continue; - } - // A url given in parts replaces the resolved path in the query outright, // so the part it leaves out has to be carried over rather than left to the // client's own default — which is the same path only until someone sets @@ -1541,10 +1536,17 @@ function clientQuery(client, resolvedPath) { } /** - * Where a client module of someone else's lives. Looked up from the + * The two transports the bundled client speaks. Anything else given as + * `hot.client.transport` names a module of someone else's that speaks for it. + * @type {readonly string[]} + */ +const BUILT_IN_CLIENT_TRANSPORTS = ["sse", "ws"]; + +/** + * Where a client transport of someone else's lives. Looked up from the * project first, where a bare package name is installed, and then from here, * so an absolute path or a package this one can see works as well. - * @param {string} request what `hot.client.module` named + * @param {string} request what `hot.client.transport` named * @param {string} context the compilation's context * @returns {string} the resolved module */ @@ -1556,7 +1558,7 @@ function resolveClientTransport(request, context) { return require.resolve(request); } catch { throw new Error( - `'hot.client.module' must be a module that exports a client class, but '${request}' could not be resolved from '${context}'.`, + `'hot.client.transport' must be 'sse', 'ws', or a module that exports a client class, but '${request}' could not be resolved from '${context}'.`, ); } } @@ -1574,20 +1576,24 @@ function resolveClientTransport(request, context) { /** * @param {Compiler[]} compilers compilers to modify - * @param {{ path: string, transport: NonNullable, inject?: boolean, client?: HotClientOptions | false, token?: string | false }} options resolved hot options + * @param {{ path: string, transport: NonNullable, inject?: HotOptions["inject"], client?: HotClientOptions, token?: string | false }} options resolved hot options * @param {Logger} logger logger */ function injectHotClient(compilers, options, logger) { - // The two halves are switched separately: `client: false` keeps the runtime - // out of the page, `inject: false` keeps `HotModuleReplacementPlugin` out of - // the compilation, and both together add nothing at all. - const addsPlugin = options.inject !== false; - // `client: false` is no runtime in the page while the compilation still - // gets everything else — the plugin, for a page that wires a client of its - // own and still wants its updates. - const addsClient = options.client !== false; + // What goes into the compilation: the runtime in the page, which is this + // package's client and its overlay, and `HotModuleReplacementPlugin`, which + // is webpack's hot logic. Both by default; either one, for a page that wires + // the other itself; or nothing. + const inject = options.inject || "all"; + + if (inject === "none") { + return; + } + + const addsClient = inject === "all" || inject === "client"; + const addsPlugin = inject === "all" || inject === "plugin"; /** @type {HotClientOptions | undefined} */ - const clientOptions = options.client || undefined; + const clientOptions = options.client; let warned = false; // A token only reaches the browser on the entry added below, so a required @@ -1599,17 +1605,18 @@ function injectHotClient(compilers, options, logger) { // runtime and the one written by hand is the one that counts. const client = clientQuery(clientOptions, options.path); - // A client of someone else's: a module exporting a class with the same - // shape as the built-in two, handed to the runtime through + // A client transport of someone else's: a module exporting a class with the + // same shape as the built-in two, handed to the runtime through // `__webpack_dev_server_client__`, which it uses in place of its own. What - // goes on the query is still a wire, since the runtime works out the - // connection url and how to hold it open from that: the one given, else the - // endpoint's, else a WebSocket — the wire webpack-dev-server has always - // built a custom client's url for. + // goes on the query is the wire the endpoint serves, since the runtime still + // works out the connection url and how to hold it open from that. /** @type {string | undefined} */ - const clientModule = clientOptions ? clientOptions.module : undefined; + const customClientTransport = + client.transport && !BUILT_IN_CLIENT_TRANSPORTS.includes(client.transport) + ? client.transport + : undefined; - if (clientModule && !client.transport) { + if (customClientTransport) { client.transport = typeof options.transport === "string" ? options.transport : "ws"; } @@ -1632,7 +1639,7 @@ function injectHotClient(compilers, options, logger) { // itself what it speaks. if ( typeof options.transport === "string" && - !clientModule && + !customClientTransport && client.transport && client.transport !== options.transport && !client.url @@ -1651,10 +1658,10 @@ function injectHotClient(compilers, options, logger) { // `hot` to apply it has no plugin of its own to fall back to. const isWeb = addsClient && isWebTarget(compiler); - if (isWeb && clientModule) { + if (isWeb && customClientTransport) { new webpack.ProvidePlugin({ __webpack_dev_server_client__: resolveClientTransport( - clientModule, + customClientTransport, compiler.context, ), }).apply(compiler); @@ -1667,7 +1674,7 @@ function injectHotClient(compilers, options, logger) { if (!warned) { warned = true; logger.warn( - "'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it — 'HotModuleReplacementPlugin' is still applied for you, and 'hot.client: false' silences this.", + "'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it — 'HotModuleReplacementPlugin' is still applied for you, and 'hot.inject: \"plugin\"' silences this.", ); } } else { @@ -1712,14 +1719,13 @@ function injectHotClient(compilers, options, logger) { // Applying a build to the page by reloading it, or not at all, has no use // for the HMR runtime, which would only add to the bundle. It is left out - // when `hot.client.apply` says so, and `hot.inject: false` leaves it out - // whatever the mode; a plugin written into the configuration is the - // developer's and stays. + // when `hot.client.apply` says so, and whatever the mode when `hot.inject` + // leaves the plugin to the developer; a plugin written into the + // configuration is the developer's and stays. const needsHmr = addsPlugin && (!clientOptions || - (clientOptions.apply !== "reload" && - clientOptions.apply !== "nothing")); + (clientOptions.apply !== "reload" && clientOptions.apply !== "nothing")); const hmrPluginExists = compiler.options.plugins.some( (plugin) => diff --git a/test/__snapshots__/validation-options.test.js.snap.webpack5 b/test/__snapshots__/validation-options.test.js.snap.webpack5 index 7019ccdce..f4eda7566 100644 --- a/test/__snapshots__/validation-options.test.js.snap.webpack5 +++ b/test/__snapshots__/validation-options.test.js.snap.webpack5 @@ -78,76 +78,70 @@ exports[`validation should throw an error on the "headers" option with "true" va exports[`validation should throw an error on the "hot" option with "{"client":{"autoConnect":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'autoConnect'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"hot":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'hot'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"liveReload":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'liveReload'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; -exports[`validation should throw an error on the "hot" option with "{"client":{"module":""}}" value 1`] = ` -"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - - options.hot.client.module should be a non-empty string. - -> A module exporting a client class of your own, used in place of the built-in one: a path, resolved from the compilation's context, or a package name. It is constructed with the url and speaks \`transport\`." -`; - exports[`validation should throw an error on the "hot" option with "{"client":{"path":"/__hmr"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'path'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"reconnect":3}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'reconnect'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"reload":false}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'reload'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"timeout":5000}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'timeout'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; exports[`validation should throw an error on the "hot" option with "{"client":{"token":"a-token"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.client has an unknown property 'token'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; -exports[`validation should throw an error on the "hot" option with "{"client":{"transport":"./my-client.js"}}" value 1`] = ` +exports[`validation should throw an error on the "hot" option with "{"client":{"urlPrefix":"my-server"}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - - options.hot.client.transport should be one of these: - "sse" | "ws" - -> Which transport the runtime speaks: \`sse\` or \`ws\`. Defaults to the resolved \`hot.transport\`." + - options.hot.client has an unknown property 'urlPrefix'. These properties are valid: + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + -> Options handed to the browser runtime through its entry query." `; -exports[`validation should throw an error on the "hot" option with "{"client":{"urlPrefix":"my-server"}}" value 1`] = ` +exports[`validation should throw an error on the "hot" option with "{"client":false}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - - options.hot.client has an unknown property 'urlPrefix'. These properties are valid: - object { transport?, module?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } + - options.hot.client should be an object: + object { transport?, url?, name?, overlay?, progress?, apply?, connect?, pageParamPrefix?, logging?, dynamicPublicPath? } -> Options handed to the browser runtime through its entry query." `; @@ -163,6 +157,22 @@ exports[`validation should throw an error on the "hot" option with "{"heartbeat" -> Heartbeat interval (in milliseconds) used to keep the connection alive." `; +exports[`validation should throw an error on the "hot" option with "{"inject":false}" value 1`] = ` +"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. + - options.hot.inject should be one of these: + "all" | "plugin" | "client" | "none" + -> What is added to the compilation: \`all\` (the default) adds the runtime — this package's client and overlay — and HotModuleReplacementPlugin; \`plugin\` adds only the plugin, for a page that wires a client of its own; \`client\` adds only the runtime, for a configuration that applies the plugin itself; \`none\` adds nothing. + -> Read more at https://github.com/webpack/webpack-dev-middleware#hotinject" +`; + +exports[`validation should throw an error on the "hot" option with "{"inject":true}" value 1`] = ` +"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. + - options.hot.inject should be one of these: + "all" | "plugin" | "client" | "none" + -> What is added to the compilation: \`all\` (the default) adds the runtime — this package's client and overlay — and HotModuleReplacementPlugin; \`plugin\` adds only the plugin, for a page that wires a client of its own; \`client\` adds only the runtime, for a configuration that applies the plugin itself; \`none\` adds nothing. + -> Read more at https://github.com/webpack/webpack-dev-middleware#hotinject" +`; + exports[`validation should throw an error on the "hot" option with "{"path":""}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot.path should match pattern "^/[^?#]*$". @@ -190,7 +200,7 @@ exports[`validation should throw an error on the "hot" option with "{"path":"hmr exports[`validation should throw an error on the "hot" option with "{"server":{}}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot has an unknown property 'server'. These properties are valid: - object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? }" + object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? }" `; exports[`validation should throw an error on the "hot" option with "{"statsOptions":"errors-only"}" value 1`] = ` @@ -210,7 +220,7 @@ exports[`validation should throw an error on the "hot" option with "{"statsOptio exports[`validation should throw an error on the "hot" option with "{"transport":"websocket"}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot should be one of these: - boolean | object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? } + boolean | object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? } -> Enable hot module replacement over a Server-Sent Events or WebSocket endpoint. -> Read more at https://github.com/webpack/webpack-dev-middleware#hot Details: @@ -226,7 +236,7 @@ exports[`validation should throw an error on the "hot" option with "{"transport" exports[`validation should throw an error on the "hot" option with "{"transport":true}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot should be one of these: - boolean | object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? } + boolean | object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? } -> Enable hot module replacement over a Server-Sent Events or WebSocket endpoint. -> Read more at https://github.com/webpack/webpack-dev-middleware#hot Details: @@ -242,31 +252,31 @@ exports[`validation should throw an error on the "hot" option with "{"transport" exports[`validation should throw an error on the "hot" option with "{"unknown":true}" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot has an unknown property 'unknown'. These properties are valid: - object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? }" + object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? }" `; exports[`validation should throw an error on the "hot" option with "0" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot should be one of these: - boolean | object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? } + boolean | object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? } -> Enable hot module replacement over a Server-Sent Events or WebSocket endpoint. -> Read more at https://github.com/webpack/webpack-dev-middleware#hot Details: * options.hot should be a boolean. * options.hot should be an object: - object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? }" + object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? }" `; exports[`validation should throw an error on the "hot" option with "foo" value 1`] = ` "Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema. - options.hot should be one of these: - boolean | object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? } + boolean | object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? } -> Enable hot module replacement over a Server-Sent Events or WebSocket endpoint. -> Read more at https://github.com/webpack/webpack-dev-middleware#hot Details: * options.hot should be a boolean. * options.hot should be an object: - object { transport?, path?, heartbeat?, ws?, progress?, token?, cors?, statsOptions?, inject?, client? }" + object { transport?, path?: string, heartbeat?: number, ws?, progress?: boolean, token?, cors?, statsOptions?, inject?, client? }" `; exports[`validation should throw an error on the "index" option with "{}" value 1`] = ` diff --git a/test/e2e/inject.test.js b/test/e2e/inject.test.js index 245d66df3..73b598a70 100644 --- a/test/e2e/inject.test.js +++ b/test/e2e/inject.test.js @@ -62,11 +62,11 @@ describe("hot with nothing but the middleware (browser)", () => { ); }); - it("leaves the compilation alone when injection and the client are off", async () => { + it("leaves the compilation alone when inject is none", async () => { hotApp = await createHotApp({ bare: true, code: acceptedApp("v1"), - hot: { inject: false, client: false }, + hot: { inject: "none" }, }); ({ page, browser } = await runBrowser()); const console_ = collectConsole(page); @@ -83,12 +83,12 @@ describe("hot with nothing but the middleware (browser)", () => { expect(console_.messages.join("\n")).not.toContain("connected"); }); - it("adds the client but leaves the plugin to you when injection is off", async () => { + it("adds the client and leaves the plugin to you with inject client", async () => { hotApp = await createHotApp({ code: acceptedApp("v1"), // The configuration's own plugin and client entry, as a project that // applies the plugin itself has them. - hot: { inject: false }, + hot: { inject: "client" }, }); ({ page, browser } = await runBrowser()); const console_ = collectConsole(page); @@ -108,14 +108,14 @@ describe("hot with nothing but the middleware (browser)", () => { expect(await page.evaluate(() => globalThis.notReloaded)).toBe(true); }); - it("adds the client with no plugin when injection is off and there is none", async () => { + it("adds only the client with inject client and no plugin of your own", async () => { hotApp = await createHotApp({ bare: true, code: ` document.getElementById("app").textContent = "v1"; globalThis.hasHotRuntime = Boolean(module.hot); `, - hot: { inject: false }, + hot: { inject: "client" }, }); ({ page, browser } = await runBrowser()); const console_ = collectConsole(page); @@ -123,8 +123,8 @@ describe("hot with nothing but the middleware (browser)", () => { await page.goto(hotApp.url); await waitForAppText(page, "v1"); - // The client is the middleware's half and is still added; the HMR - // runtime is not in the bundle. + // The runtime is added and connects; webpack's hot logic is not in the + // bundle. await console_.waitFor("connected"); expect(await page.evaluate(() => globalThis.hasHotRuntime)).toBe(false); diff --git a/test/e2e/messages.test.js b/test/e2e/messages.test.js index 2e23dbc8b..3ea8e00c0 100644 --- a/test/e2e/messages.test.js +++ b/test/e2e/messages.test.js @@ -322,7 +322,7 @@ describe("a module standing in for the client (browser)", () => { hotApp = await createHotApp({ bare: true, - hot: { inject: false, client: false, path: "/stand-in-hmr" }, + hot: { inject: "none", path: "/stand-in-hmr" }, files: { "stand-in.js": ` globalThis.__webpack_dev_middleware_client_query__ = "?path=/stand-in-hmr"; diff --git a/test/e2e/process-update.test.js b/test/e2e/process-update.test.js index 81d9e42e4..577ce1eef 100644 --- a/test/e2e/process-update.test.js +++ b/test/e2e/process-update.test.js @@ -304,7 +304,7 @@ describe("update processing (browser)", () => { hmrPlugin: false, // `hot` applies the plugin itself now, so the only way to reach a // compilation without a hot runtime is to turn that off. - hot: { inject: false }, + hot: { inject: "none" }, }); ({ page, browser } = await runBrowser()); const console_ = collectConsole(page); diff --git a/test/e2e/transport.test.js b/test/e2e/transport.test.js index 1fe93aecf..07fb81fc6 100644 --- a/test/e2e/transport.test.js +++ b/test/e2e/transport.test.js @@ -206,7 +206,7 @@ describe("a client transport of someone else's (browser)", () => { bare: true, hot: { client: { - module: path.resolve( + transport: path.resolve( __dirname, "../fixtures/custom-client-transport.js", ), diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 1ca576191..26af4cec4 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -757,33 +757,13 @@ describe("what injectHotClient leaves alone", () => { return created; } - // One switch for each half: `inject` the compiler's, `client` the page's. - it("adds the client but not the plugin when inject is off", () => { + it("adds nothing at all when inject is none", () => { const instance = compiler(); const before = entryCount(instance); injectHotClient( [instance], - { path: "/__webpack_hmr", transport: "sse", inject: false }, - logger, - ); - - expect(entryCount(instance)).toBe(before + 1); - expect(hasHmrPlugin(instance)).toBe(false); - }); - - it("adds nothing at all when inject and the client are both off", () => { - const instance = compiler(); - const before = entryCount(instance); - - injectHotClient( - [instance], - { - path: "/__webpack_hmr", - transport: "sse", - inject: false, - client: false, - }, + { path: "/__webpack_hmr", transport: "sse", inject: "none" }, logger, ); @@ -791,22 +771,6 @@ describe("what injectHotClient leaves alone", () => { expect(hasHmrPlugin(instance)).toBe(false); }); - it("does not ask for a plugin of your own to be dropped when inject is off", () => { - const instance = compiler({ - plugins: [new webpack.HotModuleReplacementPlugin()], - }); - /** @type {string[]} */ - const said = []; - - injectHotClient( - [instance], - { path: "/__webpack_hmr", transport: "sse", inject: false }, - { warn: (message) => said.push(message), log: () => {} }, - ); - - expect(said).toStrictEqual([]); - }); - it("gives a compilation a browser does not run the plugin, not the client", () => { // A server bundle hot-reloads itself through `module.hot` too — with // `webpack/hot/poll` or `webpack/hot/signal` — and webpack-dev-server has @@ -841,7 +805,7 @@ describe("what injectHotClient leaves alone", () => { expect(hasHmrPlugin(instance)).toBe(false); }); - it("adds no client but still the plugin when the client is off", () => { + it("adds no client but still the plugin when inject is plugin", () => { // A page that wires a runtime of its own still wants its updates applied, // which takes the plugin. const instance = compiler(); @@ -849,7 +813,7 @@ describe("what injectHotClient leaves alone", () => { injectHotClient( [instance], - { path: "/__webpack_hmr", transport: "sse", client: false }, + { path: "/__webpack_hmr", transport: "sse", inject: "plugin" }, logger, ); @@ -857,14 +821,44 @@ describe("what injectHotClient leaves alone", () => { expect(hasHmrPlugin(instance)).toBe(true); }); - it("does not warn about a token it never had a client to hand to, when the client is off", () => { + it("does not warn about a token it never had a client to hand to, when inject is plugin", () => { /** @type {string[]} */ const warned = []; const instance = compiler(); injectHotClient( [instance], - { path: "/__webpack_hmr", transport: "sse", client: false }, + { path: "/__webpack_hmr", transport: "sse", inject: "plugin" }, + { warn: (message) => warned.push(message), log: () => {} }, + ); + + expect(warned).toStrictEqual([]); + }); + + it("adds the client but not the plugin when inject is client", () => { + const instance = compiler(); + const before = entryCount(instance); + + injectHotClient( + [instance], + { path: "/__webpack_hmr", transport: "sse", inject: "client" }, + logger, + ); + + expect(entryCount(instance)).toBe(before + 1); + expect(hasHmrPlugin(instance)).toBe(false); + }); + + it("does not ask for a plugin of your own to be dropped when inject is client", () => { + /** @type {string[]} */ + const warned = []; + const instance = compiler({ + plugins: [new webpack.HotModuleReplacementPlugin()], + }); + + injectHotClient( + [instance], + { path: "/__webpack_hmr", transport: "sse", inject: "client" }, { warn: (message) => warned.push(message), log: () => {} }, ); @@ -1068,10 +1062,11 @@ describe("the entry query the client is given", () => { }); }); -// `hot.client.module`: a client class of someone else's, which the runtime -// uses in place of the built-in one through `__webpack_dev_server_client__`. -// webpack-dev-server's `client.webSocketTransport` has always worked this way. -describe("a client module of someone else's", () => { +// `hot.client.transport` naming a module rather than a wire: a client class of +// someone else's, which the runtime uses in place of the built-in one through +// `__webpack_dev_server_client__`. webpack-dev-server's +// `client.webSocketTransport` has always worked this way. +describe("a client transport of someone else's", () => { const CUSTOM = path.resolve(__dirname, "fixtures/custom-client-transport.js"); /** @type {EXPECTED_OBJECT[]} */ @@ -1158,7 +1153,7 @@ describe("a client module of someone else's", () => { const { provided } = inject({ path: "/__webpack_hmr", transport: "ws", - client: { module: CUSTOM }, + client: { transport: CUSTOM }, }); expect(provided).toStrictEqual([{ __webpack_dev_server_client__: CUSTOM }]); @@ -1169,7 +1164,7 @@ describe("a client module of someone else's", () => { { path: "/__webpack_hmr", transport: "ws", - client: { module: "./custom-client-transport.js" }, + client: { transport: "./custom-client-transport.js" }, }, { context: path.resolve(__dirname, "fixtures") }, ); @@ -1183,7 +1178,7 @@ describe("a client module of someone else's", () => { const { queries } = inject({ path: "/__webpack_hmr", transport: "sse", - client: { module: CUSTOM }, + client: { transport: CUSTOM }, }); expect(queries).toStrictEqual([ @@ -1195,7 +1190,7 @@ describe("a client module of someone else's", () => { inject({ path: "/__webpack_hmr", transport: "ws", - client: { module: CUSTOM }, + client: { transport: CUSTOM }, }); expect(warnings).toStrictEqual([]); @@ -1212,7 +1207,7 @@ describe("a client module of someone else's", () => { publish: () => {}, publishTo: () => {}, }), - client: { module: CUSTOM }, + client: { transport: CUSTOM }, }); expect(queries).toStrictEqual([{ url: "/ws", transport: "ws" }]); @@ -1235,7 +1230,7 @@ describe("a client module of someone else's", () => { { path: "/__webpack_hmr", transport: "ws", - client: { module: CUSTOM }, + client: { transport: CUSTOM }, }, { target: "node" }, ); @@ -1248,33 +1243,19 @@ describe("a client module of someone else's", () => { inject({ path: "/__webpack_hmr", transport: "ws", - client: { module: "./no-such-client.js" }, + client: { transport: "./no-such-client.js" }, }), ).toThrow( - /'hot\.client\.module'.*'\.\/no-such-client\.js' could not be resolved/, + /'hot\.client\.transport'.*'\.\/no-such-client\.js' could not be resolved/, ); }); - it("is accepted by the schema, apart from the wire", () => { + it("is accepted by the schema", () => { const client = schema.definitions.HotClient; - expect(client.properties.module).toMatchObject({ - type: "string", - minLength: 1, - }); - // A module is no longer taken where a wire is asked for. - expect(client.properties.transport.enum).toStrictEqual(["sse", "ws"]); - }); - - it("speaks the wire it is told to, rather than the endpoint's", () => { - const { queries } = inject({ - path: "/__webpack_hmr", - transport: "sse", - client: { module: CUSTOM, transport: "ws", url: "wss://elsewhere/ws" }, - }); - - expect(queries).toStrictEqual([ - { url: "wss://elsewhere/ws", transport: "ws" }, + expect(client.properties.transport.anyOf).toStrictEqual([ + { enum: ["sse", "ws"] }, + { type: "string", minLength: 1 }, ]); }); }); @@ -1374,10 +1355,6 @@ describe("node and the query take the same names", () => { ]), ]; - // The one name only node has: `module` reaches the runtime as code, through - // `__webpack_dev_server_client__`, not as text on its query. - const nodeOnly = ["module"]; - /** @type {string[]} every name `hot.client` accepts */ const takenInNode = Object.keys(schema.definitions.HotClient.properties); @@ -1399,9 +1376,7 @@ describe("node and the query take the same names", () => { it("is one set of names, with nothing on one side only", () => { expect( readByClient.filter((name) => !queryOnly.includes(name)).toSorted(), - ).toStrictEqual( - takenInNode.filter((name) => !nodeOnly.includes(name)).toSorted(), - ); + ).toStrictEqual(takenInNode.toSorted()); expect( takenInNode.filter((name) => queryOnly.includes(name)), ).toStrictEqual([]); diff --git a/test/universal-target.test.js b/test/universal-target.test.js index cf87fa3cc..3be749739 100644 --- a/test/universal-target.test.js +++ b/test/universal-target.test.js @@ -114,7 +114,7 @@ const CLIENTS = [ ], [ "the client through a re-export of it", - () => ({ inject: false, client: false }), + () => ({ inject: "none" }), (dir) => [`${path.join(dir, "stand-in.js")}?path=/hmr`, "./app.js"], /stand-in\.js\?/, ], diff --git a/test/validation-options.test.js b/test/validation-options.test.js index c8fc37df1..b7107dc52 100644 --- a/test/validation-options.test.js +++ b/test/validation-options.test.js @@ -107,13 +107,14 @@ describe("validation", () => { }), }, { statsOptions: { all: false } }, - { client: false }, + { inject: "all" }, + { inject: "plugin" }, + { inject: "client" }, + { inject: "none" }, { client: { url: "/__hmr" } }, { client: { url: "wss://dev.example.com/__hmr" } }, { client: { url: { port: 8080 } } }, { client: { pageParamPrefix: "my-server" } }, - { client: { module: "./custom-client-transport.js", transport: "ws" } }, - { inject: false, client: false }, { client: { apply: "reload", connect: { retries: 3, timeout: 5000 } } }, ], failure: [ @@ -147,9 +148,10 @@ describe("validation", () => { { client: { autoConnect: false } }, { client: { reconnect: 3 } }, { client: { timeout: 5000 } }, - // A module goes in `module`, not where a wire is asked for. - { client: { transport: "./my-client.js" } }, - { client: { module: "" } }, + // One enum for what is added, rather than two switches. + { inject: false }, + { inject: true }, + { client: false }, ], }, }; diff --git a/types/hot.d.ts b/types/hot.d.ts index 38250458f..cfa1adb76 100644 --- a/types/hot.d.ts +++ b/types/hot.d.ts @@ -212,13 +212,9 @@ type PathSpec = { */ type HotClientOptions = { /** - * which transport the runtime speaks, `hot.transport` by default + * which transport the runtime speaks, `hot.transport` by default; any other string is a module exporting a client class of your own, used in place of the built-in one */ - transport?: ("sse" | "ws") | undefined; - /** - * a module exporting a client class of your own, used in place of the built-in one; it is handed the url and speaks `transport` - */ - module?: string | undefined; + transport?: ("sse" | "ws" | string) | undefined; /** * where the runtime connects, `hot.path` by default: a path, an absolute url for an endpoint on another origin, or the parts that differ with the rest resolved in the page */ @@ -306,13 +302,13 @@ type HotOptions = { */ token?: (boolean | string) | undefined; /** - * add `HotModuleReplacementPlugin` to the compilation (default `true`); turn it off to apply it yourself + * what is added to the compilation: the runtime and `HotModuleReplacementPlugin` (`"all"`, the default), the plugin alone for a page that wires a client of its own, the runtime alone for a configuration that applies the plugin itself, or nothing */ - inject?: boolean | undefined; + inject?: ("all" | "plugin" | "client" | "none") | undefined; /** - * options handed to the browser runtime through its entry query; `false` adds no runtime to the page; `HotModuleReplacementPlugin` is still applied unless `inject` is `false` + * options handed to the browser runtime through its entry query */ - client?: (HotClientOptions | false) | undefined; + client?: HotClientOptions | undefined; }; /** * What an origin is matched against: one origin, several, a pattern, or a diff --git a/types/utils.d.ts b/types/utils.d.ts index f3436d7a1..9bd2aa1c2 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -346,7 +346,7 @@ export function initState< */ /** * @param {Compiler[]} compilers compilers to modify - * @param {{ path: string, transport: NonNullable, inject?: boolean, client?: HotClientOptions | false, token?: string | false }} options resolved hot options + * @param {{ path: string, transport: NonNullable, inject?: HotOptions["inject"], client?: HotClientOptions, token?: string | false }} options resolved hot options * @param {Logger} logger logger */ export function injectHotClient( @@ -354,8 +354,8 @@ export function injectHotClient( options: { path: string; transport: NonNullable; - inject?: boolean; - client?: HotClientOptions | false; + inject?: HotOptions["inject"]; + client?: HotClientOptions; token?: string | false; }, logger: Logger, From b0f931f9fef482ce578e69569cd84fde85fdc5e1 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:29:01 +0000 Subject: [PATCH 3/4] style: format the inject condition Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- src/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index cb97f3a9c..b1c72e386 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1725,7 +1725,8 @@ function injectHotClient(compilers, options, logger) { const needsHmr = addsPlugin && (!clientOptions || - (clientOptions.apply !== "reload" && clientOptions.apply !== "nothing")); + (clientOptions.apply !== "reload" && + clientOptions.apply !== "nothing")); const hmrPluginExists = compiler.options.plugins.some( (plugin) => From ca039310a69778063fb58def2b8b7a5ea1780c61 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:42:33 +0000 Subject: [PATCH 4/4] fix: apply the plugin with inject "plugin" whatever hot.client.apply says `apply` is the injected client's mode, so it only leaves the plugin out when a client is added. The transport-function warning no longer says the plugin is applied under "client", and the README says the "plugin" and "none" modes turn off only the default token requirement. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --- README.md | 2 +- src/utils.js | 6 ++++-- test/inject-client.test.js | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3e34f8fa..2e0583fa9 100644 --- a/README.md +++ b/README.md @@ -615,7 +615,7 @@ client entry was added to hand one over, so every client will be refused. > > **What a token does not protect.** The client reads it from its entry query, so it is a string in the bundle. Anything that can already read your bundle cross-origin can read the token out of it — and over plain `http` to a non-`localhost` address, nothing stops that unless your server sends `Cross-Origin-Resource-Policy`. The token hardens every case where the bundle is not readable; where it is, your source has already gone and the stream is the smaller loss. Closing that needs the response header and a `Host` allowlist, which are [your server's](#security) to set. -**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.inject: "plugin"` or `"none"` turns the requirement off — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. +**Wiring the client yourself.** The token travels in the entry this middleware adds, so `hot.inject: "plugin"` or `"none"` turns off the default requirement — there would be no way to hand one over, and requiring it would refuse a client you wired correctly. A `hot.token` you set to `true` or a string is still required: hand it to your client yourself, as below, or set `hot.token: false`. A configuration that already lists the client as an entry is the other half of that: it is built before the middleware exists, so it cannot carry a token minted per run. Give it one of your own instead, which both sides can know in advance: diff --git a/src/utils.js b/src/utils.js index b1c72e386..6ef2c6c80 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1674,7 +1674,7 @@ function injectHotClient(compilers, options, logger) { if (!warned) { warned = true; logger.warn( - "'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it — 'HotModuleReplacementPlugin' is still applied for you, and 'hot.inject: \"plugin\"' silences this.", + `'hot.transport' is a function, so no client was added: the built-in one speaks Server-Sent Events and WebSocket, not a transport of your own. Set 'hot.client.transport' if yours speaks one of them, or add an entry for the client that speaks it${addsPlugin ? " — 'HotModuleReplacementPlugin' is still applied for you" : ""}, and 'hot.inject: "${addsPlugin ? "plugin" : "none"}"' silences this.`, ); } } else { @@ -1722,9 +1722,11 @@ function injectHotClient(compilers, options, logger) { // when `hot.client.apply` says so, and whatever the mode when `hot.inject` // leaves the plugin to the developer; a plugin written into the // configuration is the developer's and stays. + // The mode is the injected client's, so it only counts when one is added. const needsHmr = addsPlugin && - (!clientOptions || + (!addsClient || + !clientOptions || (clientOptions.apply !== "reload" && clientOptions.apply !== "nothing")); diff --git a/test/inject-client.test.js b/test/inject-client.test.js index 26af4cec4..fa07f2995 100644 --- a/test/inject-client.test.js +++ b/test/inject-client.test.js @@ -835,6 +835,49 @@ describe("what injectHotClient leaves alone", () => { expect(warned).toStrictEqual([]); }); + it("adds the plugin with inject plugin whatever the client's mode says", () => { + // `apply` is the injected client's, and none is injected. + const instance = compiler(); + + injectHotClient( + [instance], + { + path: "/__webpack_hmr", + transport: "sse", + inject: "plugin", + client: { apply: "reload" }, + }, + logger, + ); + + expect(hasHmrPlugin(instance)).toBe(true); + }); + + it("does not say the plugin is applied for a transport of your own when inject is client", () => { + /** @type {string[]} */ + const warned = []; + const instance = compiler(); + + injectHotClient( + [instance], + { + path: "/__webpack_hmr", + transport: () => ({ + close: () => {}, + onConnect: () => {}, + publish: () => {}, + publishTo: () => {}, + }), + inject: "client", + }, + { warn: (message) => warned.push(message), log: () => {} }, + ); + + expect(hasHmrPlugin(instance)).toBe(false); + expect(warned.join("\n")).toContain("no client was added"); + expect(warned.join("\n")).not.toContain("still applied"); + }); + it("adds the client but not the plugin when inject is client", () => { const instance = compiler(); const before = entryCount(instance);