From 64ba3ac05ed00934374804e6feaaa60021c29c78 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 26 Jun 2026 11:55:00 +0000 Subject: [PATCH] fix(dev): set hmr server on `server.ws` (vite 8.1+) --- packages/nuxi/src/dev/utils.ts | 37 ++++++++++++++++----- packages/nuxi/test/unit/dev-hmr.spec.ts | 44 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 packages/nuxi/test/unit/dev-hmr.spec.ts diff --git a/packages/nuxi/src/dev/utils.ts b/packages/nuxi/src/dev/utils.ts index a4181596..c4920e1e 100644 --- a/packages/nuxi/src/dev/utils.ts +++ b/packages/nuxi/src/dev/utils.ts @@ -1,10 +1,10 @@ -import type { Nuxt, NuxtConfig } from '@nuxt/schema' +import type { Nuxt, NuxtConfig, ViteConfig } from '@nuxt/schema' import type { DotenvOptions } from 'c12' import type { Listener, ListenOptions } from 'listhen' import type { createDevServer } from 'nitro/builder' import type { NitroDevServer } from 'nitropack' import type { FSWatcher } from 'node:fs' -import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http' +import type { Server as HttpServer, IncomingMessage, RequestListener, ServerResponse } from 'node:http' import EventEmitter from 'node:events' import { existsSync, readdirSync, statSync, watch } from 'node:fs' @@ -116,6 +116,31 @@ export class FileChangeTracker { type NuxtWithServer = Omit & { server?: NitroDevServer | ReturnType } +type ViteServerOptions = NonNullable +type HmrOptions = Exclude + +/** + * Pin Vite's HMR websocket to the main dev server so no separate HMR port is allocated. + * vite >= 8.1 reads `server.ws`; older versions only read `server.hmr`. + */ +export function attachViteHmrServer(server: ViteServerOptions, hmrServer: HttpServer): void { + const target = server as Omit & { ws?: HmrOptions | boolean } + target.ws = { + protocol: undefined, + ...(target.ws as HmrOptions), + port: undefined, + host: undefined, + server: hmrServer, + } + target.hmr = { + protocol: undefined, + ...(target.hmr as HmrOptions), + port: undefined, + host: undefined, + server: hmrServer, + } +} + interface DevServerEventMap { 'loading:error': [error: Error] 'loading': [loadingMessage: string] @@ -349,13 +374,7 @@ export class NuxtDevServer extends EventEmitter { if (!process.env.NUXI_DISABLE_VITE_HMR) { this.#currentNuxt.hooks.hook('vite:extend', ({ config }) => { if (config.server) { - config.server.hmr = { - protocol: undefined, - ...(config.server.hmr as Exclude), - port: undefined, - host: undefined, - server: this.listener.server, - } + attachViteHmrServer(config.server, this.listener.server) } }) } diff --git a/packages/nuxi/test/unit/dev-hmr.spec.ts b/packages/nuxi/test/unit/dev-hmr.spec.ts new file mode 100644 index 00000000..a26b7936 --- /dev/null +++ b/packages/nuxi/test/unit/dev-hmr.spec.ts @@ -0,0 +1,44 @@ +import type { Server } from 'node:http' +import { describe, expect, it } from 'vitest' + +import { attachViteHmrServer } from '../../src/dev/utils' + +const hmrServer = {} as Server + +describe('attachViteHmrServer', () => { + it('pins the dev server on both ws and hmr with no separate port', () => { + const server: Record = {} + + attachViteHmrServer(server, hmrServer) + + for (const key of ['ws', 'hmr'] as const) { + expect(server[key]).toMatchObject({ + protocol: undefined, + port: undefined, + host: undefined, + server: hmrServer, + }) + } + }) + + it('preserves user-set ws and hmr options other than server/port/host', () => { + const server: Record = { + ws: { clientPort: 1234 }, + hmr: { overlay: false }, + } + + attachViteHmrServer(server, hmrServer) + + expect(server.ws).toMatchObject({ clientPort: 1234, server: hmrServer, port: undefined, host: undefined }) + expect(server.hmr).toMatchObject({ overlay: false, server: hmrServer, port: undefined, host: undefined }) + }) + + it('does not crash when ws or hmr is set to false', () => { + const server: Record = { ws: false, hmr: false } + + expect(() => attachViteHmrServer(server, hmrServer)).not.toThrow() + + expect(server.ws).toMatchObject({ server: hmrServer }) + expect(server.hmr).toMatchObject({ server: hmrServer }) + }) +})