#23575·Vite

mergeConfig mutates server.hmr defaults and leaks values across merges

Author: januththedevCreated Sep 24, 2026Updated Sep 28, 2026

Description

mergeConfig() mutates a caller-provided server.hmr object while installing compatibility accessors, causing values from one merge to leak into later independent merges.

Reproduction

javascript
import {mergeConfig} from 'vite';

const defaults = {
  server: {
    hmr: {host: 'localhost'},
  },
};

const first = mergeConfig(defaults, {
  server: {hmr: {port: 3000}},
});

const second = mergeConfig(defaults, {
  server: {hmr: {protocol: 'wss'}},
});

console.log(Object.hasOwn(defaults.server.hmr, 'port'));
console.log(second.server.ws);

Actual output on Vite 8.3.1:

json
{
  "defaultsHasPort": true,
  "second": {
    "host": "localhost",
    "port": 3000,
    "protocol": "wss"
  }
}

Expected behavior

mergeConfig() should not mutate defaults, and an independent second merge should not inherit port from the first merge.

Additional context

setupHmrWsOptionCompat() receives the HMR object from a shallowly copied config and defines accessors on it. Those accessors close over the first result's serverConfig.ws, leaving state on the caller's defaults object.

I found no existing issue or PR covering mutation or cross-merge leakage. The current tests around HMR merging do not cover either case.