vite icon indicating copy to clipboard operation
vite copied to clipboard

Avoid unnecessary reload after HMR WebSocket connection lost

Open vilicvane opened this issue 4 years ago • 23 comments

Clear and concise description of the problem

Currently Vite reloads the page after a successful ping after connection lost.

In a hybrid app, once the app launches another app, the connection lost. When the app switches back, the app reloads. To properly debug routines involve switching to another app, we have to either use production build or comment out location.reload() in vite/dist/client/client.mjs.

Suggested solution

Reconnect WebSocket to check HMR status instead of reload unconditionally.

Alternative

No response

Additional context

No response

Validations

vilicvane avatar Nov 14 '21 07:11 vilicvane

I have same problem #6089

zhengjiaqi avatar Dec 13 '21 07:12 zhengjiaqi

I want to flag an important reason I'd like to be able to deactivate the websocket.

An active websocket blocks the bfcache on the browser. So the bfcaching caching behavior locally is different than the caching behavior in prod, where there is no websocket, at least for me. I therefore couldn't reproduce a certain cache-related bug locally until I realized the Vite websocket was affecting it. Because there was no way to shut off websockets completely, I had to strip Vite out to be able to reproduce the bug.

Just leaving this comment here since I didn't see any reference to this caching issue in any of the websocket-related Vite issues, and I'm sure someone will have this problem someday.

hkhanna avatar Oct 12 '22 20:10 hkhanna

@niksy

How this happens is:

When the web socket connection was disconnected, Vite's HMR client starts to ping to http://yourserver/websocket-path. https://github.com/vitejs/vite/blob/29abeeab9cc147c6d3383c672ded68311304bbaf/packages/vite/src/client/client.ts#L311-L333 When the Vite server starts, Vite will respond to that ping request. Then, Vite's HMR client reload the page. https://github.com/vitejs/vite/blob/29abeeab9cc147c6d3383c672ded68311304bbaf/packages/vite/src/client/client.ts#L103-L105

But this has some edge cases. For example, when WebSocket server starts after the HTTP Server (can happen in middleware mode).

Where we need to change is:

  • Change the ping to use WebSocket instead of HTTP: https://github.com/vitejs/vite/blob/29abeeab9cc147c6d3383c672ded68311304bbaf/packages/vite/src/client/client.ts#L311
  • Handle that WebSocket connection: https://github.com/vitejs/vite/blob/29abeeab9cc147c6d3383c672ded68311304bbaf/packages/vite/src/node/server/ws.ts#L80

I guess we need to use a different protocol(sec-websocket-protocol) to differentiate the normal connection and the ping connection.

sapphi-red avatar Dec 11 '22 15:12 sapphi-red

@sapphi-red

Change the ping to use WebSocket instead of HTTP:

Does this mean we need to create our own WebSocket server and reference it in Vite server.hmr.server property?

niksy avatar Dec 14 '22 09:12 niksy

Does this mean we need to create our own WebSocket server and reference it in Vite server.hmr.server property?

No, Vite already has a WebSocket server and we can reuse that.

sapphi-red avatar Dec 14 '22 10:12 sapphi-red

Could somebody give me any suggestions on how I can prevent the reload?

I use the latest vite 4.5.0 with SSR, createViteServer in middlewareMode and Express.js. Most of my config is based on the very nice repo vite-typescript-ssr-react.

Honestly, I have not had any issues until we stuck with a security check with one of the 3rd party services. They use Burp Suite to intercept all of the network requests. The app running I believe reverse proxy.

The first thing I noticed is the connection via the app probably because the proxy is always insecure. image

This is why, I think, the WebSocket connection fails, and in an attempt to reestablish the connection Vite reloads the web app.

Also to be honest I don't really understand why we need the HMR WebSocket on prod.

IvanKalinin avatar Nov 11 '23 07:11 IvanKalinin

@IvanKalinin Vite isn't intended to be run in prod and you shouldn't be doing that. https://vitejs.dev/guide/ssr.html#:~:text=Then%2C%20in%20server,a%20working%20setup

(That is not related to this issue)

sapphi-red avatar Nov 11 '23 12:11 sapphi-red

Would love to see this be configurable.

For my situation (a very simple vite server setup w/ some proxies for socketio and our api, nothing else), this diff allowed me to retain HMR functionality but not reload the page:

diff --git a/dist/client/client.mjs b/dist/client/client.mjs
index e944ed731f936d5a35fe8368ce55c0518ade537e..ba56411cce8960a5dd0c47bf9b3bb13a8b8498f6 100644
--- a/dist/client/client.mjs
+++ b/dist/client/client.mjs
@@ -483,7 +483,7 @@ function setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen) {
         notifyListeners('vite:ws:disconnect', { webSocket: socket });
         console.log(`[vite] server connection lost. polling for restart...`);
         await waitForSuccessfulPing(protocol, hostAndPath);
-        location.reload();
+        setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen)
     });
     return socket;
 }

