Build and dev/HMR and watchers

HMR and watchers

Chokidar, invalidation, and what reloads when a file changes.

Chokidar

BionicJS relies on Vite's internal file watcher (Chokidar) to monitor the app/ directory for structural changes. The watcher is not custom - it is the same server.watcher that Vite's own plugins and HMR pipeline use.

Watcher registration
1if (server.watcher) {
2  server.watcher.on("add", handleFileChange);
3  server.watcher.on("unlink", handleFileChange);
4}

BionicJS listens to add and unlink events - the two that matter for route discovery. Creating a page.tsx fires add; deleting one fires unlink. Editing an existing file does not fire either, and correctly triggers ordinary HMR instead of a route-tree rebuild.

PathChangeHandled by
app/**/page.tsxadded or removedRoute-tree invalidation
app/**/layout.tsxadded or removedRoute-tree invalidation
app/**content editOrdinary granular HMR
server/**/*.tscontent editNitro server reload
ai/**/*.pycontent editPython process restart

Invalidation

When a new page.tsx or layout.tsx is added or deleted, the Vite plugin intercepts the event and invalidates the virtual:bionicjs-routes module in Vite's module graph. This forces Vite to request the file again, triggering the BionicJS generator to re-run and push the new route tree to the browser:

The invalidation handler
1const handleFileChange = (filepath: string) => {
2  if (
3    normalized.includes("/app/") &&
4    (normalized.endsWith("/page.tsx") || normalized.endsWith("/layout.tsx"))
5  ) {
6    const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
7    if (mod) {
8      server.moduleGraph.invalidateModule(mod);
9      server.ws?.send({ type: "full-reload" });
10    }
11  }
12};

The full lifecycle for creating a route:

  1. Create app/contact/page.tsx.
  2. Vite's watcher fires an add event.
  3. The plugin detects the page.tsx suffix under app/.
  4. The virtual module is invalidated and a full-reload is broadcast.
  5. The browser re-fetches virtual:bionicjs-routes, regenerating the route tree.
  6. The new /contact route is live.

This creates a seamless developer experience where routes appear in the browser the second the file is created - and disappear just as quickly, without restarting anything.

ⓘ
The watcher only reacts to structural changes (file added or removed). BionicJS uses a full-reload, not a fine-grained HMR update, because the entire route tree is regenerated - the browser needs a fresh copy of the module. Page content edits still get Vite's normal granular HMR.