Skip to content

fix: infer client port from page location#7463

Merged
patak-cat merged 2 commits intovitejs:mainfrom
lukashass:infer-client-port-from-page-location
Mar 29, 2022
Merged

fix: infer client port from page location#7463
patak-cat merged 2 commits intovitejs:mainfrom
lukashass:infer-client-port-from-page-location

Conversation

@lukashass
Copy link
Contributor

@lukashass lukashass commented Mar 26, 2022

Description

Closes #3093 and closes #5153.

By inferring the HMR client port from the current page location, vite can be run behind reverse proxies without extra configuration.

Partly reverts #6624.

Additional context

TODO

  • test with reverse proxy 🙈
  • check if reverting feat(config): hmr add disable port config #6624 is okay (this possibly covers the same use-case)
  • make sure path.posix.normalize really did not have any meaningful effect
  • check if this closes other issues (possibly more, but some might not be exclusively this problem)
  • add dedicated tests?

Builds partly on #4168

Possible follow-up: infer defaults from document.currentScript and/or import.meta.url


What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

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

  • Read the Contributing Guidelines.
  • Read the Pull Request Guidelines and follow the Commit Convention.
  • Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
  • Provide a description in this PR that addresses what the PR is solving, or reference the issue that it solves (e.g. fixes #123).
  • Ideally, include relevant tests that fail without this PR but pass with it.

Comment on lines +28 to +30
const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${
__HMR_PORT__ || location.port
}${__HMR_BASE__}`
Copy link
Member

Choose a reason for hiding this comment

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

I think we should still support hmr.port: false, let's keep it to merge this PR and we can discuss for 3.0 if we want to deprecate the option. I don't see much harm in keeping it now that we already have it. @jeoy, I'm interested to hear your opinion, and feedback in case you could test this PR for your use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@patak-dev Sure I will try to keep it.

I have to admit I don't fully get the use-case of #6624, but I thought if it is really not needed it might be good to remove now, since it was only released in beta versions 🤔 .
And I agree with #6624 (comment) that the port type number | false is a bit weird.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, you are right. I didn't consider that this option was not still released in a stable version. Good point. @jeoy if this PR covers your use case, I agree with @lukashass here that we should revert #6624 until we find a valid use case for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Furthermore I just discovered that setting hmr.port: false currently doesn't have the desired effect, since port will default to config.server.port in this line:

port = String(port || options.port || config.server.port!)

@patak-cat
Copy link
Member

This looks good to me @lukashass, thanks for working on the PR.

@mehulmpt, @edorgeville maybe you could also check this PR is working for you?

@patak-cat patak-cat added this to the 2.9 milestone Mar 26, 2022
@patak-cat patak-cat added the p3-minor-bug An edge case that only affects very specific usage (priority) label Mar 26, 2022
@lukashass
Copy link
Contributor Author

@patak-dev Can you rerun the CI?
I'm unsure if the test failure is related to this PR. The tests are working locally.

@patak-cat patak-cat merged commit 93ae8e5 into vitejs:main Mar 29, 2022
@patak-cat
Copy link
Member

See comment #7463 (comment), @jeoy let's merge this one to avoid introducing a new option if it isn't needed. Please create a new PR if we're missing something here to add the option back and let's re-discuss it. Thanks!

@lukashass thanks again for digging into this ❤️

@Artur-
Copy link
Contributor

Artur- commented Mar 30, 2022

So does this make it impossible for our setup to work when we have a Java server running on port 8080 and a Vite server running on a random port and want the browser to make the websocket connection directly to the Vite server?

@patak-cat
Copy link
Member

@Artur- thanks for testing, let's revert this PR so we can release 2.9 today. @lukashass we can try this fix again taking into account @Artur- case later.

patak-cat added a commit that referenced this pull request Mar 30, 2022
@patak-cat patak-cat mentioned this pull request Mar 30, 2022
4 tasks
patak-cat added a commit that referenced this pull request Mar 30, 2022
@lukashass
Copy link
Contributor Author

lukashass commented Mar 30, 2022

@Artur- Are you integrating the vite page in an iframe? I think to cover that case, we have to infer from import.meta.url instead of location

Edit: I just found out that vite works just fine in an iframe, since location is scoped per iframe.

@Artur-
Copy link
Contributor

Artur- commented Mar 30, 2022

No iframe. We are proxying the Vite requests through a /VAADIN/ url on a Java server so that when your app is running on localhost:8080, the browser will request localhost:8080/VAADIN/index.html -> http://localhost:8080/VAADIN/@vite/client etc. Proxying the websocket connection though seems like an unnecessary hassle in our case as the Vite server is available to the browser using localhost:12345 or whatever port it is running on. So far it has always worked fine that ws://localhost:12345 is used for the websocket connection in the browser. I guess that hmr.port: false would solve our case if it was kept around

@baileyherbert
Copy link

I'm in the same situation as @Artur-.

I often embed my live apps in subdirectories (or even within larger pages) of my server-side applications. The live apps will therefore load on http://localhost:8080 but vite is still expected to directly connect to its own port. This PR would break that.

It's easy to fix this by setting hmr.clientPort – the behavior will then revert to how it is now. I've tested this and it works fine. But it's definitely a breaking change and might cause some unexpected behavior for others.

I have two ideas on how to proceed:

  • I like the approach in feat(config): hmr add disable port config #6624 because it makes this an opt-in behavior and my existing configurations will continue to work, although I would personally want to see something a bit less ambiguous such as clientPort: 'infer'.

  • If we really want to change the default behavior (this is certainly more ideal because it matches the behavior of other devservers) then @Artur- and I can set e.g. hmr.clientPort: 12345 to include the port. It might be worth adding a case for clientPort: 0 to automatically match the port so we don't need to specify and change the same port twice.

@lbogdan
Copy link

lbogdan commented Apr 8, 2022

To @baileyherbert 's points, fixing this with an opt-in configuration doesn't really make sense, as we can already do that by using clientPort. So I strongly think this should be the default behaviour - even with the drawback of breaking a few existing environments, this might be easily mitigated, either by sticking with a previous working vite version, or configuring clientPort accordingly.

I work at CodeSandbox, and our use-case is that we reverse-proxy from an URL like https://${id}.sse.codesandbox.io/ to a vite devserver running inside a container sandbox, basically breaking HMR for all users that want to use vite inside CodeSandbox. As an aside, their initial reaction is also to blame it on us 🙂 - which only makes sense, because they expect for it to work the same as in a local environment.

A better inference, that combines both this and #6624, and might coincidentally fix @Artur- and @baileyherbert 's use-cases, is simply: if location.port is empty (''), then don't use a port for the websocket URL, otherwise use __HMR_PORT__.

So if vite is listening on localhost:12345, you're proxying to it from localhost:8080, and you're opening http://localhost:8080/ in the browser, because location.port is not empty ('8080'), we use __HMR_PORT__, and the inferred websocket URL would be ws://localhost:12345/. A weird artefact is it would break if you're trying to proxy to vite from localhost:80 - because, even if trying to open http://localhost:80/, the browser would just strip out the default http port (TIL!), and the inferred websocket URL would end up being ws://localhost/.

This would also work for our use-case - the websocket URL for https://${id}.sse.codesandbox.io/ would be wss://${id}.sse.codesandbox.io/. Although it would break for https://${id}.sse.codesandbox.io:443/, as noted above, the browser would just strip out the default https port, so it's not an issue.

@baileyherbert
Copy link

Nice idea!! That would work perfectly for me. 👍

Yesterday, I tried tunneling vite through Cloudflare Argo to quickly share a project with a colleague. I couldn't get it to connect with the vite backend on both the tunnel something.trycloudflare.com:443 and localhost:3000 simultaneously due to this constraint. This should absolutely work out of the box.

Yet, say you're running a large project on localhost:3000 and it's proxying to multiple vite projects on 3001, 3002, etc. This would also work out of the box with your proposal. I love it! Just need to make sure that clientPort can override this behavior for those who try the same thing on ports 80 or 443.

@lbogdan
Copy link

lbogdan commented Apr 8, 2022

I think we could make it work if we expose to the client the fact that clientPort was explicitly set - right now the client just gets __HMR_PORT__, without knowing if that was explicitly set with clientPort or not. So the logic would be something like:

if (clientPort explicitly set || location.port !== '') {
  use __HMR_PORT__
} else {
  don't use a port
}

@lukashass
Copy link
Contributor Author

Considering the use-cases of @Artur- and @baileyherbert, this really is about finding a better default rather than just fixing the existing behaviour 🤯

After reading your ideas I came to the following possible values for clientPort:

'auto' (default)

I really like @lbogdan's idea of only inferring the port on default ports as this will probably cover most proxy setups while only breaking very few 🤞 existing setups.

I would suggest a possible variation: (additionally) use location.hostname !== 'localhost' to decide on inferring. If you are accessing an application via localhost you probably also have direct access to the vite port, if not then your proxy can likely handle the websocket 🤔.

number

An explicit number for advanced setups. (basically what is possible now)

'infer' (convenience)

Force inferring the port even if localtion.port !== ''

This can also be done by setting the clientPort to the desired port. Hence this would only be for convenience, so you don't have to adjust this every time your proxy config changes.

'server' (convenience)

Force using vite's port for the client connection even if localtion.port === ''

This can also be done by setting the clientPort to the same port as vite. Hence this would only be for convenience, so you don't have to adjust this every time your vite port changes.

@lukashass lukashass deleted the infer-client-port-from-page-location branch February 25, 2025 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p3-minor-bug An edge case that only affects very specific usage (priority)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HMR client port should default to 443 on https Vite HMR is unusable behind reverse proxies with random port numbers for client

6 participants