Skip to content

feat(hot): take over the last of webpack-dev-server's compiler and socket wiring - #2472

Merged
alexander-akait merged 8 commits into
mainfrom
feat/finish-dev-server-migration
Oct 9, 2026
Merged

alexander-akait merged 8 commits into
mainfrom
feat/finish-dev-server-migration

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

This takes over the last pieces webpack-dev-server still wired up itself, so that server can delete its own client and socket server (webpack/webpack-dev-server#5750). There is one client: this package's. Whatever webpack-dev-server needs to stay compatible with its 6.x users (its { type, data } messages, its client/* paths, sendMessage()) lives in webpack-dev-server, not here.

New options and exports

hot.client: false Adds no runtime to the page but still applies HotModuleReplacementPlugin, for a page that wires its own client.
Plugin on every served compilation Server bundles (target: "node") get the plugin too, so webpack/hot/poll and webpack/hot/signal keep their module.hot. The client entry is still added only where a browser runs the compilation.
hot.client.transport: "<module>" A module that exports a client class is handed to the runtime as __webpack_dev_server_client__, which is how webpack-dev-server's client.webSocketTransport has always worked.
hot.ws Passed to the ws server: compression, maxPayload, handleProtocols, verifyClient, or a port/server of its own. hot.cors and hot.token are still checked first.
webpack-dev-middleware/client/socket The live connection: client is the current transport instance, null before the runtime starts to connect and while it waits to reconnect, and client.client is the WebSocket/EventSource underneath, typed. This is the shape webpack-dev-server's client/socket had, which @pmmmwh/react-refresh-webpack-plugin reads.

Client fixes from comparing webpack-dev-server 6.0's client with this one

  • Malformed escapes in the page url: ?discount=50% no longer makes every update fail with "Invalid HMR message".
  • apply: "reload" after a reconnect: a tab that reconnects after a restart reloads onto the newer build; one already running it is left alone.
  • Warnings alongside errors: a build with both logs and posts its warnings too, first, with ANSI codes stripped.
  • webpack-dev-server's query spelling: protocol, hostname, port, pathname, username, password, live-reload and hot=only are read, so a hand-written webpack-dev-server/client/index.js?… entry keeps working.
  • ES5 down to the logger: the published client imports ES5 copies of webpack's Logger and createConsoleLogger; a test parses an ["web", "es5"] bundle of it with acorn ecmaVersion: 5.
  • Overlay: z-index: 2147483647, and Esc/keyCode 27 close it.
  • Building indicator: renders without Shadow DOM, announced as role="progressbar" with aria-valuenow.

Tests

  • React Refresh, test/e2e/react-refresh.test.js (14): @pmmmwh/react-refresh-webpack-plugin 0.6.3 with React 19, over Server-Sent Events and over a WebSocket:

    • a component keeps its state across an edit, and across several in a row;
    • an edit to another module updates it and keeps its parent's state;
    • a component whose hooks changed is started over, in place;
    • a build error shows the overlay, and the fix is applied in place;
    • an error thrown while rendering shows, and the root renders again once fixed;
    • an edit no component accepts reloads the page.

    The sources are written as react-refresh/babel leaves them: babel-loader cannot load @babel/core in this test runner on every Node.js version CI uses.

  • Universal builds (target: "universal" and ["web", "node"]): test/universal-target.test.js runs the bundle in Node (no error, nothing from the client, nothing left running), and test/e2e/universal-target.test.js updates a page in place and shows and clears the overlay.

  • Options and exports: client: false, the plugin on Node targets, a client transport module, hot.ws in every shape, and the live client export.

  • test/e2e/dev-server-parity.test.js: one test per audit item above.

  • Locally: unit 7353 of 7355 (the other two expect a writeToDisk EACCES, which root never gets); e2e 215 of 215, plus the 14 React Refresh tests; lint and types clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

Summary by CodeRabbit

  • New Features
    • Configure WebSocket connections with a dedicated port or server, and pass additional WebSocket server options.
    • Use a custom client transport, or disable browser runtime injection while keeping hot-module replacement enabled.
    • Access the active client connection for monitoring, and use hot updates with universal build targets.
    • React Fast Refresh is supported with Server-Sent Events and WebSocket transports.
  • Bug Fixes
    • Improved reload behavior after reconnects, handling of malformed URL escapes, and warning and error reporting.
    • The build overlay now responds to Escape consistently and appears above other content. Progress indicators include accessibility information and work without Shadow DOM.
    • Improved compatibility with legacy client options and ES5 output.
  • Documentation
    • Expanded guidance for WebSocket options, client configuration, and hot-module replacement behavior.

alexander-akait and others added 4 commits October 6, 2026 16:08
…cket wiring

Everything webpack-dev-server still wired up for itself, and the
regressions a page using its client would see with this one in its place.

- hot.client: false adds no runtime and still applies the plugin; the
  plugin goes to every compilation served, server bundles included
- hot.client.transport accepts a module exporting a client of your own,
  provided as __webpack_dev_server_client__
- hot.ws is handed to the ws server: compression, verifyClient, or a
  port or server of its own, with cors and token still checked
- the live connection is exported as webpack-dev-middleware/client/socket
- client: malformed escapes in the page url, reload mode catching up
  after a restart, warnings alongside errors, webpack-dev-server's query
  spelling, an ES5 logger, overlay z-index and Esc, indicator without
  Shadow DOM and with progressbar semantics

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
…webpack-dev-server's client did

A deprecated name found while reading the query is reported under the
label and at the level the query asks for, and ?live-reload=true on its
own is live reload, since that client read hot as off unless told.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
hot, liveReload and reconnect were never this client's query names; they
arrive from entries written for webpack-dev-server's client, which is
what webpack's guide shows for wiring it by hand. Only reload,
autoConnect and timeout, which this client's own query offered, warn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0c2aa2a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack-dev-middleware Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 6, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs APIs the published 8.3.0 does not have (`onConnect`
with the request, `handleUpgrade`, `publish`, `publishTo`, `hot.ws`,
`hot.client: false`, a client transport module, `client/socket`).
CI installs from the registry, so without this `tsc` fails in the Build
step and every job behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • No new commits to review - use @coderabbitai full review for a full pass

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4ebacf1a-fa70-4795-a613-fafffea1e030
📥 Commits

