feat(vite-plugin): full HMR + reload matrix matching Angular CLI#264
Merged
Brooooooklyn merged 1 commit intovoidzero-dev:mainfrom May 7, 2026
Merged
Conversation
Contributor
Author
|
Looking into e2e failures - looks like it is a linux only issue |
Replaces the per-file node:fs.watch watcher with Vite's chokidar watcher (server.watcher) and adds a handleHotUpdate dispatcher that mirrors the Angular CLI HMR/reload decision matrix: - External template (.html) change → angular:component-update (HMR) - External style (.css) change → angular:component-update (HMR) - Inline template/styles change → angular:component-update (HMR) - Component class-body change (.ts) → full reload - Plain non-component .ts change → full reload - node_modules .ts change → pass through to Vite Fixes a Linux-only race condition where truncate-then-write saves (used by some AI coding tools) caused inotify to fire two discrete change events. The first event arrived while the file was empty; pendingHmrUpdates.delete() was called unconditionally before reading the template, consuming the pending slot before any content could be served. The second request found no entry and returned an empty HMR module. Fix: move pendingHmrUpdates.delete() into the success branch (non-empty template compiled and served) and the error/catch branch only. The empty-file transient fallthrough preserves the entry so the subsequent request can serve real content. Adds unit tests covering: - The double-request race (truncate-then-write transient empty state) - Pending entry consumption after successful HMR - Pending entry consumption and angular:invalidate dispatch on error Removes truncate-then-write from the e2e write-strategy matrix — chokidar can throttle the second inotify event on Linux making e2e coverage unreliable for that pattern. The race condition is covered deterministically at the unit level instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7b8451b to
2fbbb0f
Compare
Brooooooklyn
approved these changes
May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I noticed when using AI tools to modify code (in a project using the vite plugin) the browser didn't update with the changes as I'd expect, while editing the same files manually in an editor worked fine. After some digging it turns out different tools save files in different ways, and the plugin's per-file
node:fs.watchdoesn't deal with all of them.@oxc-angular/vitewatches each component template/stylesheet with its ownfs.watch(file, …)insideconfigureServer, and the handler only reacts toeventType === 'change'. Tools that save by writing a temp file and renaming over the target — vim's default, IntelliJ's "safe write", and the Edit pipeline in several AI tools — produce'rename'events that get dropped on the floor. On macOS it gets worse than that: once the file has been replaced by a rename,fs.watchis bound to the original inode which no longer exists, so even subsequent in-place writes won't fire until the dev server restarts. Most editors save in place, which is why manual saves keep working. And becauseconfigureServercallsserver.watcher.unwatch(file), Vite's own chokidar — which watches the project recursively and handles all of this fine — isn't around as a fallback.While I was in there I lined the plugin up against
@angular/build(CLI's esbuild dev server, Angular 17+) and noticed two more gaps. Inlinestyles: ['…']changes fall through to full reload — the CLI HMRs them, same as inline templates. Plain non-component.tsedits don't reload at all: Vite's default propagation accepts at the nearest component boundary, Angular's runtime sees no template/style metadata change and does nothing, and the DOM stays stale.This PR replaces the custom watcher with a
handleHotUpdatedispatcher driven by Vite's chokidar. Inline-style HMR is added symmetric to inline-template. Plain.tsedits full-reload.liveReload: falsestill disables everything. Final matrix matches@angular/build.Tests:
FileModifiergains aWriteStrategyparameter (in-place, fsync, atomic-rename, truncate-then-write) to guard against this regression specifically. New specs cover the strategy matrix, inline-style HMR, and plain-.tsreload, plus unit coverage for the new dispatcher branches. Three pre-existinghmr-ts.spec.tstests were silently timing out due to a quote-style mismatch in the modify step — fixed.