Skip to content

gate Start rebundle and reload on served module graphs - #349

Merged
raphamorim merged 1 commit into
rapha/watch-coveragefrom
rapha/start-rebundle-gate
Oct 10, 2026
Merged

raphamorim merged 1 commit into
rapha/watch-coveragefrom
rapha/start-rebundle-gate

Conversation

@raphamorim

Copy link
Copy Markdown
Collaborator

The second follow-up from the #345 review, stacked on #346: Start rebundled the client and reloaded the browser on every relevant change anywhere in the root, where Vite consults its module graphs and does nothing for a file it never served. With #345's root-wide watching, a README or docs edit cost a full rebundle plus a visible page reload.

How Vite avoids this

Every file Vite reacts to is in some environment's module graph; a file in no graph produces no HMR and no reload. Start's equivalents are split across three places, and the gate consults all of them:

  • The client bundle: closure.json, the bundle's input set. It was incomplete (built from the transform hook plus an output walk), so first the bundler now contributes this.getModuleIds() at buildEnd, which is rollup's complete watch set, and asset modules' virtual ids (\0oj-css:<file> and friends) map back to their files. Verified against the start fixture: styles/app.css, previously missing, is now in the closure; raw/inline asset files too (their bytes are embedded, so their edits must rebundle).
  • The worker environments: the plugin host's invalidateEnvironments already computed which changes matched a runner-backed graph; the /__oj_invalidate reply now carries that count (previously 204 with no body), and the Rust side keeps both the early and the settled send's verdicts (the host dedups the pair by content, so the settled reply alone can read as a miss for a change the early send carried). Any failure to tell reads as a hit, so the decision only ever errs toward reloading.
  • The engine and the generic pipeline: for batches that did not bundle, the non-lazy engine runs revalidate() (its mtime walk, already used after prewarm) instead of a full reload(), which both drops exactly the stale records and answers whether the engine served any changed file; SsrBridge::graph_knows_file covers modules only the generic pipeline served (a dev worker script fetched by URL).

The rebundle runs when a changed path is in the closure, anything relevant was created (resolution can shift onto a new file), the route set changed, the previous bundle failed, or the closure is unreadable. The reload fires when the run bundled, a regen output moved, a server fn changed, or any graph above claims a changed file. Regen checks (route tree, server-fn resolver) still run on every batch: a server fn can live in a file the client never imports.

One prerequisite fix landed in #346's branch while building this: under Deno, Vite's config loader cannot use node_modules/.vite-temp and writes its vite.config.ts.timestamp-*.mjs temp beside the config, so every rebundle's config load re-triggered the watcher -- an infinite rebundle loop once the root watch could see it. Those temps are excluded from watching, as self-inflicted artifacts like the route tree.

Measured on the start fixture: a README edit now logs change outside every served graph, no rebundle, no reload and runs nothing; css and route edits rebundle and reload as before; the loop is gone.

Test

e2e/start-rebundle-gate.mjs: an edit to a pre-existing README must not rebundle (red-proven: the base rebundles once and fails the assertion), and a route-module edit must still rebundle and reload. A unit test pins closure.json parsing (missing or torn reads as unknown, which always rebundles). start, start-hmr-gate, start-cloudflare-dev and config-restart pass locally with the gate.

Open points

  • The engine-rendered document stays stale after a route edit even on the base branch (the worker-rendered path is fresh; red-run confirmed this predates the gate). Worth its own issue.
  • The HMR gate hold is still taken for batches that end up not reloading; the editor's flush fires its own reload regardless, so editor-driven flows behave as before.

@raphamorim
raphamorim requested a review from a team as a code owner October 10, 2026 07:20
@raphamorim
raphamorim merged commit fd2c7d8 into rapha/watch-coverage Oct 10, 2026
5 of 7 checks passed
@raphamorim
raphamorim deleted the rapha/start-rebundle-gate branch October 10, 2026 09:15
raphamorim added a commit that referenced this pull request Oct 10, 2026
* watch new top-level entries, skip relocated cache dir

* exclude Vite config-loader temp files from watching

* adopt dirs renamed into the root, not just created

* gate Start rebundle and reload on served module graphs (#349)
raphamorim added a commit that referenced this pull request Oct 10, 2026
* start dev: invalidate the worker for edits outside src/

Start mode ran a second file watcher on <root>/src only, and the worker
invalidation, the lazy runner's dirty flag and the client rebundle hung off
it. An edit outside src/ reached the browser through oj_server's watcher but
never the worker, which kept rendering the old module until a restart.

The Start watcher thread now subscribes to oj_server's watcher (WatchFeed),
published from the notify callback so it adds no debounce and never waits on
the watcher thread's plugin-hook RPCs. watch_relevant also skips the route
generator's .tanstack/ temp files and declaration files, which a root-wide
watch now sees.

* adopt resolved server.watch.ignored from vite config (#350)

* adopt resolved server.watch.ignored from vite config

* dump server log and page loads on playground failure

* log which paths trigger a start rebuild

* watch new top-level entries, skip relocated cache dir (#346)

* watch new top-level entries, skip relocated cache dir

* exclude Vite config-loader temp files from watching

* adopt dirs renamed into the root, not just created

* gate Start rebundle and reload on served module graphs (#349)

---------

Co-authored-by: Raphael Amorim <rapha850@gmail.com>
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.

1 participant