Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite HMR is unusable behind reverse proxies with random port numbers for client #3093

Closed
6 tasks done
mehulmpt opened this issue Apr 22, 2021 · 18 comments · Fixed by #3578, #7463 or #8650
Closed
6 tasks done

Vite HMR is unusable behind reverse proxies with random port numbers for client #3093

mehulmpt opened this issue Apr 22, 2021 · 18 comments · Fixed by #3578, #7463 or #8650
Labels
enhancement New feature or request feat: hmr p3-downstream-blocker Blocking the downstream ecosystem to work properly (priority)

Comments

@mehulmpt
Copy link

mehulmpt commented Apr 22, 2021

Describe the bug

Vite HMR requires us to hardcode the port numbers in the vite-config.js file for HMR. However, sometimes this is not possible IF the port number generated is random, for example - a dev server sitting behind, say ngrok exposed on the internet.
In that case, the vite HMR websocket connection will fail (as it tries to connect to the websocket on local port it is running on the system, behind reverse proxy)

Reproduction

  1. Start a vite server at port 1337
  2. Port map this dev server behind nginx or ngrok or any other reverse proxy (i.e. 1.2.3.4:9999 on internet -> 127.0.0.1:1337)
  3. There is no way to tell HMR websocket to connect to 1.2.3.4:9999 (assuming 9999 is random and might change on every run) instead of 1.2.3.4:1337 (notice: wrong port number used as it is hardcoded and replaced in the code.

Line responsible: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/clientInjections.ts#L33
This assumes that the backend server running on a port is the same as the frontend port exposed.

Possible fix: Just like HMR checks for location.hostname, we should implement an option to get location.port. Allow a function to run on client-side to determine the port number instead of having the option to only serialize it from server (basically we want window.location.port instead of server.port as a default fallback)


Before submitting the issue, please make sure you do the following

  • Read the Contributing Guidelines.
  • Read the docs.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Provide a description in this issue that describes the bug.
  • Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/vue-next instead.
  • Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
@mehulmpt mehulmpt changed the title Vite HMR is unusable behind reverse proxies Vite HMR is unusable behind reverse proxies with random port numbers for client Apr 22, 2021
@Shinigami92 Shinigami92 added enhancement New feature or request feat: hmr p2-nice-to-have Not breaking anything but nice to have (priority) labels Apr 22, 2021
Sociosarbis added a commit to Sociosarbis/vite that referenced this issue Apr 23, 2021
@nihalgonsalves
Copy link
Member

@mehulmpt Could you describe the use case for a random port number? Would it be sufficient to statically override the port?

@mehulmpt
Copy link
Author

I raised the issue because we will be using vite + vue 3 setup on Vue practice area on codedamn: https://codedamn.com/test-hands-on-lab/fXOK1UVZrTpJi5FGLTzp3

Our infrastructure maps a random port on docker host to the fixed port inside docker container (the port on which vite runs). Therefore this hardcoded setup always fails because container doesn't know which port it has been mapped on.

We are already running a patched vite repo where we simply set the port to window.location.port, but thought it would be nice if it is officially available too.

@fuzzykiller
Copy link

I’m confused. With #3578, we still have to set the port number in the config file. Regarding this issue, nothing changed at all…?

@edorgeville
Copy link

edorgeville commented Jun 9, 2021

@antfu @Shinigami92 @nihalgonsalves Could this issue be reopened? I am in the same boat as @mehulmpt :

Our infrastructure maps a random port on docker host to the fixed port inside docker container (the port on which vite runs). Therefore this hardcoded setup always fails because container doesn't know which port it has been mapped on.

@nihalgonsalves nihalgonsalves reopened this Jun 9, 2021
@speigg
Copy link

speigg commented Jul 7, 2021

I've suggested this as a comment on a related PR, but I'll suggest it again here; Can we use new URL(document.currentScript.src).hostname/port instead of location.hostname/location.port as the default hostname/port? It's more reliable to assume that the websocket server is on the same origin as the client injection script, than to assume that the websocket server is on the same origin as the current page (this would allow for things to work in a cross-origin context, e.g., testing a web component w/ vite HMR in the context of a website hosted on a different origin than that in-development web component).

@rossng
Copy link

rossng commented Jul 10, 2021

I was also caught out by this. I am using Packetriot (similar to Ngrok) the HMR websocket would not connect. This creates a really bad user experience since Vite's client will refresh the page after one second if the websocket connection fails to establish.

@speigg's PR (#4168) looks like the correct direction to go in. What problem were you having with it? I'd also like to help get this fixed.

Worth noting that Snowpack has a similar implementation to that suggested here, though it does use the bare hostname instead of the client script URL. For subpaths, there's a HMR_WEBSOCKET_URL override, which is a bit less nice than figuring it out automatically.

@GongT
Copy link

GongT commented Jul 21, 2021

I don't have random ports, but I have two pre-defined port at same time...

@vfonic
Copy link

vfonic commented Aug 22, 2021

I tried to follow the discussion here, but I'm not sure if I understood everything. Is it possible now to have HMR working through ngrok tunnel?

I tried setting the port number in the vite.config.ts, but I'm not sure I'm setting it to the correct number. How do I know which port to connect to?

Were these changes merged in some other PR:
https://github.com/vitejs/vite/pull/4168/files

I'm also not sure if I'm configuring Vite correctly:

export default defineConfig({
  server: {
    hmr: { clientPort: 443 }, // I tried 4040 and some other ports that I saw in ngrok logs such as: 57587, 57004, etc.
  },
  // ...
})

@satyajitghana
Copy link

I don't get it, why do i have to specify clientPort ?, why is it not defaulted to the port being used at client ? I recently from from CRA to Vite. CRA simple calls ${HOST}/sockjs-node and i dont have to configure much. everything would just works. but on Vite, i have to configure clientPort, and since i'm behind a reverse proxy, which listen to different ports everytime, i have to manually change clientPort to whatever the proxy gave me.

@Shinigami92
Copy link
Member

Maybe you can configure the clientPort dynamically and read it out by yourself in the config file.
If you did it, please share your solution here for others.

@KaiSpencer
Copy link

I tried to follow the discussion here, but I'm not sure if I understood everything. Is it possible now to have HMR working through ngrok tunnel?

I tried setting the port number in the vite.config.ts, but I'm not sure I'm setting it to the correct number. How do I know which port to connect to?

Were these changes merged in some other PR: https://github.com/vitejs/vite/pull/4168/files

I'm also not sure if I'm configuring Vite correctly:

export default defineConfig({
  server: {
    hmr: { clientPort: 443 }, // I tried 4040 and some other ports that I saw in ngrok logs such as: 57587, 57004, etc.
  },
  // ...
})

Hi @vfonic,

Im using a similar setup to you, setting the clientPort to the web interface port that is displayed at the end of the Web Interface address in the terminal window you started ngrok in.

@vfonic
Copy link

vfonic commented Dec 27, 2021

@KaiSpencer you're saying that websocket connection works with only ngrok and I don't need to run localtunnel as well?

@KaiSpencer
Copy link

@KaiSpencer you're saying that websocket connection works with only ngrok and I don't need to run localtunnel as well?

Yes I am just running ngrok. This seems to fix the infinite reload, but the browser console hangs at connecting and the HMR doesnt work, and requires a manual refresh to detect changes. Not ideal, but useable.

@vfonic
Copy link

vfonic commented Dec 27, 2021

Ahhh, well you could try my setup. It works, but uses two tunnels.

I just realized I posted my instructions in another issue, I can't find it now.

Here's how I configured everything:

vite.config.ts:

// ...
export default defineConfig({
// ...
  server: {
    hmr: {
      host: `some-ngrok-subdomain.loca.lt`,
      port: 443,
    },
  },
// ...

package.json:

"devDependencies": {
  "localtunnel": "2.0.1",
  "ngrok": "^4.2.2"
}

And then I start everything:

./node_modules/.bin/lt --subdomain=some-ngrok-subdomain--port=3036
./node_modules/.bin/ngrok http -subdomain=some-ngrok-subdomain

@vfonic
Copy link

vfonic commented Dec 29, 2021

This issue and App keeps refreshing with log: Server connection lost polling for restart might be talking about the same issue. I've posted my solution with two tunnels in both. I hope it helps someone.

nicks added a commit to tilt-dev/vite that referenced this issue Feb 9, 2022
…st, not the ping host

The problem with checking the ping host is that the ping host can
pass, even if the socket host fails, leading to infinite reloads.

Infinite reloads are a bad way to deal with this failure.
This makes the failure less bad.

For examples, see:
vitejs#6814
vitejs#3093
@stippi
Copy link

stippi commented Feb 18, 2022

Hi. I'm new to Vite and trying to import a test project which uses websockets. I've deployed a websocket server (basically this verbatim) to Heroku. In my project, I connect to the server using wss://name-of-app.herokuapp.com. Without Vite, this works fine. In the Vite version, running npm run dev, the local site fails to connect to the public server.

There are multiple issues here which sound possibly related, but neither the documentation nor the issues and comments are on a level that I can derive if there is something I can do to make it work and what it would be. :-\

@lbogdan
Copy link

lbogdan commented Apr 8, 2022

@patak-dev Can you please reopen this, as #7463 was reverted in #7522?

@Niputi Niputi reopened this Apr 8, 2022
@patak-dev patak-dev added p3-downstream-blocker Blocking the downstream ecosystem to work properly (priority) and removed p2-nice-to-have Not breaking anything but nice to have (priority) labels Apr 8, 2022
nicks added a commit to tilt-dev/vite that referenced this issue May 10, 2022
…st, not the ping host

The problem with checking the ping host is that the ping host can
pass, even if the socket host fails, leading to infinite reloads.

Infinite reloads are a bad way to deal with this failure.
This makes the failure less bad.

For examples, see:
vitejs#6814
vitejs#3093
@murilob1337
Copy link

murilob1337 commented Jun 24, 2022

My temporary solution for this was to use vite-plugin-replace, I hope one day it will update so it works normally.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteCommonjs } from '@mb1337/vite-plugin-commonjs'
import { vitePluginReplace } from "@mb1337/vite-plugin-replace"

// https://vitejs.dev/config/
export default defineConfig(async ({ mode, command }) => {
  return {
    plugins: [react(), viteCommonjs(), vitePluginReplace({ replacements: [{ from: /__HMR_PORT__/g, to: '__HMR_PORT__.replace(3000, location.port)' }] })],
    server: {
      host: true,
      strictPort: true,
      hmr: {
        path: '/vite'
      },
      proxy: {
        '/api': {
          target: 'http://localhost:2000'
        },
        '/socket.io': {
          target: 'http://localhost:2000',
          ws: true
        }
      }
    }
  }
})

@github-actions github-actions bot locked and limited conversation to collaborators Jul 9, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.