| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
) Editing a component `templateUrl` `.html` put a `full-reload` on the HMR socket on top of the `angular:component-update` that already hot-swapped the template. `handleHotUpdate` returned `ctx.modules` for the template, and that array is empty: the file was watched but never a module. Vite full-reloads any changed `.html` whose module list is empty or holds no `js` module. Vite's client usually drops that payload, because an `.html` path only reloads when it matches `location.pathname`. That guard is gone when the path is `*`, which is what Vite sends in `middlewareMode` — so the page reloaded right after the update was applied. Reproduced at the reporter's versions (0.0.35 / vite 8.2.1 / angular 21.2.21): `middlewareMode` reloads, plain SPA does not, `.css` never does. Either way the terminal printed `page reload …` and cleared the screen on every save. Register each template with `addWatchFile`, so it enters the module graph as a `js` module with the component `.ts` as its importer, and return it as its own HMR boundary. The browser never imported the template, so the resulting `js-update` is a client no-op; the DOM change still comes from `angular:component-update`. Templates only. An import edge on a style makes Vite propagate the change up to the component `.ts` and re-execute it, which defines a duplicate class (NG0912) and leaves `angular:component-update` patching a class that is no longer mounted — styles then go stale from the second edit onward. Styles never needed it: Vite only force-reloads on `.html`. The e2e sentinel tests could not catch this. They assert the browser did not act on a reload, which stayed true while the server kept asking for one. Added `captureWirePayloads`, which records the HMR socket across navigations, and two tests: a template edit sends no `full-reload`, and `index.html` still does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUvBViRkyuDvqUMkuT5XnH
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_6491c4da-d714-4760-8ecf-0f808478dec6) |
Sorry, something went wrong.
There was a problem hiding this comment.
Here are some automated review suggestions for this pull request.
Reviewed commit: ccaa2dd7e1
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sorry, something went wrong.
`{ id, _clientModule: { isSelfAccepting: false } }` is not assignable to
`Partial<ModuleNode>` — `_clientModule` is an `EnvironmentModuleNode`.
CI's Lint step failed on it, before the e2e suite ran.
Extract `createMockTemplateModule`, which casts once and hands back the
client node so the assertion stays readable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUvBViRkyuDvqUMkuT5XnH
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e4ee3073-b717-4d85-9270-aa76cbbb3763) |
Sorry, something went wrong.
There was a problem hiding this comment.
Here are some automated review suggestions for this pull request.
Reviewed commit: 045049e3b9
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sorry, something went wrong.
Vite sets `mod.file` to `cleanUrl(mod.id)`, so a template that application code also imports as `./tpl.html?raw` is filed under the same path and lands in `ctx.modules` next to the node `addWatchFile` created. Matching on `file` marked that browser-imported variant as a boundary too, and Vite then stopped propagation at a module with no `import.meta.hot.accept` handler — its importers kept the stale raw string. Match on `id` instead. An exact match means same file, no postfix, so it covers `#fragment` as well as `?query`. Measured on an Angular app whose non-component module imports the same file as `./app.html?raw`: before raw value after a template edit: MARKER_V0 (stale) after raw value after a template edit: MARKER_V1_EDITED #443 stays fixed — no `full-reload` in SPA or middlewareMode when nothing imports the template. With a `?raw` importer present the page reloads again, which is Vite's native semantics for a chain with no accept boundary, and is what keeps the raw importer correct. Reported by Codex review on #444. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUvBViRkyuDvqUMkuT5XnH
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f22e4b49-4658-4a4d-b1b0-8fc58e66f071) |
Sorry, something went wrong.
There was a problem hiding this comment.
Here are some automated review suggestions for this pull request.
Reviewed commit: b52bc850e5
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sorry, something went wrong.
With `liveReload: false`, `handleHotUpdate` returns on its first line, so the graph edge has no consumer inside the plugin. It still fed Vite's default propagation, which found no accepting boundary and issued a full reload — turning a payload the client used to drop into an unconditional one with path `*`. Measured on an Angular app at `liveReload: false`, editing a template: build mode payload navs main SPA full-reload path=/src/app/app.html 0 PR SPA full-reload path=* 1 <- regression gated SPA full-reload path=/src/app/app.html 0 main MW full-reload path=* 1 gated MW full-reload path=* 1 The gate restores main's payloads exactly, and `liveReload: true` is unaffected — no `full-reload` in either mode, template still self-accepting. Also collapse the duplicated plugin lookup in the test file into `getAngularPlugin(options)` rather than add two more copies. Reported by Codex review on #444. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUvBViRkyuDvqUMkuT5XnH
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2dd1fb91-c7d4-4420-aaa0-bc7b6138f735) |
Sorry, something went wrong.
|
Nice! I tried to fix it myself but failed 😅. I can confirm this branch solves the issue i expereinced. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Fixes #443.
The bug
Editing a component templateUrl .html puts a full-reload on the HMR socket, on top of the angular:component-update that already hot-swapped the template.
edit src/app/app.html │ ▼ plugin handleHotUpdate ├─ ws.send angular:component-update ← the real HMR, works └─ return ctx.modules == [] ← the template is watched, never a module │ ▼ vite 8 updateModules if (!modules.length && file.endsWith('.html') && env === 'client') └─ hot.send({ type: 'full-reload', path: … })Whether the browser obeys depends on the payload path. Vite's client only path-matches when the path ends in .html; a * path skips that test and reloads at once.
Reproduced at the reporter's versions — @oxc-angular/vite@0.0.35, vite@8.2.1, @angular/*@21.2.21. Console order in the failing case:
[SPY] angular:component-update {…} ← DOM updates [SPY] vite:beforeFullReload {"path":"*"} ← ~2 ms later [vite] connecting... ← new documentEven where the browser ignores the payload, Vite prints page reload src/app/app.html and clears the terminal on every save.
The fix
Register each template with addWatchFile, so it enters the module graph as a js module with the component .ts as its importer, and return it as its own HMR boundary. Both of Vite's .html reload branches are then unreachable.
The browser never imported the template, so Vite's client finds no hotModulesMap entry and the resulting js-update is a no-op there. The DOM change still comes entirely from angular:component-update. Verified: moduleEvalCount: 0, no console errors, and the browser never requests the .html.
Templates only. An import edge on a style makes Vite propagate the change up to the component .ts and re-execute it, which defines a duplicate class (NG0912) and leaves angular:component-update patching a class that is no longer mounted. Styles then go stale from the second edit onward — this showed up as three red e2e tests while developing. Styles never needed the edge: Vite only force-reloads on .html.
Two other candidates were measured and rejected, because both look correct on the first edit and serve a stale template from the second onward:
Why no test caught it
The e2e sentinel tests assert the browser did not act on a reload. That stayed true while the server kept asking for one. setupEventListeners was meant to cover the gap, but an injected <script type="module"> has no import.meta.hot, so it never registered a listener — and no test ever read its output.
Added HmrDetector.captureWirePayloads, which wraps WebSocket before page load and persists payloads in sessionStorage so a full-reload is still observable after the reload it caused. Two new tests:
Verification
Review round (Codex)
Four P2 findings, each verified empirically before acting. Two were real regressions from this PR and are fixed; two are declined with measurements.
On #4: clearing the flag does not restore Vite's droppable .html reload — the node is type: 'js', so propagation dead-ends and Vite sends path="*", an unconditional reload in both modes. That is the same regression class #3 just removed. Full measurements are in the review threads.
Known limits
🤖 Generated with Claude Code
https://claude.ai/code/session_01FUvBViRkyuDvqUMkuT5XnH
Note
Medium Risk
Touches Vite HMR module-graph and self-accept behavior; a mistake can cause full reloads, stale templates, or duplicate component classes (NG0912). Scoped to templates with unit and e2e coverage.
Overview
Stops Vite from putting a full-reload on the HMR socket when a component templateUrl .html is edited, which stacked on top of angular:component-update and always reloaded in middlewareMode (path *) — #443.
The plugin now addWatchFiles templates (HMR on only, never styles) so they exist as js modules in the graph, then marks the template’s own node self-accepting so the update does not re-execute the component .ts. ?raw and other postfixed variants stay un-marked; index.html still full-reloads.
E2E now records HMR websocket payloads (not just DOM sentinels) and asserts a template edit sends no full-reload while index.html still does.
Reviewed by Cursor Bugbot for commit 07ed3ec. Bugbot is set up for automated code reviews on this repo. Configure here.