Reviewing files that changed from the base of the PR and between 07c1ca2 and 7eb4444.

📒 Files selected for processing (1)
  • test/universal-target.test.js

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.


Walkthrough

The changes add webpack-dev-server protocol support, custom client transports, and configurable WebSocket server listeners. They update browser-client query handling, reload behavior, message reporting, overlays, and progress indicators. The active socket connection is exposed through a package export. Build scripts generate ES5 logger modules, and tests cover the new runtime and client behavior.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 7eb44

The reviewed change adds coverage for the supported target spellings and runtimes, with no actionable merge risk identified in the supplied scope.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 98.72% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 78 functions across 58 files. (5 skipped: 5…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: moving webpack-dev-server’s remaining compiler and socket wiring into webpack-dev-middleware.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3833918b-f7fd-4c03-8ae4-88fcfab34aa4
📥 Commits

Reviewing files that changed from the base of the PR and between d15ffc2 and 27f01ae.

📒 Files selected for processing (31)
  • .changeset/dev-server-client-parity.md
  • .changeset/finish-dev-server-migration.md
  • .cspell.json
  • README.md
  • babel.config.js
  • client-src/clients/createSocket.js
  • client-src/index.js
  • client-src/indicator.js
  • client-src/overlay.js
  • client-src/socket.js
  • package.json
  • scripts/build-client-logger.mjs
  • src/hot.js
  • src/options.check.js
  • src/options.json
  • src/servers/WebSocketServer.js
  • src/utils.js
  • test/__snapshots__/validation-options.test.js.snap.webpack5
  • test/client-bundle.test.js
  • test/client-socket.test.js
  • test/e2e/dev-server-parity.test.js
  • test/e2e/live-reload.test.js
  • test/e2e/transport.test.js
  • test/fixtures/custom-client-transport.js
  • test/hot.test.js
  • test/inject-client.test.js
  • types/client/clients/createSocket.d.ts
  • types/client/socket.d.ts
  • types/hot.d.ts
  • types/servers/WebSocketServer.d.ts
  • types/utils.d.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread README.md Resolved
Comment thread types/client/clients/createSocket.d.ts Resolved
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.55%. Comparing base (d15ffc2) to head (0c2aa2a).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2472      +/-   ##
==========================================
+ Coverage   96.39%   96.55%   +0.16%     
==========================================
  Files          24       24              
  Lines        2579     2642      +63     
==========================================
+ Hits         2486     2551      +65     
+ Misses         93       91       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 6, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7cb54297-db22-40ad-985b-4b394e19d851
📥 Commits

Reviewing files that changed from the base of the PR and between 27f01ae and 0b50966.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (52)
  • .changeset/webpack-dev-server-protocol.md
  • .cspell.json
  • README.md
  • client-src/clients/EventSourceClient.js
  • client-src/webpack-dev-server/clients/WebSocketClient.js
  • client-src/webpack-dev-server/globals.d.ts
  • client-src/webpack-dev-server/index.js
  • client-src/webpack-dev-server/modules/logger/index.js
  • client-src/webpack-dev-server/modules/logger/tapable.js
  • client-src/webpack-dev-server/overlay.js
  • client-src/webpack-dev-server/progress.js
  • client-src/webpack-dev-server/socket.js
  • client-src/webpack-dev-server/utils/log.js
  • client-src/webpack-dev-server/utils/sendMessage.js
  • package.json
  • scripts/build-dev-server-logger.mjs
  • src/hot.js
  • src/index.js
  • src/options.check.js
  • src/options.json
  • src/servers/WebSocketServer.js
  • src/utils.js
  • src/webpack-dev-server.js
  • test/__snapshots__/validation-options.test.js.snap.webpack5
  • test/client-transports.test.js
  • test/client/webpack-dev-server/ReactErrorBoundary.test.js
  • test/client/webpack-dev-server/__snapshots__/index.test.js.snap.webpack5
  • test/client/webpack-dev-server/__snapshots__/socket-helper.test.js.snap.webpack5
  • test/client/webpack-dev-server/bundle.test.js
  • test/client/webpack-dev-server/clients/WebsocketClient.test.js
  • test/client/webpack-dev-server/clients/__snapshots__/WebsocketClient.test.js.snap.webpack5
  • test/client/webpack-dev-server/helpers/jsdom-setup.js
  • test/client/webpack-dev-server/index.test.js
  • test/client/webpack-dev-server/overlay-lifecycle.test.js
  • test/client/webpack-dev-server/socket-helper.test.js
  • test/client/webpack-dev-server/utils/__snapshots__/getCurrentScriptSource.test.js.snap.webpack5
  • test/client/webpack-dev-server/utils/__snapshots__/log.test.js.snap.webpack5
  • test/client/webpack-dev-server/utils/__snapshots__/sendMessage.test.js.snap.webpack5
  • test/client/webpack-dev-server/utils/createSocketURL.test.js
  • test/client/webpack-dev-server/utils/getCurrentScriptSource.test.js
  • test/client/webpack-dev-server/utils/log.test.js
  • test/client/webpack-dev-server/utils/sendMessage.test.js
  • test/e2e/dev-server-parity.test.js
  • test/e2e/webpack-dev-server-runtime.test.js
  • test/validation-options.test.js
  • test/webpack-dev-server-protocol.test.js
  • tsconfig.client.dev-server.json
  • tsconfig.client.json
  • types/hot.d.ts
  • types/index.d.ts
  • types/utils.d.ts
  • types/webpack-dev-server.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .cspell.json
  • test/e2e/dev-server-parity.test.js

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread src/utils.js Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (2)

🟡 Minor · Keep the username and password separate until URL formatting. · index.js:830-843

client-src/webpack-dev-server/index.js:830-843
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep the username and password separate until URL formatting.

runtimeQuery emits username and password separately, but createSocketURL joins them before formatURL splits at the first colon. For username=a%3Ab&password=pw, the socket URL becomes ws://a:b%3Apw@example.com/ws, encoding username a and password b:pw instead of the configured values. An endpoint that uses these credentials can reject the connection. Pass both fields separately to formatURL, and add this case to the createSocketURL tests.

Suggested fix
--- a/client-src/webpack-dev-server/index.js
+++ b/client-src/webpack-dev-server/index.js
@@
- * @param {{ protocol?: string, auth?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL object URL
+ * @param {{ protocol?: string, username?: string, password?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL object URL
@@
-  let auth = objURL.auth || "";
-
-  if (auth) {
-    // Only the first colon separates the username from the password.
-    const separator = auth.indexOf(":");
-
-    auth =
-      separator === -1
-        ? encodeURIComponent(auth)
-        : `${encodeURIComponent(auth.slice(0, separator))}:${encodeURIComponent(auth.slice(separator + 1))}`;
+  let auth = "";
+
+  if (objURL.username) {
+    auth = encodeURIComponent(objURL.username);
+
+    if (objURL.password) {
+      auth += `:${encodeURIComponent(objURL.password)}`;
+    }
+
     auth += "@";
   }
@@
-  let socketURLAuth = "";
+  let socketURLUsername = "";
+  let socketURLPassword = "";
@@
-    socketURLAuth = decodeAuth(parsedURL.username);
+    socketURLUsername = decodeAuth(parsedURL.username);
@@
-      socketURLAuth = socketURLAuth.concat(":", decodeAuth(parsedURL.password));
+      socketURLPassword = decodeAuth(parsedURL.password);
@@
-    auth: socketURLAuth,
+    username: socketURLUsername,
+    password: socketURLPassword,
--- a/test/client/webpack-dev-server/utils/createSocketURL.test.js
+++ b/test/client/webpack-dev-server/utils/createSocketURL.test.js
@@
     [
       "?hostname=example.com&username=user&password=pa:ss",
       "http://example.com",
       "ws://user:pa%3Ass@example.com/ws",
     ],
+    [
+      "?hostname=example.com&username=a%3Ab&password=pw",
+      "http://example.com",
+      "ws://a%3Ab:pw@example.com/ws",
+    ],
🟡 Minor · Limit the raw-client statement to built-in transports. · README.md:1018-1021

README.md:1018-1021
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Limit the raw-client statement to built-in transports.

When the runtime uses a custom transport, the exported client is the adapter. The CommunicationClient hooks do not require a .client property, so the documented listener can skip attaching to a raw connection. State that the guarantee applies to built-in transports and that custom transports need not expose a native client.

Suggested fix
-// `client` is live: `null` before the runtime starts to connect and while it
-// waits to reconnect. `client.client` is the `WebSocket` or `EventSource`
-// underneath, a new one for each connection, so a listener added to it hears
-// that connection only.
+// `client` is live: `null` before the runtime starts to connect and while it
+// waits to reconnect. With a built-in transport, `client.client` is the
+// `WebSocket` or `EventSource` underneath, a new one for each connection, so
+// a listener added to it hears that connection only. Custom transports need
+// not expose a native client.

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1685de6d-ac30-47fe-bc0b-f0c76e4a17bd
📥 Commits

Reviewing files that changed from the base of the PR and between 0b50966 and ad06ba9.

📒 Files selected for processing (8)
  • README.md
  • client-src/clients/createSocket.js
  • client-src/socket.js
  • client-src/webpack-dev-server/index.js
  • src/utils.js
  • test/client/webpack-dev-server/utils/createSocketURL.test.js
  • test/webpack-dev-server-protocol.test.js
  • types/client/clients/createSocket.d.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • client-src/socket.js
  • client-src/clients/createSocket.js
  • test/webpack-dev-server-protocol.test.js
  • types/client/clients/createSocket.d.ts
  • src/utils.js
  • README.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 6, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 6, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
alexander-akait and others added 2 commits October 6, 2026 22:06
…xample

`client/socket`'s `client` is now typed with its `client`, the
`WebSocket` or `EventSource` underneath, which the documented usage
reads. It is a type of that export's own, so the contract a custom
transport implements is unchanged.

The README's example reads it only when there is one, and both it and
the module's comment now say when that is: from when the runtime starts
to connect, with a new one for each connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
A universal build (`target: "universal"` or `["web", "node"]`) is one
bundle for a page and for Node. Tests build both spellings with the
client over each transport, and through a re-export that hands it its
query: Node runs the bundle with no error, no output from the client and
nothing left running, and a browser updates the page in place and shows
and clears the overlay. The check that the client is in the bundle takes
either path separator, as webpack writes Windows paths with backslashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
@alexander-akait
alexander-akait force-pushed the feat/finish-dev-server-migration branch from 7eb4444 to 3c41aec Compare October 6, 2026 22:23
`@pmmmwh/react-refresh-webpack-plugin` applies Fast Refresh through
this client, so a change to how it applies updates shows here, over
each transport:

- a component keeps its state across an edit, and across several;
- an edit to another module updates it and keeps its parent's state;
- a component whose hooks changed is started over, in place;
- a build error shows the overlay, and the fix is applied in place;
- an error thrown while rendering shows, and the root renders again;
- an edit no component accepts reloads the page.

The sources are written as `react-refresh/babel` leaves them, since
babel-loader cannot load `@babel/core` in this test runner on every
Node.js version CI uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
@socket-security

socket-security Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedreact-refresh@​0.19.01001007296100
Addedreact@​19.3.01001008497100
Added@​pmmmwh/​react-refresh-webpack-plugin@​0.6.39810010087100
Addedreact-dom@​19.3.01001009298100

View full report

alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 6, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0ba8706c-e994-42c0-97b0-f21269ffecfd
📥 Commits

Reviewing files that changed from the base of the PR and between 7eb4444 and c82c422.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .cspell.json
  • README.md
  • package.json
  • src/hot.js
  • src/options.check.js
  • src/options.json
  • src/servers/WebSocketServer.js
  • src/utils.js
  • test/__snapshots__/validation-options.test.js.snap.webpack5
  • test/e2e/dev-server-parity.test.js
  • test/e2e/react-refresh.test.js
  • test/e2e/universal-target.test.js
  • test/universal-target.test.js
  • types/hot.d.ts
  • types/utils.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/snapshots/validation-options.test.js.snap.webpack5

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread test/universal-target.test.js Resolved
The helper made its directory and started a watcher before anything
was handed back, so an error on the way left both behind. The watcher
is now closed either way, and the directory removed when nothing is
returned to remove it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
@alexander-akait
alexander-akait merged commit 8a7af5e into main Oct 9, 2026
24 checks passed
@alexander-akait
alexander-akait deleted the feat/finish-dev-server-migration branch October 9, 2026 13:11
alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 9, 2026
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
alexander-akait added a commit to webpack/webpack-dev-server that referenced this pull request Oct 17, 2026
… here (#5750)

* refactor(client): take the WebSocket transport from dev-middleware

First step of moving the hot clients into webpack-dev-middleware: this
package's `WebSocketClient` is a strict subset of the one that package now
ships, and worse in three ways. It has no `close()` at all, despite
declaring `@implements {CommunicationClient}`, which the interface requires.
It has no guard against an event the socket had already queued reporting
after the caller closed, so a close could schedule a reconnection nobody
asked for. And it hands the url to `new WebSocket` unresolved, which throws
on browsers before Chrome 125 / Firefox 124 / Safari 17.3 for a relative or
`http(s):` url — this package always builds an absolute `ws:` url, so that
one never bit here, but it is a trap for anyone reusing the class.

Re-exported rather than deleted: `client.webSocketTransport` resolves to
this path, so anything pointing at it keeps working.

`import/no-unresolved` is switched off for the file, following the exemption
already in place for `@changesets/get-github-info`: the import resolver
cannot follow an `exports` subpath. TypeScript does resolve it, so
`lint:types-client` still covers the import.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor(client): take the error overlay from dev-middleware too

692 lines of overlay deleted in favour of the one dev-middleware ships —
the state machine, the iframe, the rendering, the runtime-error listeners.
What is left is an adapter: this package's `createOverlay`/`send` shape on
top of that overlay's `showProblems`/`clear`, plus `formatProblem`, which
stays because the console uses it and because this package still receives
webpack's error objects rather than formatted strings.

Three things had to be carried across so nothing outward changes:

  * the element id. It is what a test, a screenshot tool or an integration
    finds the overlay by, so `webpack-dev-server-client-overlay` is passed
    through dev-middleware's new `overlay.id` rather than renamed;
  * the Trusted Types policy name. Under an enforced
    `require-trusted-types-for 'script'` the page's CSP allowlists a policy
    by name, and dev-middleware's default is a different one, so
    `webpack-dev-server#overlay` is passed explicitly — including where
    this package's option is `false`, which means "no name of my own";
  * `/webpack-dev-server/open-editor`. dev-middleware leaves the endpoint
    empty by default, having no route to point at, so the file references
    in a problem would have stopped being clickable.

The first two came from the e2e suite failing, not from reading the code,
which is the argument for swapping under the tests rather than after.

What does change is the overlay's internal DOM, which is not an interface
this package documents: a clickable file reference is `[data-open-file]`
rather than `[data-can-open]`, and a problem is headed by its level and
origin rather than "Compiled with problems". Two tests assert on those and
move with the implementation.

Checked against a baseline taken first: the overlay suite fails exactly
the set it fails on a clean `main` in this container — no case newly
failing, none newly passing — and the client and web-socket-url suites are
unmoved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* feat(client): add Server-Sent Events as a second transport

`client.webSocketTransport: "sse"` connects the page with `EventSource`
instead of a WebSocket. The endpoint is webpack-dev-middleware's hot endpoint,
which is an ordinary response rather than an upgrade, so there is no second
server to start and it sits in the middleware chain where everything this
package puts in front of it can still see it.

Naming it as the client's transport picks the endpoint that serves it, so
`webSocketServer` does not have to be set as well; `webSocketServer: "sse"` is
accepted too. `lib/servers/EventSourceServer.js` is the adapter between that
endpoint and the transport shape the rest of this package is written against,
so nothing above the wire can tell which one it is talking to — same protocol,
same messages, same options.

All of the protection stays here: the endpoint hands over the request each
client connected with, and `allowedHosts` and the origin check decide as they
do for a socket. One difference in how that check reads a request. A browser
puts an `Origin` on a WebSocket handshake whether or not it is cross-origin,
so one without it is refused; `EventSource` sends no `Origin` on a same-origin
request, so for a stream an absent one is taken as a page of this server's own.
A stream that does carry an `Origin` — which is what a cross-origin page sends
— is checked as before, and the `Host` check runs either way.

Both transports are the middleware's now, re-exported from
`client-src/clients/`, and each has a test of its own: the contract both
answer, plus the silence watchdog only a stream needs. `test/e2e/event-source.test.js`
drives a real browser through a hot update over the stream, the handshake it
is greeted with, and both sides of the host check.

`socket.js` no longer throws on a frame it cannot parse. A stream's keep-alive
has to be a `data:` frame rather than a comment, or the client's own watchdog
would count a quiet connection as a dead one, so something that is not JSON
now arrives on the wire by design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor(client): no overlay code left to adapt

`client-src/overlay.js` was the last of it: an adapter whose substance was
turning webpack's error objects into the shape the shared overlay renders, and
a `send({ type })` state machine over `showProblems`/`clear`. Both belong
elsewhere. The formatting is webpack-dev-middleware's `client/problem` now,
and the state machine was standing in for per-source slots that overlay
already has, so the events map onto two calls.

What stays is this package's identity on top of a shared overlay, as options:
the `webpack-dev-server-client-overlay` element id, so anything querying it is
unaffected; the `webpack-dev-server#overlay` Trusted Types policy name, which
a page's CSP allowlists by name; and the `/webpack-dev-server/open-editor`
route that makes a file reference clickable.

`test/client/ReactErrorBoundary.test.js` goes with it — the heuristic it
covers lives in webpack-dev-middleware, which tests it.

Requires webpack/webpack-dev-middleware#2438.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* feat: reuse webpack-dev-middleware's browser runtime

This server has no client of its own any more. `client-src/` is gone — the
runtime, the error overlay, the progress indicator, the logger, the socket
wrapper and the WebSocket client, around two thousand lines that existed in
near-duplicate in both projects — and the middleware's hot option drives all
of it, including putting the entry and `HotModuleReplacementPlugin` into the
compilation.

Nothing about a configuration changes. Every option keeps its name and its
meaning and is mapped onto the middleware's vocabulary in `lib/hotOptions.js`:
`hot`/`liveReload` become one `apply` mode, `client.reconnect` becomes
`connect.retries`, `client.webSocketURL` is already the shape the middleware's
`hot.client.path` takes, and the overlay is passed through with this project's
own element id so anything querying for it still finds it.

The `webpack-dev-server/client/*` paths are generated re-exports of the
middleware's client, so importing them resolves to what it always did. The
paths that were only this bundle's internals are no longer published.

Protection stays here. `allowedHosts`, the `Origin` checks and the same-origin
rule are unchanged: the middleware is handed `cors: true` and no token, which
is it applying no policy of its own, and every connection is judged here
before anything is published to it. A handshake is refused before it completes
rather than after, since the middleware exposes the upgrade rather than taking
the server over.

A `webSocketServer` of your own still works. A class, a module exporting one,
or options giving the socket a port or a server of its own are wrapped into
the shape the middleware asks a custom transport for, so there is one path
from a build to a page either way, and `BaseServer` keeps its export.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor: serve "sse" from the middleware's own transport

The adapter this branch added, `lib/servers/EventSourceServer.js`, existed to
dress the middleware's hot endpoint up as the transport shape this server was
written against. There is no such shape any more — the middleware owns both
ends of both transports — so naming `sse` is now just choosing one of its
transports, and the adapter and its special case in `normalizeOptions` are
gone with it.

`client.webSocketTransport: "sse"` and `webSocketServer: "sse"` keep working
and keep picking each other, which is what the option values are for. The
published `client/clients/EventSourceClient.js` path is generated alongside
its WebSocket neighbour, so a transport named by module still resolves.

One thing this caught: `"sse"` was being read as an implementation of this
server's own, since the test for that was "not `ws`" — which sent it looking
for a class to construct and failed before a page could connect. Both of the
middleware's transports are named now, and a smoke run over each confirms the
endpoint answers: `426` to a plain GET on the WebSocket one, an event stream
on the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: keep this package's name and its refusals in the browser console

Two things the browser used to say that the middleware's runtime had no way
to say for it.

The console prefix: every message read `[webpack-dev-server]`, and four
snapshots record exactly that, including `Invalid Host/Origin header` — a line
people search for. Taking the runtime as-is would relabel all of it with a
dependency's name and send anyone with a problem to the wrong repository, so
the name travels with the logging option.

The refusal itself: a client turned away by `allowedHosts` or the origin check
is told why, and the reason is logged in the page. That needs the handshake to
complete, so the upgrade is answered even for a client that will be refused
and `guardHotEndpoint` closes it immediately after saying so — which is what
this server has always done. Owning the `upgrade` listener is still what keeps
a proxied upgrade with whoever it belongs to; `isHotEndpoint` went with the
earlier attempt to refuse before the handshake, which would have made the
reason unreachable.

Verified in a browser: the console carries `[webpack-dev-server]`, and a
client from a disallowed origin receives
`{"action":"error","message":"Invalid Host/Origin header"}` and is closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: wait for a build rather than the handshake's configuration push

Two proxy cases waited for `{ type: "hot" }`, which was this server telling a
client what to enable right after the handshake. That configuration is in the
client's entry query now, so the first thing the socket says is the build the
page is to run.

What the cases are about is untouched and still asserted: the HMR upgrade must
not reach the proxy target (`backendUpgradeCount` stays `0`), and must not
make the proxy log an error. Both hold.

`test/server/open-option.js` fails 33/33 here and on `main` alike — it drives
a real browser through `open`, which this environment has none of.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: refuse an event-stream client, not just a WebSocket one

`allowedHosts` and the origin check were not being enforced on the `sse`
transport. The guard ran and decided to refuse, and then neither half of the
refusal landed: the message went through `sendMessage`, which tests
`readyState === 1` and calls `client.send` — a `ServerResponse` has neither,
so the loop skipped it — and `client.close()`, which a `ServerResponse` also
does not have, threw and was swallowed. The stream stayed open and kept
receiving builds.

The message goes through the middleware's `publishTo` now, which puts it on
the wire the client is actually on, and the connection is dropped the way that
client is dropped: a WebSocket closes, a response ends.

Verified on both transports: a disallowed origin is told
`Invalid Host/Origin header` and dropped, over a WebSocket and over a stream
alike — and a same-origin page, which `EventSource` gives no `Origin` at all,
is still let through.

Found via a CodeRabbit review of the middleware's README example, which had
the same two mistakes in it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: treat an empty `webSocketURL` part as one that was not given

A `client.webSocketURL` written as a string is normalized by parsing it, which
fills in every part — as `""` for the ones the url did not carry. Those were
passed straight through to the middleware, which refuses an empty port: so
`webSocketURL: "ws://example.test/ws"` stopped the server from starting at
all.

Empty has always meant "not said" here, and both the old client and the new
one resolve such a part from the page, so it is left out rather than sent.

The `allowed-hosts` snapshots are regenerated with it. Three changes, all
consequences of the runtime moving rather than of anything going wrong:
`connected` is logged where this server used to log which features were
enabled, `[HMR] Waiting for update signal from WDS...` is gone with the
`webpack/hot/dev-server` entry that printed it, and the prefix is still
`[webpack-dev-server]`. The suite is 31 of 32 here; the one failure is an
IPv6 case, and this container cannot listen on `::1` at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: drop a test of a file that no longer exists, and regenerate the types

Two things CI's Lint job reports that are not about the dependency.

`test/client/clients/EventSourceClient.test.js` was added earlier on this
branch and imports `client-src/clients/EventSourceClient.js`, which is gone.
It never survived the rebase as a deletion because it did not exist on the
side that deleted the rest of `test/client/`. Every case in it tests the
middleware's own stream client through a re-export, and the middleware's
`test/client-transports.test.js` already covers all six — plus the ones only a
stream has — so nothing is lost.

`types/` is tracked and the Lint job fails with "Missing types" when a build
changes it. `Server.d.ts` still described the removed methods, and the two
new modules had no declarations at all.

The changeset now says the two public methods that went with them,
`getClientEntry()` and `getClientHotEntry()`, are a breaking removal. It
recorded the rest of the removal and not that.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: regenerate the web-socket-server-url console snapshots

The suite reads the browser's console, which is now the middleware's client
talking. Every difference is one the migration makes on purpose:

  - the startup banner, `Server started: Hot Module Replacement enabled, …`,
    and `[HMR] Waiting for update signal from WDS...` are gone — the old client
    printed the first from the configuration this server pushed after the
    handshake, and the second came from the `webpack/hot/dev-server` entry this
    server no longer adds;
  - `Hey.` comes first, because the page no longer waits on a banner;
  - the reconnect cases gain `Trying to reconnect...` and `Disconnected!`.

Regenerated only after reading what kind of failure each of the 34 was: all
snapshot mismatches, none of them an error.

One test here was worse than a mismatch. "should not work and output disconnect
wrong web socket URL" waits for `Disconnected!` with no timeout, and the
middleware's client did not print it, so it spun forever with its server and
browser open and took the whole file to the runner's 400 s limit. That is fixed
in webpack-dev-middleware, which now says so when the connection goes away;
with it the file runs in 53 s and passes twice running.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: keep manual client entries, page-url opt-outs and a hand-made middleware working

- `client/index.js` is now CommonJS and leaves the query written on it where
  the middleware's client reads it, so `client/index.js?...` entries keep
  their options instead of connecting with defaults.
- Name the client's page-url parameters after this package again, so
  `?webpack-dev-server-hot=false` and `?webpack-dev-server-live-reload=false`
  still opt a tab out.
- `client: false` no longer throws while reading `client.webSocketTransport`.
- A middleware mounted through `setupMiddlewares` has no hot endpoint of this
  server's, so there is nothing to guard.
- Update the e2e expectations for the middleware's client: its messages, the
  order of `connected` after the page's own output, and a wait that no longer
  polls forever for a message the client does not log.
- Drop the tests of the removed `getClientEntry()`/`getClientHotEntry()`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: bring the end-to-end suites in line with the middleware's client

Every difference is one the migration makes on purpose, and the ones that
were not are fixed:

- Provide the transport through `__webpack_dev_server_client__` again for
  `ws` and `sse` too, as the global has always held the one in use, and have
  the `client/` re-exports hand back the default export of the modules that
  have one.
- Point the overlay at this server's open-editor route.
- Expect what the middleware's client logs, with the build's timing and
  absolute paths normalized, and its message for each way an update goes.
- Port the tests that reached into the removed WebSocket server: the
  heartbeat, the client count, the host check on the event stream, and the
  federation entries, which are now checked on a browser target — a build for
  node is given no client.
- Replace the tests of the old progress element with ones of what
  `client.progress` promises, the indicator's own being the middleware's.
- Add unit tests for the options this server hands the middleware.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: update the command line tests for the middleware's client, and say what differs

The hot option is now visible in the client's entry rather than in a
webpack/hot entry, and a build for node is given neither. The changeset lists
what a page and its console look like now, which it had called nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: record what a CI browser sees, not what a sandbox does

Snapshots regenerated in a sandbox carried its differences with them:

- The overlay's style attributes came out with border: none, where the
  browser CI uses writes the four longhands the committed snapshots have.
- A sandbox proxy refuses connections to the addresses the web socket URL
  tests use with a 403, so those entries recorded a refusal where CI connects
  or fails to resolve the name.
- The IPv6 entry of the allowed hosts test could not run there, so its
  snapshot was dropped by the update.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: write the changed file's path the same on every platform

A rebuild's message names the file that changed, and a Windows machine writes
that path with backslashes, so the snapshots that record it could only match
on a machine that does not.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: wait for the client to give up on a name that never resolves

How long a machine takes to say a name does not resolve is its own, and the
page was idle before the answer came on one runner, so the snapshot was taken
of "Hey." alone. The test now waits for the message that ends the sequence it
records.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: wait for the worker's client to connect before the snapshot

The worker's own client connects after the page has settled, so on a
slow runner the snapshot could see one "connected" line instead of two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore: merge this change's changesets into one entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* feat: leave the last of the compiler and socket wiring to the middleware

dev-server no longer applies a ProvidePlugin or HotModuleReplacementPlugin,
or runs a WebSocket server of its own: `client: false`, a custom
`client.webSocketTransport`, `webSocketServer.options` (a port or server
of its own, verifyClient, compression) and the HMR plugin for every
compilation are webpack-dev-middleware's `hot` options now.
lib/servers/WebsocketServer.js and bridge.js are gone; BaseServer stays
as the contract for a `webSocketServer` implementation of your own.

And what a page or a tool written against 6.0 relies on keeps working:

- `client/socket.js` and this server's own `{ type }` messages, which
  React Refresh's overlay reads off the socket
- `webSocketServer.clients` and `.implementation`, `sendMessage()`
  (deprecated; `static-changed` reloads), `getClientEntry()` and
  `getClientHotEntry()` (deprecated)
- the Trusted Types policy `webpack-dev-server#overlay`, progress off
  unless asked, a one second ping, overlay filters handed `{ message }`
- a hand-written `client/index.js` entry connects to `/ws` as before
- a `setupMiddlewares` that replaces the dev middleware (a Hono app)
  keeps the hot endpoint
- the editor is opened on the file resolved against webpack's context

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* fix: keep a subclass's client entries, the client paths in use and universal builds working

- `getClientEntry()` and `getClientHotEntry()` overridden by a subclass
  put their modules in `entry` again, the client one with the query this
  server always wrote, and the middleware then adds no client of its own.
- `client/` keeps only the paths something outside this package uses:
  `index.js`, written into `entry` by hand; `socket.js`, which React
  Refresh reads; and `clients/WebSocketClient.js`, which a transport of
  someone else's extends, still with `default` for `require()`.
- `client/index.js` hands its query over only where there is a `self`,
  so a universal build runs in Node without it throwing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: main's suite on the middleware's client, and React Refresh's hot scenarios

main's tests run as they are wherever they still pass; the rest only
record what the middleware's client says and shows, in its console,
overlay and progress indicator. A subclass's `getClientEntry()` and
`getClientHotEntry()` keep main's test.

React Refresh is tested with `@pmmmwh/react-refresh-webpack-plugin`
itself: a component keeps its state across one edit and several, a
child module updates in place, changed hooks remount in place, its
overlay shows a build error and an error thrown while rendering and
clears once fixed, an unaccepted edit reloads, `hot: "only"` does not,
and all of it over Server-Sent Events too. A universal build is served
to a page and run in Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore(ci): TEMPORARY - install the unreleased webpack-dev-middleware

DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a
released `^8.4.0`, once webpack-dev-middleware ships.

This branch needs what the published 8.3.0 does not have: `hot.protocol`
with this server's runtime in `client/webpack-dev-server/`, `onConnect`
with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws`
server handed out as `hot.implementation`. CI installs from the
registry, so without this `tsc` fails in the Build step and every job
behind it is cancelled.

This vendors a pack of webpack-dev-middleware `main` plus
webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration).
The lockfile change is confined to that one dependency.

To undo, with the release out:

    git revert <this commit>
    npm install webpack-dev-middleware@^8.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor: follow the middleware's hot.client.url and pageParamPrefix names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore(ci): TEMPORARY - re-vendor webpack-dev-middleware with the settled option names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: check the hot option against webpack-dev-middleware's schema

Builds the middleware from each shape of configuration, so a name renamed
on either side fails here, and checks a hand-written client entry still
honours this server's page-url parameters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor: hand a custom client to hot.client.module, and turn off both halves together

Follows webpack-dev-middleware's split: `client.webSocketTransport` naming
a module now maps onto `hot.client.module`, with `transport` left as the
wire; and "add nothing" is `client: false` plus `inject: false`, since
`inject: false` alone now only leaves the plugin out. Re-vendors the
middleware build carrying the change (TEMPORARY until it is released).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* refactor: map client: false onto the middleware's hot.inject enum

Reverts the `hot.client.module` mapping, so `client.webSocketTransport`
goes to `hot.client.transport` again; `client: false` becomes
`inject: "plugin"` with `hot` and `inject: "none"` without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore(ci): TEMPORARY - re-vendor webpack-dev-middleware with the hot.inject enum

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore: mark sendStats and the client/socket shim for the next major release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore: regenerate types for the sendStats TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore(ci): TEMPORARY - re-vendor webpack-dev-middleware main (f084ec9)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* chore(deps): depend on webpack-dev-middleware ^8.4.0

Replaces the TEMPORARY vendored build with the released package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

* test: buffer split SSE frames and accept extra connects in the worker test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants