Skip to content

Commit 9ce3036

Browse files
sapphi-redbluwy
andauthored
feat: rename server.hmr options to server.ws options (#21357)
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
1 parent 4417b4f commit 9ce3036

10 files changed

Lines changed: 305 additions & 46 deletions

File tree

docs/config/server-options.md

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,45 @@ Specify server response headers.
181181

182182
## server.hmr
183183

184-
- **Type:** `boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }`
184+
- **Type:** `boolean | { overlay?: boolean }`
185185

186-
Disable or configure HMR connection (in cases where the HMR websocket must use a different address from the http server).
186+
Disable or configure HMR behavior.
187187

188188
Set `server.hmr.overlay` to `false` to disable the server error overlay.
189189

190-
`protocol` sets the WebSocket protocol used for the HMR connection: `ws` (WebSocket) or `wss` (WebSocket Secure).
190+
::: warning Deprecated Options
191191

192-
`clientPort` is an advanced option that overrides the port only on the client side, allowing you to serve the websocket on a different port than the client code looks for it on.
192+
The WebSocket-related options (`protocol`, `host`, `port`, `path`, `clientPort`, `timeout`, `server`) are deprecated. Use [`server.ws`](#server-ws) instead. These options are automatically synced, so existing configurations will continue to work.
193193

194-
When `server.hmr.server` is defined, Vite will process the HMR connection requests through the provided server. If not in middleware mode, Vite will attempt to process HMR connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port.
194+
:::
195+
196+
## server.ws
197+
198+
- **Type:** `false | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, clientPort?: number, server?: Server }`
199+
200+
Configure WebSocket connection options. Set to `false` to disable the WebSocket connection entirely.
201+
202+
- `protocol` - WebSocket protocol (`ws` or `wss`)
203+
- `host` - WebSocket server host
204+
- `port` - WebSocket server port
205+
- `path` - WebSocket path
206+
- `clientPort` - Override the port on the client side, allowing you to serve the websocket on a different port than the client code looks for it on
207+
- `timeout` - Connection timeout in milliseconds (default: 30000)
208+
- `server` - Use a custom HTTP server for WebSocket connections
209+
210+
When `server.ws.server` is defined, Vite will process the WebSocket connection requests through the provided server. If not in middleware mode, Vite will attempt to process WebSocket connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port.
211+
212+
```js
213+
export default defineConfig({
214+
server: {
215+
ws: {
216+
protocol: 'wss',
217+
host: 'localhost',
218+
port: 3001,
219+
},
220+
},
221+
})
222+
```
195223

196224
Check out [`vite-setup-catalogue`](https://github.com/sapphi-red/vite-setup-catalogue) for some examples.
197225

@@ -200,14 +228,14 @@ Check out [`vite-setup-catalogue`](https://github.com/sapphi-red/vite-setup-cata
200228
With the default configuration, reverse proxies in front of Vite are expected to support proxying WebSocket. If the Vite HMR client fails to connect WebSocket, the client will fall back to connecting the WebSocket directly to the Vite HMR server bypassing the reverse proxies:
201229

202230
```
203-
Direct websocket connection fallback. Check out https://vite.dev/config/server-options.html#server-hmr to remove the previous connection error.
231+
Direct websocket connection fallback. Check out https://vite.dev/config/server-options.html#server-ws to remove the previous connection error.
204232
```
205233

206234
The error that appears in the Browser when the fallback happens can be ignored. To avoid the error by directly bypassing reverse proxies, you could either:
207235

208236
- configure the reverse proxy to proxy WebSocket too
209-
- set [`server.strictPort = true`](#server-strictport) and set `server.hmr.clientPort` to the same value with `server.port`
210-
- set `server.hmr.port` to a different value from [`server.port`](#server-port)
237+
- set [`server.strictPort = true`](#server-strictport) and set `server.ws.clientPort` to the same value with `server.port`
238+
- set `server.ws.port` to a different value from [`server.port`](#server-port)
211239

212240
:::
213241

packages/vite/src/node/__tests__/config.spec.ts

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import http from 'node:http'
22
import os from 'node:os'
33
import path from 'node:path'
44
import fs from 'node:fs'
5-
import { afterEach, describe, expect, test, vi } from 'vitest'
5+
import { afterEach, assert, describe, expect, test, vi } from 'vitest'
66
import type { InlineConfig, PluginOption } from '..'
77
import type { UserConfig, UserConfigExport } from '../config'
88
import { defineConfig, loadConfigFromFile, resolveConfig } from '../config'
@@ -293,7 +293,11 @@ describe('mergeConfig', () => {
293293
}
294294

295295
const mergedConfig = {
296-
server: { allowedHosts: true },
296+
server: {
297+
allowedHosts: true,
298+
hmr: expect.any(Object),
299+
ws: expect.any(Object),
300+
},
297301
}
298302

299303
expect(mergeConfig(baseConfig, newConfig)).toEqual(mergedConfig)
@@ -665,6 +669,114 @@ describe('mergeConfig', () => {
665669
).toBe('browser')
666670
})
667671

672+
test('syncs `server.hmr.*` to `server.ws.*`', () => {
673+
const baseConfig = defineConfig({
674+
server: {
675+
hmr: {
676+
protocol: 'wss',
677+
host: 'example.com',
678+
port: 3001,
679+
clientPort: 443,
680+
path: '/ws',
681+
timeout: 60000,
682+
},
683+
},
684+
})
685+
686+
const mergedConfig = mergeConfig(
687+
{
688+
server: { ws: {} },
689+
},
690+
baseConfig,
691+
)
692+
693+
expect(mergedConfig.server.ws).toStrictEqual({
694+
protocol: 'wss',
695+
host: 'example.com',
696+
port: 3001,
697+
clientPort: 443,
698+
path: '/ws',
699+
timeout: 60000,
700+
})
701+
expect(mergedConfig.server.hmr).toStrictEqual({
702+
protocol: 'wss',
703+
host: 'example.com',
704+
port: 3001,
705+
clientPort: 443,
706+
path: '/ws',
707+
timeout: 60000,
708+
server: undefined,
709+
})
710+
})
711+
712+
test('mergeConfig works with `server.ws` and `server.hmr`', () => {
713+
const baseConfig = defineConfig({
714+
server: {
715+
ws: {
716+
host: 'old-host.com',
717+
port: 3001,
718+
},
719+
},
720+
})
721+
722+
const newConfig = defineConfig({
723+
server: {
724+
hmr: {
725+
host: 'new-host.com',
726+
port: 3002,
727+
},
728+
},
729+
})
730+
731+
const mergedConfig = mergeConfig(baseConfig, newConfig)
732+
733+
expect(mergedConfig.server.ws.host).toBe('new-host.com')
734+
expect(mergedConfig.server.hmr.host).toBe('new-host.com')
735+
expect(mergedConfig.server.ws.port).toBe(3002)
736+
expect(mergedConfig.server.hmr.port).toBe(3002)
737+
})
738+
739+
test('`server.hmr.overlay` is not mapped to `server.ws.overlay`', () => {
740+
const config = mergeConfig(
741+
{},
742+
defineConfig({
743+
server: {
744+
hmr: {
745+
overlay: false,
746+
},
747+
},
748+
}),
749+
)
750+
751+
expect(config.server.hmr.overlay).toBe(false)
752+
// overlay should not be synced to ws
753+
expect(config.server.ws?.overlay).toBeUndefined()
754+
})
755+
756+
test('resolveConfig properly syncs hmr and ws', async () => {
757+
const config = await resolveConfig(
758+
{
759+
server: {
760+
hmr: {
761+
host: 'test-host.com',
762+
port: 4000,
763+
},
764+
},
765+
},
766+
'serve',
767+
)
768+
769+
assert(typeof config.server.ws === 'object')
770+
expect(config.server.ws.host).toBe('test-host.com')
771+
expect(config.server.ws.port).toBe(4000)
772+
773+
assert(typeof config.server.hmr === 'object')
774+
config.server.hmr!.host = 'new-host.com'
775+
776+
expect(config.server.ws.host).toBe('new-host.com')
777+
expect(config.server.hmr.host).toBe('new-host.com')
778+
})
779+
668780
describe('later plugin can read `rollupOptions` set via `rolldownOptions` in earlier plugin', () => {
669781
test('top-level config', async () => {
670782
expect.assertions(2)

packages/vite/src/node/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export type {
231231
} from './server/transformRequest'
232232
export type {
233233
HmrOptions,
234+
WsOptions,
234235
HmrContext,
235236
HotUpdateOptions,
236237
HotChannelListener,

packages/vite/src/node/plugins/clientInjections.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,29 +78,30 @@ async function createClientConfigValueReplacer(
7878

7979
const serverHost = `${resolvedServerHostname}:${resolvedServerPort}${devBase}`
8080

81-
let hmrConfig = config.server.hmr
82-
hmrConfig = isObject(hmrConfig) ? hmrConfig : undefined
83-
const host = hmrConfig?.host || null
84-
const protocol = hmrConfig?.protocol || null
85-
const timeout = hmrConfig?.timeout || 30000
86-
const overlay = hmrConfig?.overlay !== false
87-
const isHmrServerSpecified = !!hmrConfig?.server
81+
const wsConfig = isObject(config.server.ws) ? config.server.ws : undefined
82+
const host = wsConfig?.host || null
83+
const protocol = wsConfig?.protocol || null
84+
const timeout = wsConfig?.timeout || 30000
85+
const isWsServerSpecified = !!wsConfig?.server
8886
const hmrConfigName = path.basename(config.configFile || 'vite.config.js')
8987

90-
// hmr.clientPort -> hmr.port
91-
// -> (24678 if middleware mode and HMR server is not specified) -> new URL(import.meta.url).port
92-
let port = hmrConfig?.clientPort || hmrConfig?.port || null
93-
if (config.server.middlewareMode && !isHmrServerSpecified) {
88+
const hmrConfig = isObject(config.server.hmr) ? config.server.hmr : undefined
89+
const overlay = hmrConfig?.overlay !== false
90+
91+
// ws.clientPort -> ws.port
92+
// -> (24678 if middleware mode and WS server is not specified) -> new URL(import.meta.url).port
93+
let port = wsConfig?.clientPort || wsConfig?.port || null
94+
if (config.server.middlewareMode && !isWsServerSpecified) {
9495
port ||= 24678
9596
}
9697

97-
let directTarget = hmrConfig?.host || resolvedServerHostname
98-
directTarget += `:${hmrConfig?.port || resolvedServerPort}`
98+
let directTarget = wsConfig?.host || resolvedServerHostname
99+
directTarget += `:${wsConfig?.port || resolvedServerPort}`
99100
directTarget += devBase
100101

101102
let hmrBase = devBase
102-
if (hmrConfig?.path) {
103-
hmrBase = path.posix.join(hmrBase, hmrConfig.path)
103+
if (wsConfig?.path) {
104+
hmrBase = path.posix.join(hmrBase, wsConfig.path)
104105
}
105106

106107
const modeReplacement = escapeReplacement(config.mode)

packages/vite/src/node/server/hmr.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,45 @@ const whitespaceRE = /\s/
4545

4646
const normalizedClientDir = normalizePath(CLIENT_DIR)
4747

48+
export interface WsOptions {
49+
protocol?: string
50+
host?: string
51+
port?: number
52+
clientPort?: number
53+
path?: string
54+
timeout?: number
55+
server?: HttpServer
56+
}
57+
4858
export interface HmrOptions {
59+
/**
60+
* @deprecated Use `server.ws.protocol` instead.
61+
*/
4962
protocol?: string
63+
/**
64+
* @deprecated Use `server.ws.host` instead.
65+
*/
5066
host?: string
67+
/**
68+
* @deprecated Use `server.ws.port` instead.
69+
*/
5170
port?: number
71+
/**
72+
* @deprecated Use `server.ws.clientPort` instead.
73+
*/
5274
clientPort?: number
75+
/**
76+
* @deprecated Use `server.ws.path` instead.
77+
*/
5378
path?: string
79+
/**
80+
* @deprecated Use `server.ws.timeout` instead.
81+
*/
5482
timeout?: number
5583
overlay?: boolean
84+
/**
85+
* @deprecated Use `server.ws.server` instead.
86+
*/
5687
server?: HttpServer
5788
}
5889

packages/vite/src/node/server/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
normalizePath,
4343
resolveHostname,
4444
resolveServerUrls,
45+
setupHmrWsOptionCompat,
4546
setupSIGTERMListener,
4647
teardownSIGTERMListener,
4748
} from '../utils'
@@ -98,7 +99,7 @@ import { ModuleGraph } from './mixedModuleGraph'
9899
import type { ModuleNode } from './mixedModuleGraph'
99100
import { notFoundMiddleware } from './middlewares/notFound'
100101
import { errorMiddleware } from './middlewares/error'
101-
import type { HmrOptions, NormalizedHotChannel } from './hmr'
102+
import type { HmrOptions, NormalizedHotChannel, WsOptions } from './hmr'
102103
import { handleHMRUpdate, updateModules } from './hmr'
103104
import { openBrowser as _openBrowser } from './openBrowser'
104105
import type { TransformOptions, TransformResult } from './transformRequest'
@@ -117,10 +118,10 @@ export interface ServerOptions extends CommonServerOptions {
117118
*/
118119
hmr?: HmrOptions | boolean
119120
/**
120-
* Do not start the websocket connection.
121-
* @experimental
121+
* Configure WebSocket connection options.
122+
* Set to `false` to disable the WebSocket server and connection.
122123
*/
123-
ws?: false
124+
ws?: WsOptions | false
124125
/**
125126
* Warm-up files to transform and cache the results in advance. This improves the
126127
* initial page load during server starts and prevents transform waterfalls.
@@ -1204,6 +1205,8 @@ export async function resolveServerOptions(
12041205
raw ?? {},
12051206
)
12061207

1208+
setupHmrWsOptionCompat(_server)
1209+
12071210
const server: ResolvedServerOptions = {
12081211
..._server,
12091212
fs: {

packages/vite/src/node/server/middlewares/__tests__/hostCheck.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ test('getAdditionalAllowedHosts', async () => {
55
const actual = getAdditionalAllowedHosts(
66
{
77
host: 'vite.host.example.com',
8-
hmr: {
8+
ws: {
99
host: 'vite.hmr-host.example.com',
1010
},
1111
origin: 'http://vite.origin.example.com:5173',

packages/vite/src/node/server/middlewares/hostCheck.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Connect } from '#dep-types/connect'
33
import type { ResolvedPreviewOptions, ResolvedServerOptions } from '../..'
44

55
export function getAdditionalAllowedHosts(
6-
resolvedServerOptions: Pick<ResolvedServerOptions, 'host' | 'hmr' | 'origin'>,
6+
resolvedServerOptions: Pick<ResolvedServerOptions, 'host' | 'ws' | 'origin'>,
77
resolvedPreviewOptions: Pick<ResolvedPreviewOptions, 'host'>,
88
): string[] {
99
const list = []
@@ -17,10 +17,10 @@ export function getAdditionalAllowedHosts(
1717
list.push(resolvedServerOptions.host)
1818
}
1919
if (
20-
typeof resolvedServerOptions.hmr === 'object' &&
21-
resolvedServerOptions.hmr.host
20+
typeof resolvedServerOptions.ws === 'object' &&
21+
resolvedServerOptions.ws.host
2222
) {
23-
list.push(resolvedServerOptions.hmr.host)
23+
list.push(resolvedServerOptions.ws.host)
2424
}
2525
if (
2626
typeof resolvedPreviewOptions.host === 'string' &&

0 commit comments

Comments
 (0)