Skip to content
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

feat: allow configuration of how route modules are loaded in the browser #12638

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions packages/react-router/lib/dom-export/hydrated-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
matchRoutes,
} from "react-router";
import { RouterProvider } from "./dom-router-provider";
import {
defaultLoadRouteModule,
LoadRouteModuleFunction,
} from "../dom/ssr/routeModules";

type SSRInfo = {
context: NonNullable<(typeof window)["__reactRouterContext"]>;
Expand Down Expand Up @@ -60,7 +64,11 @@ function initSsrInfo(): void {
}
}

function createHydratedRouter(): DataRouter {
function createHydratedRouter({
loadRouteModule = defaultLoadRouteModule,
}: {
loadRouteModule?: LoadRouteModuleFunction;
} = {}): DataRouter {
initSsrInfo();

if (!ssrInfo) {
Expand Down Expand Up @@ -102,7 +110,8 @@ function createHydratedRouter(): DataRouter {
ssrInfo.manifest.routes,
ssrInfo.routeModules,
ssrInfo.context.state,
ssrInfo.context.isSpaMode
ssrInfo.context.isSpaMode,
loadRouteModule
);

let hydrationData: HydrationState | undefined = undefined;
Expand Down Expand Up @@ -177,7 +186,8 @@ function createHydratedRouter(): DataRouter {
ssrInfo.manifest,
ssrInfo.routeModules,
ssrInfo.context.isSpaMode,
ssrInfo.context.basename
ssrInfo.context.basename,
loadRouteModule
),
});
ssrInfo.router = router;
Expand All @@ -201,9 +211,13 @@ function createHydratedRouter(): DataRouter {
/**
* @category Router Components
*/
export function HydratedRouter() {
export function HydratedRouter({
loadRouteModule = defaultLoadRouteModule,
}: {
loadRouteModule?: LoadRouteModuleFunction;
}) {
if (!router) {
router = createHydratedRouter();
router = createHydratedRouter({ loadRouteModule });
}

// Critical CSS can become stale after code changes, e.g. styles might be
Expand Down Expand Up @@ -247,7 +261,8 @@ export function HydratedRouter() {
router,
ssrInfo.manifest,
ssrInfo.routeModules,
ssrInfo.context.isSpaMode
ssrInfo.context.isSpaMode,
loadRouteModule
);

// We need to include a wrapper RemixErrorBoundary here in case the root error
Expand All @@ -265,6 +280,7 @@ export function HydratedRouter() {
future: ssrInfo.context.future,
criticalCss,
isSpaMode: ssrInfo.context.isSpaMode,
loadRouteModule,
}}
>
<RemixErrorBoundary location={location}>
Expand Down
13 changes: 8 additions & 5 deletions packages/react-router/lib/dom/ssr/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export function PrefetchPageLinks({
}

function useKeyedPrefetchLinks(matches: AgnosticDataRouteMatch[]) {
let { manifest, routeModules } = useFrameworkContext();
let { manifest, routeModules, loadRouteModule } = useFrameworkContext();

let [keyedPrefetchLinks, setKeyedPrefetchLinks] = React.useState<
KeyedHtmlLinkDescriptor[]
Expand All @@ -297,13 +297,16 @@ function useKeyedPrefetchLinks(matches: AgnosticDataRouteMatch[]) {
React.useEffect(() => {
let interrupted: boolean = false;

void getKeyedPrefetchLinks(matches, manifest, routeModules).then(
(links) => {
void getKeyedPrefetchLinks(
matches,
manifest,
routeModules,
loadRouteModule
).then((links) => {
if (!interrupted) {
setKeyedPrefetchLinks(links);
}
}
);
});

return () => {
interrupted = true;
Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/lib/dom/ssr/entry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { StaticHandlerContext } from "../../router/router";

import type { EntryRoute } from "./routes";
import type { RouteModules } from "./routeModules";
import type { LoadRouteModuleFunction, RouteModules } from "./routeModules";
import type { RouteManifest } from "../../router/utils";

type SerializedError = {
Expand All @@ -17,6 +17,7 @@ export interface FrameworkContextObject {
serverHandoffString?: string;
future: FutureConfig;
isSpaMode: boolean;
loadRouteModule: LoadRouteModuleFunction;
serializeError?(error: Error): SerializedError;
renderMeta?: {
didRenderScripts?: boolean;
Expand Down
20 changes: 16 additions & 4 deletions packages/react-router/lib/dom/ssr/fog-of-war.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Router as DataRouter } from "../../router/router";
import type { RouteManifest } from "../../router/utils";
import { matchRoutes } from "../../router/utils";
import type { AssetsManifest } from "./entry";
import type { RouteModules } from "./routeModules";
import type { LoadRouteModuleFunction, RouteModules } from "./routeModules";
import type { EntryRoute } from "./routes";
import { createClientRoutes } from "./routes";

Expand Down Expand Up @@ -71,7 +71,8 @@ export function getPatchRoutesOnNavigationFunction(
manifest: AssetsManifest,
routeModules: RouteModules,
isSpaMode: boolean,
basename: string | undefined
basename: string | undefined,
loadRouteModule: LoadRouteModuleFunction
): PatchRoutesOnNavigationFunction | undefined {
if (!isFogOfWarEnabled(isSpaMode)) {
return undefined;
Expand All @@ -86,6 +87,7 @@ export function getPatchRoutesOnNavigationFunction(
manifest,
routeModules,
isSpaMode,
loadRouteModule,
basename,
patch
);
Expand All @@ -96,7 +98,8 @@ export function useFogOFWarDiscovery(
router: DataRouter,
manifest: AssetsManifest,
routeModules: RouteModules,
isSpaMode: boolean
isSpaMode: boolean,
loadRouteModule: LoadRouteModuleFunction
) {
React.useEffect(() => {
// Don't prefetch if not enabled or if the user has `saveData` enabled
Expand Down Expand Up @@ -142,6 +145,7 @@ export function useFogOFWarDiscovery(
manifest,
routeModules,
isSpaMode,
loadRouteModule,
router.basename,
router.patchRoutes
);
Expand Down Expand Up @@ -204,6 +208,7 @@ export async function fetchAndApplyManifestPatches(
manifest: AssetsManifest,
routeModules: RouteModules,
isSpaMode: boolean,
loadRouteModule: LoadRouteModuleFunction,
basename: string | undefined,
patchRoutes: DataRouter["patchRoutes"]
): Promise<void> {
Expand Down Expand Up @@ -257,7 +262,14 @@ export async function fetchAndApplyManifestPatches(
parentIds.forEach((parentId) =>
patchRoutes(
parentId || null,
createClientRoutes(patches, routeModules, null, isSpaMode, parentId)
createClientRoutes(
patches,
routeModules,
null,
isSpaMode,
loadRouteModule,
parentId
)
)
);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/react-router/lib/dom/ssr/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { AgnosticDataRouteMatch } from "../../router/utils";
import type { AssetsManifest } from "./entry";
import type { RouteModules, RouteModule } from "./routeModules";
import type { EntryRoute } from "./routes";
import { loadRouteModule } from "./routeModules";
import type { LoadRouteModuleFunction } from "./routeModules";
import type {
HtmlLinkDescriptor,
LinkDescriptor,
Expand Down Expand Up @@ -136,7 +136,8 @@ export type KeyedHtmlLinkDescriptor = { key: string; link: HtmlLinkDescriptor };
export async function getKeyedPrefetchLinks(
matches: AgnosticDataRouteMatch[],
manifest: AssetsManifest,
routeModules: RouteModules
routeModules: RouteModules,
loadRouteModule: LoadRouteModuleFunction
): Promise<KeyedHtmlLinkDescriptor[]> {
let links = await Promise.all(
matches.map(async (match) => {
Expand Down
11 changes: 8 additions & 3 deletions packages/react-router/lib/dom/ssr/routeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,15 @@ export type RouteComponent = ComponentType<{}>;
*/
export type RouteHandle = unknown;

export async function loadRouteModule(
export type LoadRouteModuleFunction = (
route: EntryRoute,
routeModulesCache: RouteModules
): Promise<RouteModule> {
) => Promise<RouteModule>;

export const defaultLoadRouteModule: LoadRouteModuleFunction = async (
route,
routeModulesCache
) => {
if (route.id in routeModulesCache) {
return routeModulesCache[route.id] as RouteModule;
}
Expand Down Expand Up @@ -281,4 +286,4 @@ export async function loadRouteModule(
// check out of this hook cause the DJs never gonna re[s]olve this
});
}
}
};
15 changes: 14 additions & 1 deletion packages/react-router/lib/dom/ssr/routes-test-stub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import type {
IndexRouteObject,
NonIndexRouteObject,
} from "../../context";
import type { LinksFunction, MetaFunction, RouteModules } from "./routeModules";
import {
defaultLoadRouteModule,
type LinksFunction,
type LoadRouteModuleFunction,
type MetaFunction,
type RouteModules,
} from "./routeModules";
import type { InitialEntry } from "../../router/history";
import type { HydrationState } from "../../router/router";
import { convertRoutesToDataRoutes } from "../../router/utils";
Expand Down Expand Up @@ -84,6 +90,11 @@ export interface RoutesTestStubProps {
* Future flags mimicking the settings in react-router.config.ts
*/
future?: Partial<FutureConfig>;

/**
* LoadRouteModuleFunction to use in the test
*/
loadRouteModule?: LoadRouteModuleFunction;
}

/**
Expand All @@ -98,6 +109,7 @@ export function createRoutesStub(
initialIndex,
hydrationData,
future,
loadRouteModule = defaultLoadRouteModule,
}: RoutesTestStubProps) {
let routerRef = React.useRef<ReturnType<typeof createMemoryRouter>>();
let remixContextRef = React.useRef<FrameworkContextObject>();
Expand All @@ -113,6 +125,7 @@ export function createRoutesStub(
},
routeModules: {},
isSpaMode: false,
loadRouteModule,
};

// Update the routes to include context in the loader/action and populate
Expand Down
14 changes: 10 additions & 4 deletions packages/react-router/lib/dom/ssr/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
} from "../../router/utils";
import { ErrorResponseImpl } from "../../router/utils";
import type { RouteModule, RouteModules } from "./routeModules";
import { loadRouteModule } from "./routeModules";
import type { LoadRouteModuleFunction } from "./routeModules";
import type { FutureConfig } from "./entry";
import { prefetchStyleLinks } from "./links";
import { RemixRootDefaultErrorBoundary } from "./errorBoundaries";
Expand Down Expand Up @@ -171,13 +171,15 @@ export function createClientRoutesWithHMRRevalidationOptOut(
routeModulesCache: RouteModules,
initialState: HydrationState,
future: FutureConfig,
isSpaMode: boolean
isSpaMode: boolean,
loadRouteModule: LoadRouteModuleFunction
) {
return createClientRoutes(
manifest,
routeModulesCache,
initialState,
isSpaMode,
loadRouteModule,
"",
groupRoutesByParentId(manifest),
needsRevalidation
Expand Down Expand Up @@ -226,6 +228,7 @@ export function createClientRoutes(
routeModulesCache: RouteModules,
initialState: HydrationState | null,
isSpaMode: boolean,
loadRouteModule: LoadRouteModuleFunction,
parentId: string = "",
routesByParentId: Record<
string,
Expand Down Expand Up @@ -418,7 +421,8 @@ export function createClientRoutes(
dataRoute.lazy = async () => {
let mod = await loadRouteModuleWithBlockingLinks(
route,
routeModulesCache
routeModulesCache,
loadRouteModule
);

let lazyRoute: Partial<DataRouteObject> = { ...mod };
Expand Down Expand Up @@ -475,6 +479,7 @@ export function createClientRoutes(
routeModulesCache,
initialState,
isSpaMode,
loadRouteModule,
route.id,
routesByParentId,
needsRevalidation
Expand Down Expand Up @@ -531,7 +536,8 @@ function wrapShouldRevalidateForHdr(

async function loadRouteModuleWithBlockingLinks(
route: EntryRoute,
routeModules: RouteModules
routeModules: RouteModules,
loadRouteModule: LoadRouteModuleFunction
) {
let routeModule = await loadRouteModule(route, routeModules);
await prefetchStyleLinks(route, routeModule);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-router/lib/dom/ssr/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { EntryContext } from "./entry";
import { RemixErrorBoundary } from "./errorBoundaries";
import { createServerRoutes, shouldHydrateRouteLoader } from "./routes";
import { StreamTransfer } from "./single-fetch";
import { defaultLoadRouteModule } from "./routeModules";

export interface ServerRouterProps {
context: EntryContext;
Expand Down Expand Up @@ -78,6 +79,7 @@ export function ServerRouter({
isSpaMode: context.isSpaMode,
serializeError: context.serializeError,
renderMeta: context.renderMeta,
loadRouteModule: defaultLoadRouteModule,
}}
>
<RemixErrorBoundary location={router.state.location}>
Expand Down
5 changes: 5 additions & 0 deletions packages/react-router/lib/server-runtime/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import {
import { getDocumentHeaders } from "./headers";
import invariant from "./invariant";
import type { EntryRoute } from "../dom/ssr/routes";
import {
defaultLoadRouteModule,
type LoadRouteModuleFunction,
} from "../dom/ssr/routeModules";

export type RequestHandler = (
request: Request,
Expand Down Expand Up @@ -392,6 +396,7 @@ async function handleDocumentRequest(
future: build.future,
isSpaMode: build.isSpaMode,
serializeError: (err) => serializeError(err, serverMode),
loadRouteModule: defaultLoadRouteModule,
};

let handleDocumentRequestFunction = build.entry.module.default;
Expand Down
Loading