diff --git a/.changeset/rude-cows-brake.md b/.changeset/rude-cows-brake.md new file mode 100644 index 00000000000..fc1c7b29fdf --- /dev/null +++ b/.changeset/rude-cows-brake.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +[REMOVE] Fix HDR for single fetch diff --git a/integration/vite-hmr-hdr-test.ts b/integration/vite-hmr-hdr-test.ts index 68003e673a9..7a0f536b6f9 100644 --- a/integration/vite-hmr-hdr-test.ts +++ b/integration/vite-hmr-hdr-test.ts @@ -10,6 +10,7 @@ import { EXPRESS_SERVER, viteConfig, } from "./helpers/vite.js"; +import { js } from "./helpers/create-fixture.js"; const indexRoute = ` // imports @@ -112,6 +113,61 @@ test("Vite / HMR & HDR / mdx", async ({ page, viteDev }) => { expect(page.errors).toEqual([]); }); +test.describe("single fetch", () => { + test("Vite / HMR & HDR / vite dev", async ({ + page, + browserName, + viteDev, + }) => { + let files: Files = async ({ port }) => ({ + "vite.config.js": js` + import { vitePlugin as remix } from "@remix-run/dev"; + + export default { + ${await viteConfig.server({ port })} + plugins: [ + remix({ + future: { + unstable_singleFetch: true + }, + }) + ] + } + `, + "app/routes/_index.tsx": indexRoute, + }); + let { cwd, port } = await viteDev(files); + await workflow({ page, browserName, cwd, port }); + }); + + test("Vite / HMR & HDR / express", async ({ + page, + browserName, + customDev, + }) => { + let files: Files = async ({ port }) => ({ + "vite.config.js": js` + import { vitePlugin as remix } from "@remix-run/dev"; + + export default { + ${await viteConfig.server({ port })} + plugins: [ + remix({ + future: { + unstable_singleFetch: true + }, + }) + ] + } + `, + "server.mjs": EXPRESS_SERVER({ port }), + "app/routes/_index.tsx": indexRoute, + }); + let { cwd, port } = await customDev(files); + await workflow({ page, browserName, cwd, port }); + }); +}); + async function workflow({ page, browserName, diff --git a/packages/remix-dev/vite/static/refresh-utils.cjs b/packages/remix-dev/vite/static/refresh-utils.cjs index a2891438a0c..6d2db1ef007 100644 --- a/packages/remix-dev/vite/static/refresh-utils.cjs +++ b/packages/remix-dev/vite/static/refresh-utils.cjs @@ -58,7 +58,13 @@ const enqueueUpdate = debounce(async () => { window.__remixRouteModuleUpdates.clear(); } - await revalidate(); + try { + window.__remixHdrActive = true; + await revalidate(); + } finally { + window.__remixHdrActive = false; + } + if (manifest) { Object.assign(window.__remixManifest, manifest); } diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index 1ee298c23f0..39e27f11844 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -46,6 +46,7 @@ declare global { var __remixRouteModules: RouteModules; var __remixManifest: AssetsManifest; var __remixRevalidation: number | undefined; + var __remixHdrActive: boolean; var __remixClearCriticalCss: (() => void) | undefined; var $RefreshRuntime$: { performReactRefresh: () => void; diff --git a/packages/remix-react/single-fetch.tsx b/packages/remix-react/single-fetch.tsx index e10da5f199f..e500937f4f9 100644 --- a/packages/remix-react/single-fetch.tsx +++ b/packages/remix-react/single-fetch.tsx @@ -261,35 +261,29 @@ async function singleFetchLoaderNavigationStrategy( results[m.route.id] = { type: "error", result: e }; } return; - } else if (!manifest.routes[m.route.id].hasLoader) { - // If we don't have a server loader, then we don't care about the HTTP - // call and can just send back a `null` - because we _do_ have a `loader` - // in the client router handling route module/styles loads - results[m.route.id] = { - type: "data", - result: null, - }; - return; } - // Otherwise, we want to load this route on the server and can lump this - // it in with the others on a singular promise - routesParams.add(m.route.id); + // Load this route on the server if it has a loader + if (manifest.routes[m.route.id].hasLoader) { + routesParams.add(m.route.id); + } - await handler(async () => { - try { + // Lump this match in with the others on a singular promise + try { + let result = await handler(async () => { let data = await singleFetchDfd.promise; - results[m.route.id] = { - type: "data", - result: unwrapSingleFetchResults(data, m.route.id), - }; - } catch (e) { - results[m.route.id] = { - type: "error", - result: e, - }; - } - }); + return unwrapSingleFetchResults(data, m.route.id); + }); + results[m.route.id] = { + type: "data", + result, + }; + } catch (e) { + results[m.route.id] = { + type: "error", + result: e, + }; + } }) ) ); @@ -297,10 +291,17 @@ async function singleFetchLoaderNavigationStrategy( // Wait for all routes to resolve above before we make the HTTP call await routesLoadedPromise; - // Don't make any single fetch server calls: + // We can skip the server call: // - On initial hydration - only clientLoaders can pass through via `clientLoader.hydrate` // - If there are no routes to fetch from the server - if (!router.state.initialized || routesParams.size === 0) { + // + // One exception - if we are performing an HDR revalidation we have to call + // the server in case a new loader has shown up that the manifest doesn't yet + // know about + if ( + (!router.state.initialized || routesParams.size === 0) && + !window.__remixHdrActive + ) { singleFetchDfd.resolve({}); } else { try {