fix: support WebSocket upgrades in Vite dev server#3737
Conversation
WebSocket upgrade requests never reached Fresh handlers in dev mode because Vite's Connect-based server fires 'upgrade' events on the underlying http.Server rather than routing them through middleware. Fix by starting a Deno HTTP server on a random port alongside Vite and proxying upgrade requests to it via raw TCP socket forwarding. This allows Deno.upgradeWebSocket() to work natively. Fixes #3350 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8bc3c1c to
1ac26e1
Compare
- Replace Buffer type with Uint8Array to fix CI type checking - Add onError handler to Deno.serve to suppress broken pipe errors - Add close event handlers on proxy sockets for clean teardown - Wrap destroy calls in try/catch to prevent cascading errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The second Once that Deno issue is resolved, this workaround can be replaced with a simple |
fibibot
left a comment
There was a problem hiding this comment.
Strategy is sensible — Vite's Connect-based server can't run Deno.upgradeWebSocket(), so proxying upgrade frames through a separate Deno.serve is the right shape.
Blocker: CI is red on 53bc5c82 — nine vite dev tests timed out at ~55s each (loads islands, tailwind no _app, tailwind _app, css modules, route css import, partial island, nested islands, inline env vars, can import json in npm package), all going through Puppeteer + SSR. The run is from 2026-03-29; a rebase + re-run will at least rule out drift from main, then it's worth confirming whether the new Deno.serve or the upgrade handler is interacting with the browser sessions.
- nit:
connect(wsPort, "127.0.0.1", ...)readswsPortsynchronously inside the upgrade callback, but it's only set in the asynconListen. If an upgrade fires before listening completes,connect(0, ...)fails. Practically rare (the Vite server hasn't started accepting either) but easy to harden with aPromise.withResolvers()gate. - nit:
req.url === \"/__vite_hmr\"is an exact-string match. Any query string would miss the bypass and proxy to the Deno server instead of letting Vite handle HMR.req.url?.startsWith(\"/__vite_hmr\")is safer.
|
Closing in favor of #3823 |
Summary
upgradeevents on the underlyinghttp.Serverrather than routing them through middleware, soDeno.upgradeWebSocket()was never calledDeno.serve()on a random port alongside Vite and proxy upgrade requests to it via raw TCP socket forwarding, whereDeno.upgradeWebSocket()works natively/__vite_hmr,/__vite_ping) are excluded from proxyingTest plan
vite dev - websocket upgradeverifies echo via WebSocket in dev modeFixes #3350
🤖 Generated with Claude Code