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
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:
{
"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.
Source: vitejs/vite