Skip to content

fix: gate fresh:reload on SSR-relevant changes (plugin-vite)#3809

Open
fibibot wants to merge 2 commits into
mainfrom
orch/issue-64
Open

fix: gate fresh:reload on SSR-relevant changes (plugin-vite)#3809
fibibot wants to merge 2 commits into
mainfrom
orch/issue-64

Conversation

@fibibot
Copy link
Copy Markdown
Contributor

@fibibot fibibot commented May 14, 2026

Summary

Fixes #3637.

serverSnapshot attaches a viteServer.watcher.on(\"all\", ...) handler that runs alongside Vite's own watcher. After the island-file early-return and the route add/unlink branch, it was calling viteServer.ws.send(\"fresh:reload\") unconditionally — regardless of whether the changed file had anything to do with SSR.

Because Fresh attaches directly to Vite's chokidar instance, the user's server.watch.ignored config does not filter events before Fresh sees them. On Windows, journaling and delayed-close semantics mean files like Deno KV's main-shm/main-wal, *.tmp.*, and .timestamp-* emit watcher events on every write, so each KV write reloaded the page.

This change gates fresh:reload so it only fires when:

  • the changed file is in the SSR module graph (ssr.moduleGraph.getModulesByFile(filePath) non-empty), or
  • the changed file is a route file under options.routeDir.

Files unknown to either are ignored: Vite's own HMR pipeline already handles client-graph files (the island early-return below covers islands; dev_server.ts's hotUpdate covers SSR modules through Vite's normal HMR path which does honour server.watch.ignored).

The route add/unlink branch that invalidates the snapshot is preserved.

Test plan

  • New regression test vite dev - unrelated file changes do not trigger fresh:reload writes a file (main-shm) outside routes/ and the SSR module graph, then asserts no fresh:reload message arrives on the Vite HMR WebSocket, and finally confirms that editing the route file still produces fresh:reload so the gate isn't too aggressive.
  • Verified the test fails on the pre-fix code (reproduces the bug) and passes after the fix.
  • deno fmt --check, deno lint, deno task check:types, and deno test -A packages/plugin-vite/tests/ (73/73 passing) locally.

Closes bartlomieju/orchid-inbox#64

The chokidar watcher attached by `serverSnapshot` was calling
`viteServer.ws.send("fresh:reload")` on every file change, irrespective
of whether the changed file had anything to do with SSR. Because Fresh
attaches directly to Vite's chokidar instance, the user's
`server.watch.ignored` config does not filter events before Fresh sees
them, so on Windows journal/temp writes (Deno KV `main-shm`/`main-wal`,
`*.tmp.*`, `.timestamp-*`) were triggering a full page reload on every
KV write.

Only emit `fresh:reload` when the changed path is in the SSR module
graph or is a route file under `routeDir`. Island and other client-graph
edits keep flowing through Vite's normal HMR path, and route add/unlink
still invalidates the snapshot.

Closes #3637
@fibibot fibibot changed the title fix(plugin-vite): gate fresh:reload on SSR-relevant changes fix: gate fresh:reload on SSR-relevant changes (plugin-vite) May 14, 2026
@fibibot fibibot closed this May 14, 2026
@fibibot fibibot reopened this May 14, 2026
@fibibot
Copy link
Copy Markdown
Contributor Author

fibibot commented May 14, 2026

Quick note on the failing test (canary, ...) checks: those are a pre-existing canary Deno regression around node:* scheme imports during Vite SSR (Unsupported scheme "node" for module "node:assert" / "node:path"), surfacing in maxmind, npm:pg, npm:ioredis, redis, @supabase/postgres-js, qs, stripe, and the www/main_test.ts build path. The same canary failures are visible on other unrelated open PRs (e.g. #3808). They are not caused by this change — the v2.x matrix (ubuntu/macOS/windows) is fully green and the new regression test for #3637 lives in the vite dev integration suite which runs under v2.x as well.

Copy link
Copy Markdown
Contributor

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnosis is correct, fix is well-scoped, test is solid. The watcher callback at server_snapshot.ts:76 was indeed sending fresh:reload for every chokidar event after the island early-return — gating on SSR-relevant changes is the right move, and the legitimate SSR-modification path through dev_server.ts's hotUpdate hook keeps working.

A couple of optional notes:

  • Pre-existing behavior worth knowing. Adding a new island file (one not yet in islandsByFile) no longer triggers fresh:reload. The pre-fix reload was effectively useless anyway — the snapshot wouldn't pick up the new island without a route add/unlink invalidating it — so this isn't a meaningful regression, just a behavior to be aware of.

  • Test timing. The 1500ms negative wait is fine in principle (false negatives are tolerable for "no event arrived" assertions). On a busy CI runner you may occasionally see a spurious event sneak in just after the window — consider bumping to ~2s if it ever flakes.

  • Follow-up. The original issue also reported server.watch = null not stopping reloads. Worth verifying separately whether Vite respects that with our direct viteServer.watcher access — if not, that's a Vite-side concern worth filing upstream.

Left one inline note on the comment-block phrasing.

// relevant to SSR — either a route file, or something already
// tracked in the SSR module graph. Vite attaches us directly to
// its chokidar instance, so `server.watch.ignored` does not
// filter events before we see them. Without this gate, any
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit on phrasing: chokidar's ignored option does drop events at the source, so server.watch.ignored isn't strictly bypassed. The real reason this gate matters is that the user's globs are hard to author correctly (especially on Windows with mixed separators) and shouldn't be load-bearing for avoiding KV temp file reloads. Could we reword to focus on "defense in depth: we shouldn't depend on user ignore globs being perfect" rather than the bypass claim?

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.

Fresh 2 + Vite keeps rebuilding on Windows

2 participants