Repository navigation
Description
Describe the bug
With experimental.bundledDev, nothing is ever removed from BundledDev.memoryFiles until the server closes.
storeOutputFilesonly callsmemoryFiles.set.getRolldownOptionsforceschunkFileNames: 'assets/[name]-[hash].js'andassetFileNames: 'assets/[name]-[hash][extname]'.
So every rebuild that changes a chunk stores it, and its source map, under a new name, next to all the previous copies. HMR patches (hmr_patch_N.js + .map) are never removed either. Every old file stays served.
There are two ways to hit it.
- A chunk shared between entries (multiple
rolldownOptions.inputentries, ordevMode: { lazy: false }with dynamic imports). Each edit to a module in a shared chunk adds that chunk and its map again, plus every hashed chunk that imports it, because the hash change cascades. In our app, with ~230 entries, 20 edits to a module most entries import tookmemoryFilesfrom 1,919 to 28,479 files, and the process RSS from 3.9 GB to 12.1 GB. - The default config with a page open. Each edit adds
hmr_patch_N.jsandhmr_patch_N.js.map. That's slower growth, but it's unbounded for a long-running session.
Expected: memoryFiles holds the latest build output, plus the patches that are still pending.
Actual: it holds every file ever emitted.
storeOutputFiles has // NOTE: don't clear memoryFiles here as incremental build reuses the files. In the reproduction, though, each rebuild hands storeOutputFiles the complete output, unchanged entries included. A possible fix:
- Remember the previous output's file names, and delete the ones the new output does not contain.
- Delete an HMR patch or lazy payload once the client reports it delivered (
vite:bundled-dev:payload-delivered), or keep only the last few.
A workaround for build output, not for HMR patches: a plugin that removes the hash, so each rebuild overwrites the same keys:
{
name: 'unhashed-dev-chunks',
outputOptions: options => ({ ...options, chunkFileNames: 'assets/[name].js', assetFileNames: 'assets/[name][extname]' }),
}Reproduction
Steps to reproduce
Case 1, a chunk shared between two entries (scripted):
npm installnpm run repro
The script uses bundledDev.memoryFiles and devEngine internals only to count the files and to wait for each rebuild deterministically. It makes five code edits to src/shared.js, then restores it.
Case 2, the default config:
npm run dev- Open http://localhost:5173/
- Edit
src/shared.jsand save, three times, with different code each time. curl -i http://localhost:5173/hmr_patch_0.jsreturns 200, as dohmr_patch_1.jsandhmr_patch_2.js. All of them stay inmemoryFilesuntil the server closes.
System Info
System:
OS: Windows 11 10.0.26100
CPU: (18) x64 Intel(R) Core(TM) Ultra 5 135H
Memory: 9.03 GB / 31.46 GB
Binaries:
Node: 24.16.0
npm: 11.13.0
pnpm: 11.11.0
Browsers:
Chrome: 155.0.8059.40
Edge: Chromium (152.0.4191.53)
npmPackages:
vite: 8.3.4 => 8.3.4
rolldown: 1.2.14 (dependency of vite)Used Package Manager
npm
Logs
npm run repro
initial build: 6 files
after edit 1: 8 files
after edit 2: 10 files
after edit 3: 12 files
after edit 4: 14 files
after edit 5: 16 files
assets/main.js
assets/other.js
assets/shared-1euN4Vmf.js
assets/main.js.map
assets/other.js.map
assets/shared-1euN4Vmf.js.map
assets/shared-Dt-zbz8f.js
assets/shared-Dt-zbz8f.js.map
assets/shared-BkId7QYW.js
assets/shared-BkId7QYW.js.map
assets/shared-DB4C89Nn.js
assets/shared-DB4C89Nn.js.map
assets/shared-BcUH7Yx-.js
assets/shared-BcUH7Yx-.js.map
assets/shared-CGgg6g2c.js
assets/shared-CGgg6g2c.js.map
GET /assets/shared-1euN4Vmf.js (initial build) -> 200, still the original codeCase 2: memoryFiles after three edits with the page open
bundledDevClient.mjs, @rolldown/experimental-runtime.mjs, @vite/client, @vite/env, assets/index.js, assets/index.js.map,
index.html, hmr_patch_0.js, hmr_patch_0.js.map, hmr_patch_1.js, hmr_patch_1.js.map, hmr_patch_2.js, hmr_patch_2.js.map
GET /hmr_patch_1.js -> 200Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to vuejs/core instead.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
Activity
fix(bundled-dev): evict stale build output and delivered payloads fro…
rob2244 commentedon Nov 5, 2026
A third way to hit this, with no edit: lazy compiles. Each lazy entry that a page opens (
/@vite/lazy?id=...) starts a full Rolldown build (rolldown/rolldown#11489). The new entry changes how modules are split into shared chunks, so most chunks get a new hash, and the earlier files stay inmemoryFiles. Each one keeps its Rolldown output object, with its native code, map and module list, alive until the server closes.Reproduction: https://github.com/kater-ai/vite-issues-repro/tree/main/vite-memoryfiles (Vite 8.3.5, 40 lazy routes, a
WeakRefon each output file):In our app (about 900 modules at start), each new lazy entry kept about 120 MB, and the dev server passed 3 GB after 24 entries.
A note on the fix: a page that still runs an earlier build asks for that build's hashed chunks, and their imports use the export names that that build mangled. So a file deleted after the next build, or a name without a hash, gives that page a 404 or a wrong import until it reloads. What we use for now: before
storeOutputFilesof a new build, each JS file of the previous build that the new build does not name is read one time (memoryFiles.get(name)), so it keeps only its text, and each such.mapfile is deleted. After 24 lazy entries, the footprint was 2849 MB in place of 3413 MB, and the native part 1896 MB in place of 2918 MB.fix(dev): long-running dev servers no longer leak memory when other w…