It's not clear if this would break other things but it seems to work just fine for me.

psychedelicious avatar Jan 09 '24 00:01 psychedelicious

any update on this?

mxvsh avatar Oct 02 '24 18:10 mxvsh

+1 on wondering if this will be fixed

jeanlescure avatar Nov 06 '24 19:11 jeanlescure

+1 so many issues and pull requests have been created, but they are being closed and rejected, and this error is still being repeated. Why?

a-velesov avatar Dec 04 '24 21:12 a-velesov

It seems like first part of the change ("Change the ping to use WebSocket instead of HTTP") is already here

But I’m failing to understand where we should handle WebSocket connection in createWebSocketServer implementation. From what I can see, that is already handled (vite-ping references).

niksy avatar Feb 15 '25 12:02 niksy

@niksy What I explained at https://github.com/vitejs/vite/issues/5675#issuecomment-1345588419 was implemented by #17891

sapphi-red avatar Feb 18 '25 07:02 sapphi-red

@sapphi-red I’m referencing this situation; it doesn’t seem to work.

What happens is Nodemon restarts whole application (including Vite dev server) and client keeps pinging but after some time it decides to reload whole page instead of using new WebSocket connection.

Image

niksy avatar Feb 18 '25 12:02 niksy

If the connection is lost and the ping successes later on, Vite will reload the page. This is an intended behavior. Vite cannot just connect the WebSocket, because the Vite cannot know whether the page content that is already loaded is stale or not.

I don't remember why I said "For that case, I think implementing https://github.com/vitejs/vite/issues/5675 would work." at https://github.com/vitejs/vite/pull/9007#issuecomment-1345190952. I should have said "For https://github.com/vitejs/vite/pull/9007#issuecomment-1179560998" instead of "For that case".

sapphi-red avatar Feb 18 '25 13:02 sapphi-red

If the connection is lost and the ping successes later on, Vite will reload the page. This is an intended behavior. Vite cannot just connect the WebSocket, because the Vite cannot know whether the page content that is already loaded is stale or not.

So give us some options.

  1. Reload the page (like now)
  2. Assume it's not stale and attempt to do a hot reload anyway
  3. Print a warning to the console that a full page reload is required but do nothing

mnpenner avatar Feb 19 '25 04:02 mnpenner

Assume it's not stale and attempt to do a hot reload anyway

Is this anyhow possible? Like try to create fresh connection and use it for subsequent interactions.

niksy avatar Feb 19 '25 07:02 niksy

Assume it's not stale and attempt to do a hot reload anyway

Is this anyhow possible? Like try to create fresh connection and use it for subsequent interactions.

Dunno. Clearly it's reconnecting if it's forcing a refresh, whether or not that can technically be used for subsequent interactions I'm not sure. Maybe there's some kind of state that's kept in memory and it's flushed when Vite dies so it's not possible. I'm really not sure how the internals work. But if it's not possible, I'd still prefer (3) over (1)

mnpenner avatar Feb 20 '25 02:02 mnpenner

I'm debugging in webview on iOS and this problem is driving me crazy...

YowaiCoder avatar Mar 19 '25 07:03 YowaiCoder

Will stop HMR help? I just don’t want the page to reload, don’t care if it is stale or not.

sep2 avatar May 03 '25 02:05 sep2

I'm debugging in webview on iOS and this problem is driving me crazy...

We have found a way. see https://github.com/vitejs/vite/issues/18489#issuecomment-2848404974

sep2 avatar May 03 '25 03:05 sep2

I'm debugging in webview on iOS and this problem is driving me crazy...

We have found a way. see #18489 (comment)

I used the same approach, but now I'm moving to rsbuild, it's much more sane. :)

YowaiCoder avatar May 15 '25 01:05 YowaiCoder

Probably we can keep the last send message on the server and the last received message on the client. Then, we can avoid the reload if the value is same.

sapphi-red avatar May 29 '25 14:05 sapphi-red

Relevant issue - https://github.com/vitejs/vite/issues/17858 (@nefcanto)

I just had the worst day today, spending more than 15/30 seconds on a breakpoint causes a connection loss and reload, and leaves dev tools frozen.

I decided to just use another server. (python http.server)

Angular ... Vite did you switch to this junk?

olfek avatar Jul 13 '25 22:07 olfek

Throwing an error on vite:ws:disconnect can prevent page from reloading

  import.meta.hot.on('vite:ws:disconnect', () => {
    throw new Error('Connection lost! Please refresh the page manually');
  });

Zzzen avatar Aug 15 '25 08:08 Zzzen