From be3a1a982d20ff117cfa5ee014c266e221ddb09f Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 14 Nov 2022 17:06:01 -0500 Subject: [PATCH 1/5] fix: support basename in static routers --- .../__tests__/data-static-router-test.tsx | 68 ++++++++++++++++++- .../__tests__/static-link-test.tsx | 15 ++++ packages/react-router-dom/server.tsx | 7 +- packages/router/__tests__/router-test.ts | 47 +++++++++++++ packages/router/router.ts | 30 +++++--- 5 files changed, 150 insertions(+), 17 deletions(-) diff --git a/packages/react-router-dom/__tests__/data-static-router-test.tsx b/packages/react-router-dom/__tests__/data-static-router-test.tsx index fcaf04aec3..2eb68c1b12 100644 --- a/packages/react-router-dom/__tests__/data-static-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-static-router-test.tsx @@ -3,6 +3,7 @@ import * as ReactDOMServer from "react-dom/server"; import type { StaticHandlerContext } from "@remix-run/router"; import { unstable_createStaticHandler as createStaticHandler } from "@remix-run/router"; import { + Link, Outlet, useLoaderData, useLocation, @@ -12,12 +13,13 @@ import { unstable_createStaticRouter as createStaticRouter, unstable_StaticRouterProvider as StaticRouterProvider, } from "react-router-dom/server"; +import { getPositionOfLineAndCharacter } from "typescript"; beforeEach(() => { jest.spyOn(console, "warn").mockImplementation(() => {}); }); -describe("A ", () => { +describe("A ", () => { it("renders an initialized router", async () => { let hooksData1: { location: ReturnType; @@ -45,7 +47,12 @@ describe("A ", () => { loaderData: useLoaderData(), matches: useMatches(), }; - return

👋

; + return ( + <> +

👋

+ Other + + ); } let routes = [ @@ -71,7 +78,7 @@ describe("A ", () => { let { query } = createStaticHandler(routes); let context = (await query( - new Request("http:/localhost/the/path?the=query#the-hash", { + new Request("http://localhost/the/path?the=query#the-hash", { signal: new AbortController().signal, }) )) as StaticHandlerContext; @@ -85,6 +92,7 @@ describe("A ", () => { ); expect(html).toMatch("

👋

"); + expect(html).toMatch(''); // @ts-expect-error expect(hooksData1.location).toEqual({ @@ -155,6 +163,60 @@ describe("A ", () => { ]); }); + it.only("renders an initialized router with a basename", async () => { + let location: ReturnType; + + function GetLocation() { + location = useLocation(); + return ( + <> +

👋

+ Other + + ); + } + + let routes = [ + { + path: "the", + children: [ + { + path: "path", + element: , + }, + ], + }, + ]; + let { query } = createStaticHandler(routes, { basename: "/base" }); + + let context = (await query( + new Request("http://localhost/base/the/path?the=query#the-hash", { + signal: new AbortController().signal, + }) + )) as StaticHandlerContext; + + debugger; + let html = ReactDOMServer.renderToStaticMarkup( + + + + ); + expect(html).toMatch("

👋

"); + expect(html).toMatch('
'); + + // @ts-expect-error + expect(location).toEqual({ + pathname: "/the/path", + search: "?the=query", + hash: "#the-hash", + state: null, + key: expect.any(String), + }); + }); + it("renders hydration data by default", async () => { let routes = [ { diff --git a/packages/react-router-dom/__tests__/static-link-test.tsx b/packages/react-router-dom/__tests__/static-link-test.tsx index 766628e6d1..f2bade5fdd 100644 --- a/packages/react-router-dom/__tests__/static-link-test.tsx +++ b/packages/react-router-dom/__tests__/static-link-test.tsx @@ -17,6 +17,21 @@ describe("A in a ", () => { expect(renderer.root.findByType("a").props.href).toEqual("/mjackson"); }); + + it("uses the right href with a basename", () => { + let renderer: TestRenderer.ReactTestRenderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + + + ); + }); + + expect(renderer.root.findByType("a").props.href).toEqual( + "/base/mjackson" + ); + }); }); describe("with an object", () => { diff --git a/packages/react-router-dom/server.tsx b/packages/react-router-dom/server.tsx index e668894779..7e010ea14c 100644 --- a/packages/react-router-dom/server.tsx +++ b/packages/react-router-dom/server.tsx @@ -64,7 +64,6 @@ export function StaticRouter({ } export interface StaticRouterProviderProps { - basename?: string; context: StaticHandlerContext; router: RemixRouter; hydrate?: boolean; @@ -76,7 +75,6 @@ export interface StaticRouterProviderProps { * on the server where there is no stateful UI. */ export function unstable_StaticRouterProvider({ - basename, context, router, hydrate = true, @@ -91,7 +89,7 @@ export function unstable_StaticRouterProvider({ router, navigator: getStatelessNavigator(), static: true, - basename: basename || "/", + basename: context.basename || "/", }; let hydrateScript = ""; @@ -191,7 +189,7 @@ export function unstable_createStaticRouter( return { get basename() { - return "/"; + return context.basename; }, get state() { return { @@ -231,6 +229,7 @@ export function unstable_createStaticRouter( throw msg("revalidate"); }, createHref() { + // Not needed since we use navigator.createHref internally throw msg("createHref"); }, getFetcher() { diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts index 0b19d09eaf..ca2804174f 100644 --- a/packages/router/__tests__/router-test.ts +++ b/packages/router/__tests__/router-test.ts @@ -10100,6 +10100,21 @@ describe("a router", () => { }); }); + it("should support document load navigations with a basename", async () => { + let { query } = createStaticHandler(SSR_ROUTES, { basename: "/base" }); + let context = await query(createRequest("/base/parent/child")); + expect(context).toMatchObject({ + actionData: null, + loaderData: { + parent: "PARENT LOADER", + child: "CHILD LOADER", + }, + errors: null, + location: { pathname: "/base/parent/child" }, + matches: [{ route: { id: "parent" } }, { route: { id: "child" } }], + }); + }); + it("should support document load navigations returning responses", async () => { let { query } = createStaticHandler(SSR_ROUTES); let context = await query(createRequest("/parent/json")); @@ -11020,6 +11035,38 @@ describe("a router", () => { expect(data).toBe(""); }); + it("should support singular route load navigations (with a basename)", async () => { + let { queryRoute } = createStaticHandler(SSR_ROUTES, { + basename: "/base", + }); + let data; + + // Layout route + data = await queryRoute(createRequest("/base/parent"), "parent"); + expect(data).toBe("PARENT LOADER"); + + // Index route + data = await queryRoute(createRequest("/base/parent"), "parentIndex"); + expect(data).toBe("PARENT INDEX LOADER"); + + // Parent in nested route + data = await queryRoute(createRequest("/base/parent/child"), "parent"); + expect(data).toBe("PARENT LOADER"); + + // Child in nested route + data = await queryRoute(createRequest("/base/parent/child"), "child"); + expect(data).toBe("CHILD LOADER"); + + // Non-undefined falsey values should count + let T = setupFlexRouteTest(); + data = await T.resolveLoader(null); + expect(data).toBeNull(); + data = await T.resolveLoader(false); + expect(data).toBe(false); + data = await T.resolveLoader(""); + expect(data).toBe(""); + }); + it("should support singular route submit navigations (primitives)", async () => { let { queryRoute } = createStaticHandler(SSR_ROUTES); let data; diff --git a/packages/router/router.ts b/packages/router/router.ts index 51e02187de..cddb31ad61 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -288,6 +288,7 @@ export interface RouterInit { * State returned from a server-side query() call */ export interface StaticHandlerContext { + basename: Router["basename"]; location: RouterState["location"]; matches: RouterState["matches"]; loaderData: RouterState["loaderData"]; @@ -1844,7 +1845,10 @@ const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]); const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]); export function unstable_createStaticHandler( - routes: AgnosticRouteObject[] + routes: AgnosticRouteObject[], + opts?: { + basename?: string; + } ): StaticHandler { invariant( routes.length > 0, @@ -1852,6 +1856,7 @@ export function unstable_createStaticHandler( ); let dataRoutes = convertRoutesToDataRoutes(routes); + let basename = (opts ? opts.basename : null) || "/"; /** * The query() method is intended for document requests, in which we want to @@ -1877,13 +1882,14 @@ export function unstable_createStaticHandler( ): Promise { let url = new URL(request.url); let location = createLocation("", createPath(url), null, "default"); - let matches = matchRoutes(dataRoutes, location); + let matches = matchRoutes(dataRoutes, location, basename); if (!validRequestMethods.has(request.method)) { let error = getInternalRouterError(405, { method: request.method }); let { matches: methodNotAllowedMatches, route } = getShortCircuitMatches(dataRoutes); return { + basename, location, matches: methodNotAllowedMatches, loaderData: {}, @@ -1900,6 +1906,7 @@ export function unstable_createStaticHandler( let { matches: notFoundMatches, route } = getShortCircuitMatches(dataRoutes); return { + basename, location, matches: notFoundMatches, loaderData: {}, @@ -1921,7 +1928,7 @@ export function unstable_createStaticHandler( // When returning StaticHandlerContext, we patch back in the location here // since we need it for React Context. But this helps keep our submit and // loadRouteData operating on a Request instead of a Location - return { location, ...result }; + return { location, basename, ...result }; } /** @@ -1947,7 +1954,7 @@ export function unstable_createStaticHandler( async function queryRoute(request: Request, routeId?: string): Promise { let url = new URL(request.url); let location = createLocation("", createPath(url), null, "default"); - let matches = matchRoutes(dataRoutes, location); + let matches = matchRoutes(dataRoutes, location, basename); if (!validRequestMethods.has(request.method)) { throw getInternalRouterError(405, { method: request.method }); @@ -1993,7 +2000,7 @@ export function unstable_createStaticHandler( location: Location, matches: AgnosticDataRouteMatch[], routeMatch?: AgnosticDataRouteMatch - ): Promise | Response> { + ): Promise | Response> { invariant( request.signal, "query()/queryRoute() requests must contain an AbortController signal" @@ -2042,7 +2049,7 @@ export function unstable_createStaticHandler( matches: AgnosticDataRouteMatch[], actionMatch: AgnosticDataRouteMatch, isRouteRequest: boolean - ): Promise | Response> { + ): Promise | Response> { let result: DataResult; if (!actionMatch.route.action) { @@ -2064,7 +2071,7 @@ export function unstable_createStaticHandler( request, actionMatch, matches, - undefined, // Basename not currently supported in static handlers + basename, true, isRouteRequest ); @@ -2154,7 +2161,10 @@ export function unstable_createStaticHandler( routeMatch?: AgnosticDataRouteMatch, pendingActionError?: RouteData ): Promise< - | Omit + | Omit< + StaticHandlerContext, + "location" | "basename" | "actionData" | "actionHeaders" + > | Response > { let isRouteRequest = routeMatch != null; @@ -2194,7 +2204,7 @@ export function unstable_createStaticHandler( request, match, matches, - undefined, // Basename not currently supported in static handlers + basename, true, isRouteRequest ) @@ -2505,7 +2515,7 @@ async function callLoaderOrAction( request: Request, match: AgnosticDataRouteMatch, matches: AgnosticDataRouteMatch[], - basename: string | undefined, + basename = "/", isStaticRequest: boolean = false, isRouteRequest: boolean = false ): Promise { From 20436a66721d64b6d30bf6324df327e2db05fc11 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 14 Nov 2022 17:06:29 -0500 Subject: [PATCH 2/5] add changeset --- .changeset/funny-oranges-arrive.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/funny-oranges-arrive.md diff --git a/.changeset/funny-oranges-arrive.md b/.changeset/funny-oranges-arrive.md new file mode 100644 index 0000000000..a7f52b3e48 --- /dev/null +++ b/.changeset/funny-oranges-arrive.md @@ -0,0 +1,6 @@ +--- +"react-router-dom": patch +"@remix-run/router": patch +--- + +Support `basename` in static data routers From 7502272b29dd4a52efefa4f677ea82210f233799 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 14 Nov 2022 17:07:42 -0500 Subject: [PATCH 3/5] remove it.only --- packages/react-router-dom/__tests__/data-static-router-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-router-dom/__tests__/data-static-router-test.tsx b/packages/react-router-dom/__tests__/data-static-router-test.tsx index 2eb68c1b12..808bba5234 100644 --- a/packages/react-router-dom/__tests__/data-static-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-static-router-test.tsx @@ -163,7 +163,7 @@ describe("A ", () => { ]); }); - it.only("renders an initialized router with a basename", async () => { + it("renders an initialized router with a basename", async () => { let location: ReturnType; function GetLocation() { From 284eb6e9ad2017db9f47ead3557293cc747f3d6f Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 14 Nov 2022 17:12:09 -0500 Subject: [PATCH 4/5] fix lint issues --- packages/react-router-dom/__tests__/data-static-router-test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-router-dom/__tests__/data-static-router-test.tsx b/packages/react-router-dom/__tests__/data-static-router-test.tsx index 808bba5234..edebe0ddef 100644 --- a/packages/react-router-dom/__tests__/data-static-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-static-router-test.tsx @@ -13,7 +13,6 @@ import { unstable_createStaticRouter as createStaticRouter, unstable_StaticRouterProvider as StaticRouterProvider, } from "react-router-dom/server"; -import { getPositionOfLineAndCharacter } from "typescript"; beforeEach(() => { jest.spyOn(console, "warn").mockImplementation(() => {}); @@ -195,7 +194,6 @@ describe("A ", () => { }) )) as StaticHandlerContext; - debugger; let html = ReactDOMServer.renderToStaticMarkup( Date: Fri, 18 Nov 2022 13:10:15 -0500 Subject: [PATCH 5/5] Bump bundle --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b29785bf16..1f29d2697a 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ }, "filesize": { "packages/router/dist/router.umd.min.js": { - "none": "34.5 kB" + "none": "35 kB" }, "packages/react-router/dist/react-router.production.min.js": { "none": "12.5 kB"