diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 6623a6fed92f6e..f4f091b1eee6a9 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1098,6 +1098,32 @@ describe('mergeConfig', () => { expect(mergedConfig.server.hmr).toBeTruthy() }) + test('does not warn about ignored `server.hmr.server` during preview', async () => { + const warnFn = vi.fn() + const originalWarn = console.warn + console.warn = warnFn + try { + await resolveConfig( + { + configFile: false, + server: { + ws: false, + hmr: { + server: http.createServer(), + }, + }, + } as InlineConfig, + 'serve', + 'production', + 'production', + true, + ) + } finally { + console.warn = originalWarn + } + expect(warnFn).not.toHaveBeenCalled() + }) + test('resolveConfig properly syncs hmr and ws', async () => { const config = await resolveConfig( { @@ -1122,6 +1148,130 @@ describe('mergeConfig', () => { expect(config.server.hmr.host).toBe('new-host.com') }) + test('warns when `server.hmr.server` is ignored due to `server.ws: false`', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const trace = vi.spyOn(console, 'trace').mockImplementation(() => {}) + + try { + // a fresh module instance has a fresh "already warned" state + vi.resetModules() + const { resolveConfig } = await import('../config') + await resolveConfig( + { + configFile: false, + customLogger: createLogger('silent'), + server: { + ws: false, + hmr: { + server: http.createServer(), + }, + }, + }, + 'serve', + ) + + const reported = [...warn.mock.calls, ...trace.mock.calls] + .map((args) => String(args[0])) + .filter((message) => message.includes('`server.hmr.server` is ignored')) + + expect(reported).toHaveLength(1) + } finally { + warn.mockRestore() + trace.mockRestore() + } + }) + + test('does not warn for `server.hmr` options that `server.ws: false` implies are ignored', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + try { + vi.resetModules() + const { resolveConfig } = await import('../config') + await resolveConfig( + { + configFile: false, + customLogger: createLogger('silent'), + server: { + ws: false, + hmr: { + host: 'test-host.com', + port: 4000, + }, + }, + }, + 'serve', + ) + + const reported = warn.mock.calls + .map((args) => String(args[0])) + .filter((message) => message.includes('is ignored')) + + expect(reported).toHaveLength(0) + } finally { + warn.mockRestore() + } + }) + + test('does not warn when `server.hmr` has no HMR server', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + try { + vi.resetModules() + const { resolveConfig } = await import('../config') + await resolveConfig( + { + configFile: false, + customLogger: createLogger('silent'), + server: { + ws: false, + hmr: { + overlay: false, + }, + }, + }, + 'serve', + ) + + const reported = warn.mock.calls + .map((args) => String(args[0])) + .filter((message) => message.includes('is ignored')) + + expect(reported).toHaveLength(0) + } finally { + warn.mockRestore() + } + }) + + test('does not warn for `server.hmr.server` on build', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + try { + vi.resetModules() + const { resolveConfig } = await import('../config') + await resolveConfig( + { + configFile: false, + customLogger: createLogger('silent'), + server: { + ws: false, + hmr: { + server: http.createServer(), + }, + }, + }, + 'build', + ) + + const reported = warn.mock.calls + .map((args) => String(args[0])) + .filter((message) => message.includes('is ignored')) + + expect(reported).toHaveLength(0) + } finally { + warn.mockRestore() + } + }) + describe('later plugin can read `rollupOptions` set via `rolldownOptions` in earlier plugin', () => { test('top-level config', async () => { expect.assertions(2) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 41f72966f9d666..fbd468edf547ca 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -1920,7 +1920,13 @@ export async function resolveConfig( : '' const input = normalizeInput(config.input) - const server = await resolveServerOptions(resolvedRoot, config.server, logger) + const server = await resolveServerOptions( + resolvedRoot, + config.server, + logger, + command, + isPreview, + ) const builder = resolveBuilderOptions(config.builder) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 2779716cbc5899..53c31eb00ce955 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -1258,6 +1258,8 @@ export async function resolveServerOptions( root: string, raw: ServerOptions | undefined, logger: Logger, + command: 'build' | 'serve' = 'serve', + isPreview = false, ): Promise { const _server = mergeWithDefaults( { @@ -1268,7 +1270,7 @@ export async function resolveServerOptions( raw ?? {}, ) - setupHmrWsOptionCompat(_server) + setupHmrWsOptionCompat(_server, command, isPreview) const workspaceRoot = searchForWorkspaceRoot(root) const server: ResolvedServerOptions = { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index c587349591a473..72eea777ceb36b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1424,10 +1424,46 @@ const hmrWsOptionsDeprecationCall = /* @__PURE__ */ (() => { } })() +const hmrServerIgnoredCall = /* @__PURE__ */ (() => { + let logged = false + return () => { + if (logged) return + logged = true + const method = process.env.VITE_DEPRECATION_TRACE ? 'trace' : 'warn' + // eslint-disable-next-line no-console + console[method]( + '`server.ws` is `false`, so `server.hmr.server` is ignored. ' + + '`server.hmr.server` is the transport used for HMR, which `server.ws: false` turns off. ' + + 'Remove `server.ws: false`, or use `server.ws.server` instead.', + ) + } +})() + export function setupHmrWsOptionCompat( serverConfig: Pick, + command?: 'build' | 'serve', + isPreview = false, ): void { - if (serverConfig.hmr === false || serverConfig.ws === false) { + if (serverConfig.hmr === false) { + return + } + if (serverConfig.ws === false) { + // `server.ws: false` turns the WebSocket server off entirely, so the deprecated + // `server.hmr.*` options that are otherwise synced onto `server.ws` cannot be + // honoured here. Only `server.hmr.server` is worth reporting: it is the HMR + // transport, not a socket parameter, so losing it is not implied by `server.ws: false` + // the way losing e.g. `server.hmr.port` is. + // The warning is only meaningful when a dev server is started, so it is skipped for + // `vite build`, for `vite preview` (which serves static files and has no HMR), and + // for bare `mergeConfig` (which passes no `command`). + if ( + command === 'serve' && + !isPreview && + isObject(serverConfig.hmr) && + serverConfig.hmr.server !== undefined + ) { + hmrServerIgnoredCall() + } return } if (serverConfig.hmr === true) {