diff --git a/server/src/__tests__/vite-html-renderer.test.ts b/server/src/__tests__/vite-html-renderer.test.ts index 2b7d50679c1..f958ce79b8b 100644 --- a/server/src/__tests__/vite-html-renderer.test.ts +++ b/server/src/__tests__/vite-html-renderer.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { createCachedViteHtmlRenderer, type ViteWatcherHost } from "../vite-html-renderer.js"; function createWatcher() { @@ -50,10 +50,12 @@ describe("createCachedViteHtmlRenderer", () => { const renderer = createCachedViteHtmlRenderer({ vite, uiRoot: tempDir }); await expect(renderer.render("/")).resolves.toContain("/@vite/client"); + await expect(renderer.render("/")).resolves.toContain('"/@react-refresh"'); const first = await renderer.render("/"); const second = await renderer.render("/issues"); expect(first).toBe(second); expect(first.match(/\/@vite\/client/g)?.length).toBe(1); + expect(first).toContain("window.$RefreshReg$"); fs.writeFileSync( indexPath, @@ -67,12 +69,12 @@ describe("createCachedViteHtmlRenderer", () => { renderer.dispose(); }); - it("does not duplicate the vite client tag when it already exists", async () => { + it("does not duplicate the vite client tag or react refresh preamble when already present", async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-vite-html-")); tempDirs.push(tempDir); fs.writeFileSync( path.join(tempDir, "index.html"), - '', + '', "utf8", ); @@ -84,5 +86,6 @@ describe("createCachedViteHtmlRenderer", () => { const html = await renderer.render("/"); expect(html.match(/\/@vite\/client/g)?.length).toBe(1); + expect(html.match(/\/@react-refresh/g)?.length).toBe(1); }); }); diff --git a/server/src/vite-html-renderer.ts b/server/src/vite-html-renderer.ts index 3a768c23ed3..4834591acf6 100644 --- a/server/src/vite-html-renderer.ts +++ b/server/src/vite-html-renderer.ts @@ -18,13 +18,25 @@ export interface CachedViteHtmlRenderer { const WATCHER_EVENTS: ViteWatcherEvent[] = ["add", "change", "unlink"]; const MAIN_ENTRY_TAG = ''; const VITE_CLIENT_TAG = ''; +const REACT_REFRESH_PREAMBLE = ``; -function injectViteClient(html: string): string { - if (html.includes(VITE_CLIENT_TAG)) return html; - if (html.includes(MAIN_ENTRY_TAG)) { - return html.replace(MAIN_ENTRY_TAG, `${VITE_CLIENT_TAG}\n ${MAIN_ENTRY_TAG}`); +function injectViteDevPreamble(html: string): string { + let injectedHtml = html; + if (!injectedHtml.includes('"/@react-refresh"') && !injectedHtml.includes("'/@react-refresh'")) { + injectedHtml = injectedHtml.includes("") + ? injectedHtml.replace("", ` ${REACT_REFRESH_PREAMBLE}\n `) + : `${REACT_REFRESH_PREAMBLE}\n${injectedHtml}`; } - return html.replace("", ` ${VITE_CLIENT_TAG}\n `); + if (injectedHtml.includes(VITE_CLIENT_TAG)) return injectedHtml; + if (injectedHtml.includes(MAIN_ENTRY_TAG)) { + return injectedHtml.replace(MAIN_ENTRY_TAG, `${VITE_CLIENT_TAG}\n ${MAIN_ENTRY_TAG}`); + } + return injectedHtml.replace("", ` ${VITE_CLIENT_TAG}\n `); } export function createCachedViteHtmlRenderer(opts: { @@ -40,7 +52,7 @@ export function createCachedViteHtmlRenderer(opts: { function loadHtml(): string { if (cachedHtml === null) { const rawTemplate = fs.readFileSync(templatePath, "utf-8"); - cachedHtml = brandHtml(injectViteClient(rawTemplate)); + cachedHtml = injectViteDevPreamble(brandHtml(rawTemplate)); } return cachedHtml; }