feat(vite-plugin): full HMR + reload matrix matching Angular CLI#264
Open
ashley-hunter wants to merge 1 commit intovoidzero-dev:mainfrom
Open
feat(vite-plugin): full HMR + reload matrix matching Angular CLI#264ashley-hunter wants to merge 1 commit intovoidzero-dev:mainfrom
ashley-hunter wants to merge 1 commit intovoidzero-dev:mainfrom
Conversation
Refactor handleHotUpdate into a single dispatcher driven by Vite's own
chokidar, replacing the per-file `node:fs.watch` watcher that lived in
configureServer. The custom watcher missed single fast in-place writes
on macOS (FSEvents coalescing — Claude Code's Edit tool, IntelliJ
"safe write", AI tools generally) and silently dropped 'rename' events
(vim atomic-save). Vite's recursive root watcher handles all of these
reliably, and `handleHotUpdate` is the canonical extension point.
Behavior matrix (now mirrors @angular/build's esbuild dev server):
external templateUrl → angular:component-update HMR, no reload
external styleUrl → angular:component-update HMR, no reload
inline `template:` only → angular:component-update HMR, no reload
inline `styles:` only → angular:component-update HMR, no reload (NEW)
component class body / → full reload
imports / decorator
non-component .ts → full reload (NEW)
global stylesheet → Vite default CSS HMR
node_modules / *.spec.ts → ignored
Inline-style HMR is added symmetric to the existing inline-template
detection. Cached stripped form of each component .ts file (template:
and styles: decorator fields removed) is diffed against the new content
on each save — if byte-identical, the change is template/styles-only
and we dispatch HMR; otherwise a full reload.
Plain non-component .ts files now full-reload by default (matching
Angular CLI). Without this, Vite's default propagation would accept via
the importing component's HMR boundary without re-rendering, leaving
the DOM stale on every utility/service/constants edit. Gated by
`liveReload`.
Tests:
- Extended FileModifier with a write-strategy parameter
(writeFile-in-place / fsync / atomic-rename / truncate-then-write)
- New e2e specs:
* hmr-html-write-strategies.spec.ts (4-strategy matrix)
* hmr-inline-template.spec.ts
* hmr-inline-styles.spec.ts
* hmr-plain-ts.spec.ts
- New unit tests in test/hmr-hot-update.test.ts:
* full-reload for plain (non-component) .ts
* ignore .ts files in node_modules
* no-op when liveReload is disabled
- Updated existing component-resource unit tests to assert the
angular:component-update ws message is sent
- Fixed 3 pre-existing quote-style bugs in hmr-ts.spec.ts (test
patterns looked for signal("X") but source uses signal('X'); they
had been silently passing the modify step and timing out on reload)
Cross-platform: branch 3 uses `normalizedFile.includes('/node_modules/')`
so the substring check works on Windows where ctx.file may contain
backslashes.
README: added an HMR + reload behavior matrix table.
Contributor
Author
|
Looking into e2e failures - looks like it is a linux only issue |
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.