diff --git a/.changeset/dull-files-warn.md b/.changeset/dull-files-warn.md new file mode 100644 index 00000000000..6f0394ef53a --- /dev/null +++ b/.changeset/dull-files-warn.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Properly handle interrupted \_\_manifest requests in lazy route discovery diff --git a/integration/fog-of-war-test.ts b/integration/fog-of-war-test.ts index 6dc66ad62ed..fa4c2ebfe86 100644 --- a/integration/fog-of-war-test.ts +++ b/integration/fog-of-war-test.ts @@ -1448,4 +1448,66 @@ test.describe("Fog of War", () => { ), ]); }); + + test("handles interruptions from back to back navigations", async ({ + page, + }) => { + let fixture = await createFixture({ + config: { + future: { + v3_lazyRouteDiscovery: true, + }, + }, + files: { + ...getFiles(), + "app/routes/a.tsx": js` + import { Link, Outlet, useLoaderData, useNavigate } from "@remix-run/react"; + + export function loader({ request }) { + return { message: "A LOADER" }; + } + + export default function Index() { + let data = useLoaderData(); + let navigate = useNavigate(); + return ( + <> +

A: {data.message}

+ + + + ) + } + `, + }, + }); + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + + await app.goto("/a", true); + expect( + await page.evaluate(() => + Object.keys((window as any).__remixManifest.routes) + ) + ).toEqual(["root", "routes/a", "routes/_index"]); + + // /a/b gets discovered on click + await app.clickElement("[data-link]"); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(await (await page.$("body"))?.textContent()).not.toContain( + "Not Found" + ); + await page.waitForSelector("#b"); + + expect( + await page.evaluate(() => + Object.keys((window as any).__remixManifest.routes) + ) + ).toEqual(["root", "routes/a", "routes/_index", "routes/a.b"]); + }); }); diff --git a/integration/package.json b/integration/package.json index d36352dc104..aaebef4d883 100644 --- a/integration/package.json +++ b/integration/package.json @@ -14,7 +14,7 @@ "@remix-run/dev": "workspace:*", "@remix-run/express": "workspace:*", "@remix-run/node": "workspace:*", - "@remix-run/router": "1.21.2-pre-v6.0", + "@remix-run/router": "1.22.0-pre-v6.1", "@remix-run/server-runtime": "workspace:*", "@types/express": "^4.17.9", "@vanilla-extract/css": "^1.10.0", diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index 0a8c56b2c70..f00d59e7eb3 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -32,7 +32,7 @@ "@mdx-js/mdx": "^2.3.0", "@npmcli/package-json": "^4.0.1", "@remix-run/node": "workspace:*", - "@remix-run/router": "1.21.2-pre-v6.0", + "@remix-run/router": "1.22.0-pre-v6.1", "@remix-run/server-runtime": "workspace:*", "@types/mdx": "^2.0.5", "@vanilla-extract/integration": "^6.2.0", diff --git a/packages/remix-react/fog-of-war.ts b/packages/remix-react/fog-of-war.ts index ad4020d1faf..4092368206f 100644 --- a/packages/remix-react/fog-of-war.ts +++ b/packages/remix-react/fog-of-war.ts @@ -74,7 +74,7 @@ export function getPatchRoutesOnNavigationFunction( if (!isFogOfWarEnabled(future, isSpaMode)) { return undefined; } - return async ({ path, patch }) => { + return async ({ path, patch, signal }) => { if (discoveredPaths.has(path)) { return; } @@ -85,7 +85,8 @@ export function getPatchRoutesOnNavigationFunction( future, isSpaMode, basename, - patch + patch, + signal ); }; } @@ -207,7 +208,8 @@ export async function fetchAndApplyManifestPatches( future: FutureConfig, isSpaMode: boolean, basename: string | undefined, - patchRoutes: Router["patchRoutes"] + patchRoutes: Router["patchRoutes"], + signal?: AbortSignal ): Promise { let manifestPath = `${basename ?? "/"}/__manifest`.replace(/\/+/g, "/"); let url = new URL(manifestPath, window.location.origin); @@ -222,15 +224,21 @@ export async function fetchAndApplyManifestPatches( return; } - let res = await fetch(url); + let serverPatches: AssetsManifest["routes"]; + try { + let res = await fetch(url, { signal }); - if (!res.ok) { - throw new Error(`${res.status} ${res.statusText}`); - } else if (res.status >= 400) { - throw new Error(await res.text()); - } + if (!res.ok) { + throw new Error(`${res.status} ${res.statusText}`); + } else if (res.status >= 400) { + throw new Error(await res.text()); + } - let serverPatches = (await res.json()) as AssetsManifest["routes"]; + serverPatches = (await res.json()) as AssetsManifest["routes"]; + } catch (e) { + if (signal?.aborted) return; + throw e; + } // Patch routes we don't know about yet into the manifest let knownRoutes = new Set(Object.keys(manifest.routes)); diff --git a/packages/remix-react/package.json b/packages/remix-react/package.json index 14b7af832f6..6d2c9376f9b 100644 --- a/packages/remix-react/package.json +++ b/packages/remix-react/package.json @@ -19,10 +19,10 @@ "tsc": "tsc" }, "dependencies": { - "@remix-run/router": "1.21.2-pre-v6.0", + "@remix-run/router": "1.22.0-pre-v6.1", "@remix-run/server-runtime": "workspace:*", - "react-router": "6.28.3-pre-v6.1", - "react-router-dom": "6.28.3-pre-v6.1", + "react-router": "6.29.0-pre-v6.2", + "react-router-dom": "6.29.0-pre-v6.2", "turbo-stream": "2.4.0" }, "devDependencies": { diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json index 16e7da6541b..33e60891209 100644 --- a/packages/remix-server-runtime/package.json +++ b/packages/remix-server-runtime/package.json @@ -19,7 +19,7 @@ "tsc": "tsc" }, "dependencies": { - "@remix-run/router": "1.21.2-pre-v6.0", + "@remix-run/router": "1.22.0-pre-v6.1", "@types/cookie": "^0.6.0", "@web3-storage/multipart-parser": "^1.0.0", "cookie": "^0.6.0", diff --git a/packages/remix-testing/package.json b/packages/remix-testing/package.json index ce1b40b6aab..b1d6d83c30a 100644 --- a/packages/remix-testing/package.json +++ b/packages/remix-testing/package.json @@ -21,8 +21,8 @@ "dependencies": { "@remix-run/node": "workspace:*", "@remix-run/react": "workspace:*", - "@remix-run/router": "1.21.2-pre-v6.0", - "react-router-dom": "6.28.3-pre-v6.1" + "@remix-run/router": "1.22.0-pre-v6.1", + "react-router-dom": "6.29.0-pre-v6.2" }, "devDependencies": { "@remix-run/server-runtime": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4a1c6fe8a1..35d537fdc5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -323,8 +323,8 @@ importers: specifier: workspace:* version: link:../packages/remix-node '@remix-run/router': - specifier: 1.21.2-pre-v6.0 - version: 1.21.2-pre-v6.0 + specifier: 1.22.0-pre-v6.1 + version: 1.22.0-pre-v6.1 '@remix-run/server-runtime': specifier: workspace:* version: link:../packages/remix-server-runtime @@ -883,8 +883,8 @@ importers: specifier: ^2.15.3-pre.0 version: link:../remix-react '@remix-run/router': - specifier: 1.21.2-pre-v6.0 - version: 1.21.2-pre-v6.0 + specifier: 1.22.0-pre-v6.1 + version: 1.22.0-pre-v6.1 '@remix-run/server-runtime': specifier: workspace:* version: link:../remix-server-runtime @@ -1247,17 +1247,17 @@ importers: packages/remix-react: dependencies: '@remix-run/router': - specifier: 1.21.2-pre-v6.0 - version: 1.21.2-pre-v6.0 + specifier: 1.22.0-pre-v6.1 + version: 1.22.0-pre-v6.1 '@remix-run/server-runtime': specifier: workspace:* version: link:../remix-server-runtime react-router: - specifier: 6.28.3-pre-v6.1 - version: 6.28.3-pre-v6.1(react@18.2.0) + specifier: 6.29.0-pre-v6.2 + version: 6.29.0-pre-v6.2(react@18.2.0) react-router-dom: - specifier: 6.28.3-pre-v6.1 - version: 6.28.3-pre-v6.1(react-dom@18.2.0)(react@18.2.0) + specifier: 6.29.0-pre-v6.2 + version: 6.29.0-pre-v6.2(react-dom@18.2.0)(react@18.2.0) turbo-stream: specifier: 2.4.0 version: 2.4.0 @@ -1364,8 +1364,8 @@ importers: packages/remix-server-runtime: dependencies: '@remix-run/router': - specifier: 1.21.2-pre-v6.0 - version: 1.21.2-pre-v6.0 + specifier: 1.22.0-pre-v6.1 + version: 1.22.0-pre-v6.1 '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -1401,11 +1401,11 @@ importers: specifier: workspace:* version: link:../remix-react '@remix-run/router': - specifier: 1.21.2-pre-v6.0 - version: 1.21.2-pre-v6.0 + specifier: 1.22.0-pre-v6.1 + version: 1.22.0-pre-v6.1 react-router-dom: - specifier: 6.28.3-pre-v6.1 - version: 6.28.3-pre-v6.1(react-dom@18.2.0)(react@18.2.0) + specifier: 6.29.0-pre-v6.2 + version: 6.29.0-pre-v6.2(react-dom@18.2.0)(react@18.2.0) devDependencies: '@remix-run/server-runtime': specifier: workspace:* @@ -4267,8 +4267,8 @@ packages: - encoding dev: false - /@remix-run/router@1.21.2-pre-v6.0: - resolution: {integrity: sha512-wbnSFjjHmAOYArpveJ4IYAxhPRzBW4onkjcukKUC8xCUXleteSP1On+YcqFeXnB0FEUzSD9cT2s/0y77w28Slg==} + /@remix-run/router@1.22.0-pre-v6.1: + resolution: {integrity: sha512-SotUMuK2uvVsJVBXa98R2UlYtniGuSMAKhOmiVu+efpSPCPf3G4npT32pSGYfZYVZ/7RqTxlghfgQkh2URKIqg==} engines: {node: '>=14.0.0'} dev: false @@ -12840,26 +12840,26 @@ packages: engines: {node: '>=0.10.0'} dev: false - /react-router-dom@6.28.3-pre-v6.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-qPrKJZ8VX83vQZ5RxIh+GKbeA1ZV3aR7zEOCBewFnexMdiQBwmDGiq+39NZ6ts7KuyAqFTSsRtiPznTopeQ0Ow==} + /react-router-dom@6.29.0-pre-v6.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-tq8W0OJ/wHgRcTRGPoQleRFIJnwy9/kynfvo3BYvjE0YzEHdQag8e8liAkRMJDZ4laZZWyM+r55cGlv9kt4JtA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.21.2-pre-v6.0 + '@remix-run/router': 1.22.0-pre-v6.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router: 6.28.3-pre-v6.1(react@18.2.0) + react-router: 6.29.0-pre-v6.2(react@18.2.0) dev: false - /react-router@6.28.3-pre-v6.1(react@18.2.0): - resolution: {integrity: sha512-CqBA2PYs4m6CbVZ18pm3fpq/Jww4M1L9UODnxnthZe5c5/23QAwS6PU9EWLTa5WWATrO83+GK4qOpZjV98Y6cA==} + /react-router@6.29.0-pre-v6.2(react@18.2.0): + resolution: {integrity: sha512-8MQITnj3tFxiBs/U+letrIyMcapbnkjL/jM0Gte4FmkMxToO7PO806eYiRfJxRL6VeBg4JPPRe1vuXs4qzbJsQ==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.21.2-pre-v6.0 + '@remix-run/router': 1.22.0-pre-v6.1 react: 18.2.0 dev: false