Skip to content

fix(build): watch publicDir files in build watch mode - #23245

Open
ValentinYoushkevich wants to merge 1 commit into
vitejs:mainfrom
ValentinYoushkevich:fix/build-watch-public-dir
Open

fix(build): watch publicDir files in build watch mode#23245
ValentinYoushkevich wants to merge 1 commit into
vitejs:mainfrom
ValentinYoushkevich:fix/build-watch-public-dir

Conversation

@ValentinYoushkevich

Copy link
Copy Markdown

What this solves

vite build --watch doesn't rebuild when you change a file in publicDir. Rolldown watches the module graph plus whatever plugins register via addWatchFile(), and public assets are in neither — they're just copied once in renderStart.

The fix is small, because prepareOutDirPlugin already re-copies publicDir on every rebuild (watchChange() clears the rendered guard). All that was missing was making a change under publicDir trigger a rebuild at all, so buildStart now registers each public file with this.addWatchFile(). No copying logic added.

Closes #18655

Why this slipped past the existing tests: playground/assets does edit static/foo.txt under build watch and passes — but that file is imported as ?raw from index.html, so it's in the module graph, and the assertion reads the bundle rather than outDir.

Alternatives

#22667 (closed by its author) synced files straight into outDir without rebuilding. Faster, but in build checkPublicFile falls back to tryStatSync, so whether a public file exists changes the bundle itself — <img src="/icon.png">, @import '/foo.css'. A plain copy can't re-resolve those, and outDir drifts from a real build. Rebuilding keeps them identical for free.

I also tried registering the directory instead of each file. rolldown accepts it and then fires nothing, not even change:

addWatchFile(DIR .../public)
  change existing.txt : no rebuild (5s timeout)
  add    added.txt    : no rebuild (5s timeout)
  unlink existing.txt : no rebuild (5s timeout)
  change main.js      : rebuild triggered   <- control

One thing I'd like your call on

A newly added public file doesn't rebuild immediately. It didn't exist at buildStart, and Watcher has no invalidate(), so there's nothing to hook into. It does land on the next rebuild, since buildStart re-reads publicDir each time. Covering it eagerly would mean a chokidar watcher copying files directly — two different consistency guarantees in one feature, which is why I left it out. Glad to add it if you'd rather.

The other trade-off: every public edit now costs a full rebuild, emptyDir(outDir) included.

Tests

Two tests in the existing build-watch block in playground/assets, using static/bar — a public file nothing imports — asserting on dist/foo/bar on disk rather than through the page. Without the patch both time out after 30s, waiting on a rebuild that never comes; with it they pass in 1.5s. No test for the added-file case, since it's knowingly not covered.

Files in `publicDir` are copied to `outDir` instead of going through the
bundler, so they are not part of the module graph and were never watched.
`vite build --watch` copied them once at startup and never again.

Register them with `this.addWatchFile()` in `buildStart`, so that editing
or removing one triggers a rebuild. The rebuild clears the `rendered`
guard via `watchChange` and `renderStart` re-copies `publicDir`, so no
separate copy path is needed.

Closes vitejs#18655
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

build --watch doesn't watch files in public

1 participant