-
Notifications
You must be signed in to change notification settings - Fork 749
fix: inject client entry on islands-free pages for HMR #3810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,10 +165,19 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] { | |
| const staticFiles: PendingStaticFile[] = []; | ||
| let islandMods: IslandModChunk[] = []; | ||
| let clientEntry = "/@id/fresh:client-entry"; | ||
| let hmrClientEntry: string | undefined; | ||
| let buildId = ""; | ||
| const entryAssets: string[] = []; | ||
|
|
||
| if (isDev && server !== undefined) { | ||
| // Set hmrClientEntry so the SSR runtime always emits a boot script | ||
| // in dev, even when a page has zero islands. Without this, edits | ||
| // to island-free routes never trigger a browser reload because the | ||
| // `fresh:reload` WebSocket listener is never attached. The value | ||
| // is used as a marker only — its presence is what matters, not | ||
| // what it points to. | ||
| hmrClientEntry = clientEntry; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assigning |
||
|
|
||
| for (const id of islands.keys()) { | ||
| const mod = server.environments.client.moduleGraph.getModuleById( | ||
| id, | ||
|
|
@@ -380,6 +389,7 @@ export function serverSnapshot(options: ResolvedFreshViteConfig): Plugin[] { | |
| staticFiles, | ||
| buildId, | ||
| clientEntry, | ||
| hmrClientEntry, | ||
| entryAssets, | ||
| fsRoutesFiles: result.routes, | ||
| islands: islandMods, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,23 @@ integrationTest("vite dev - starts without islands/ dir", async () => { | |
| }); | ||
| }); | ||
|
|
||
| // Issue: https://github.com/denoland/fresh/issues/3806 | ||
| // Pages without islands must still load the client entry in dev so the | ||
| // HMR `fresh:reload` listener attaches and route edits trigger a refresh. | ||
| integrationTest( | ||
| "vite dev - injects client entry on islands-free pages for HMR", | ||
| async () => { | ||
| const fixture = path.join(FIXTURE_DIR, "no_islands"); | ||
| await withDevServer(fixture, async (address) => { | ||
| const res = await fetch(`${address}/`); | ||
| const text = await res.text(); | ||
| expect(text).toContain("ok"); | ||
| expect(text).toContain("/@id/fresh:client-entry"); | ||
| expect(text).toMatch(/import\s*\{\s*boot\s*\}/); | ||
| }); | ||
| }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good regression test for the missing script tag. Worth considering a follow-up that exercises the actual reload flow end-to-end (edit a route file, observe |
||
| ); | ||
|
|
||
| integrationTest("vite dev - starts without routes/ dir", async () => { | ||
| const fixture = path.join(FIXTURE_DIR, "no_routes"); | ||
| await withDevServer(fixture, async (address) => { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc calls this a "Pathname for an HMR-only client entry," but in practice the value is never imported — only its presence gates the boot script in
preact_hooks.ts:726and the preload header incontext.ts:420. The boot script always importsbuildCache.clientEntry, neverhmrClientEntry.That's fine, but the field is effectively a
bootInDev: booleanmasquerading as a pathname (the legacy esbuild builder happens to assign a real chunk path; the Vite plugin assigns the same string asclientEntry). Two options:forceBootInDev) in a follow-up cleanup.Option 1 is enough for this PR.