fix(vite): don't send browser full-reload for ssr-only changes#4034
fix(vite): don't send browser full-reload for ssr-only changes#4034
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe changes refine HMR behavior by distinguishing between server-only and shared modules, enabling granular reload control to prevent unnecessary full-page reloads when only client code changes. Documentation is updated, and comprehensive test fixtures validate the new behavior. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
52f9839 to
2cd2782
Compare
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/build/vite/plugin.ts`:
- Around line 262-268: The conditional uses
ctx.pluginConfig.experimental?.vite.serverReload which can throw if experimental
exists but vite is undefined; update the check to safely optional-chain through
vite (e.g., ctx.pluginConfig.experimental?.vite?.serverReload) so access to
serverReload is guarded; locate the block around serverOnlyModules/sharedModules
and change the experimental?.vite.serverReload usage in that if-statement to use
?.vite?.serverReload.
2cd2782 to
55086b2
Compare
hotUpdate hook now separates server-only vs shared modules. Browser full-reload only sent when serverReload option is true (default false). Shared modules continue through normal HMR pipeline. Adds e2e tests for both default (no reload) and serverReload:true behaviors.
55086b2 to
f545c4d
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/build/vite/types.ts (1)
33-38:⚠️ Potential issue | 🟡 Minor
@default trueis inconsistent with the PR's stated intent.The implementation uses
=== falseas the guard (Line 244 ofplugin.ts), soundefined/unset behaves the same astrue. If the intent is that browser full-reloads are opt-in (defaultfalse), the guard inplugin.tsshould be!== true(or theserver.ws.sendcall should be additionally gated onserverReload === true). If the current behavior is intentional, the JSDoc description should clarify what exactlyserverReload: falsesuppresses versus the browser-reload-only suppression described in the PR. As per coding guidelines, update types and JSDoc for API changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/build/vite/types.ts` around lines 33 - 38, The JSDoc for serverReload is inconsistent with the implementation: update the API so the default is opt-in (browser reloads disabled unless explicitly enabled) by changing the JSDoc in types.ts to "@default false" and then adjust the runtime guard in plugin.ts (the check around serverReload at the current guard) to treat only true as enabling reloads (use a !== true check or gate the server.ws.send on serverReload === true) so undefined behaves as false; reference the serverReload property and the guard in plugin.ts when making the changes.
♻️ Duplicate comments (1)
test/vite/hmr.test.ts (1)
71-75:⚠️ Potential issue | 🟠 Major"Editing client entry" test is a no-op due to fixture/test mismatch.
content.replace(\+ ""`, `+ " (modified)"`)searches for+ "", which does not exist in the currententry-client.tsfixture (it already contains+ " (modified)"). Thereplacesilently returns the original string,writeFileSyncwrites unchanged content, andpollResponseimmediately matches because the pre-existing text satisfies the regex. The test provides no coverage of the no-browser-reload HMR path. This resolves automatically onceentry-client.tsis corrected to use+ ""` as the initial state.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/vite/hmr.test.ts` around lines 71 - 75, The "Editing client entry (no full-reload)" test is currently a no-op because files.client.update in the test replaces `+ ""` which isn't present in the entry-client.ts fixture; update the fixture so the initial entry-client.ts contains `+ ""` (the original unmodified text) so files.client.update(...) actually changes the file, thereby exercising the no-full-reload HMR path in the test; change the entry-client.ts fixture (the file used by the tests) to restore `+ ""` as the starting content so the test's replace call and the pollResponse(`${serverURL}/app/entry-client.ts`, /modified/) assertion become meaningful.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/vite/hmr-fixture/api/state.ts`:
- Around line 1-3: The current named JSON import causes TS1544 under NodeNext;
replace the named import with a default JSON import so TypeScript can compile:
change the import statement that references "state" (import { state } from
"../shared.json" with { type: "json" };) to a default import (e.g., import state
from "../shared.json" assert { type: "json" };) and keep the existing export
default () => ({ state }); unchanged so the module uses the default-imported
state value.
In `@test/vite/hmr-fixture/app/entry-client.ts`:
- Around line 1-3: Replace the namespace JSON import with a default import and
make the client-entry initially include + "" so the HMR test's string-replace
actually mutates the file: change the import to use default import from
"../shared.json" (so shared.state is a valid property) and update the
document.getElementById("client-state-value")!.textContent assignment to use
shared.state + "" (so the test can replace + "" with + " (modified)" and trigger
the intended HMR behavior).
In `@test/vite/hmr-fixture/app/entry-server.ts`:
- Line 7: The code assigns apiData from serverFetch(...).then((res) =>
res.json()) where Response.json() is typed unknown; update the call to provide a
concrete type so you can access .state safely — e.g., await
serverFetch("/api/state").then((res) => res.json() as { state: YourStateType })
or add a generic: serverFetch("/api/state").then((res) => res.json<{ state:
YourStateType }>()), and apply the same change to the other occurrence so
accesses to apiData.state type-check; reference the apiData variable and the
serverFetch(...).then((res) => res.json()) expression to locate the fixes.
- Line 3: The namespace JSON import in entry-server.ts (the `import * as shared
...` line) prevents accessing shared.state at compile time; change the import to
a default import of the JSON module (so `shared` is the module value rather than
a namespace) to restore access to shared.state and fix static analysis — update
the same pattern in entry-client.ts as well where `import * as shared` appears.
---
Outside diff comments:
In `@src/build/vite/types.ts`:
- Around line 33-38: The JSDoc for serverReload is inconsistent with the
implementation: update the API so the default is opt-in (browser reloads
disabled unless explicitly enabled) by changing the JSDoc in types.ts to
"@default false" and then adjust the runtime guard in plugin.ts (the check
around serverReload at the current guard) to treat only true as enabling reloads
(use a !== true check or gate the server.ws.send on serverReload === true) so
undefined behaves as false; reference the serverReload property and the guard in
plugin.ts when making the changes.
---
Duplicate comments:
In `@test/vite/hmr.test.ts`:
- Around line 71-75: The "Editing client entry (no full-reload)" test is
currently a no-op because files.client.update in the test replaces `+ ""` which
isn't present in the entry-client.ts fixture; update the fixture so the initial
entry-client.ts contains `+ ""` (the original unmodified text) so
files.client.update(...) actually changes the file, thereby exercising the
no-full-reload HMR path in the test; change the entry-client.ts fixture (the
file used by the tests) to restore `+ ""` as the starting content so the test's
replace call and the pollResponse(`${serverURL}/app/entry-client.ts`,
/modified/) assertion become meaningful.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/build/vite/plugin.tssrc/build/vite/types.tstest/vite/hmr-fixture/api/state.tstest/vite/hmr-fixture/app/entry-client.tstest/vite/hmr-fixture/app/entry-server.tstest/vite/hmr-fixture/shared.jsontest/vite/hmr-fixture/tsconfig.jsontest/vite/hmr-fixture/vite.config.tstest/vite/hmr.test.ts
✅ Files skipped from review due to trivial changes (1)
- test/vite/hmr-fixture/shared.json
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
test/vite/hmr-fixture/app/entry-client.ts (1)
3-3:⚠️ Potential issue | 🔴 CriticalThe HMR client-edit test is a no-op — initial fixture content must contain
+ "".The test at
hmr.test.tsline 72 doescontent.replace(\+ ""`, `+ " (modified)"`), but this file already contains+ " (modified)". Thereplace` matches nothing, the file is never written, and the "Editing client entry (no full-reload)" test passes vacuously without exercising HMR.🐛 Proposed fix
-document.getElementById("client-state-value")!.textContent = state + " (modified)"; +document.getElementById("client-state-value")!.textContent = state + "";,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/vite/hmr-fixture/app/entry-client.ts` at line 3, The fixture's client entry already contains the modified suffix, so the HMR test's replace call (which looks for the literal + "") never matches; update the initial expression in entry-client.ts—specifically the assignment to document.getElementById("client-state-value")!.textContent that currently uses state + " (modified)"—to use state + "" so the test can perform its replace to + " (modified)" and actually exercise the HMR path.
🧹 Nitpick comments (1)
test/vite/hmr.test.ts (1)
136-153:pollResponseaborts on the first transient fetch error.A single failed
fetch(e.g. server momentarily restarting after a file edit) rejects the entire promise instead of retrying. Consider retrying on network errors within the timeout window, similar to how timeout is handled on Line 142.♻️ Suggested improvement
} catch (err) { - reject(err); + if (Date.now() - start > timeout) { + reject(err); + } else { + setTimeout(check, 100); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/vite/hmr.test.ts` around lines 136 - 153, The helper currently rejects the whole poll on the first fetch error inside the inner async function check; change the catch block in check (used by pollResponse) to treat transient network/fetch failures as retryable: instead of immediate reject(err), if Date.now() - start > timeout then reject(err), otherwise wait (setTimeout(check, 100)) and continue polling; reference the async function check, variables url, match, lastResponse, timeout and start when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/vite/hmr.test.ts`:
- Around line 71-75: The test "Editing client entry (no full-reload)" is a no-op
because files.client.update(...replace(`+ ""`, ...)) doesn't match the fixture;
either update the fixture entry-client.ts to contain state + "" so the replace
will produce state + " (modified)", or change the test's replace target to match
the current fixture (e.g. replace(`state + " (modified)"`, `state + " (modified)
(edited)"`) or similar) so a real file write occurs; after that, keep using
pollResponse(`${serverURL}/app/entry-client.ts`, /modified/) but confirm that
polling the raw module URL detects the client-side change in your Vite setup
(adjust to the appropriate served URL if Vite serves a transformed path).
---
Duplicate comments:
In `@test/vite/hmr-fixture/app/entry-client.ts`:
- Line 3: The fixture's client entry already contains the modified suffix, so
the HMR test's replace call (which looks for the literal + "") never matches;
update the initial expression in entry-client.ts—specifically the assignment to
document.getElementById("client-state-value")!.textContent that currently uses
state + " (modified)"—to use state + "" so the test can perform its replace to +
" (modified)" and actually exercise the HMR path.
---
Nitpick comments:
In `@test/vite/hmr.test.ts`:
- Around line 136-153: The helper currently rejects the whole poll on the first
fetch error inside the inner async function check; change the catch block in
check (used by pollResponse) to treat transient network/fetch failures as
retryable: instead of immediate reject(err), if Date.now() - start > timeout
then reject(err), otherwise wait (setTimeout(check, 100)) and continue polling;
reference the async function check, variables url, match, lastResponse, timeout
and start when making the change.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
test/vite/hmr-fixture/api/state.tstest/vite/hmr-fixture/app/entry-client.tstest/vite/hmr-fixture/app/entry-server.tstest/vite/hmr-fixture/shared.tstest/vite/hmr.test.ts
hotUpdate hook was unconditionally sending server.ws.send full-reload when any server-only module was detected, destroying client-side HMR. Split modules into server-only vs shared, only browser-reload when ALL modules are server-only and serverReload is explicitly enabled (default changed from true to false).
closes TanStack/router#5904