kleinigkeiten

This commit is contained in:
2026-08-09 21:14:15 +02:00
parent 3b1828291a
commit fe52b3cf5c
7530 changed files with 758084 additions and 5 deletions
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
MIT License
Copyright (c) React Training LLC 2015-2019
Copyright (c) Remix Software Inc. 2020-2021
Copyright (c) Shopify Inc. 2022-2023
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
`react-router` is the primary package in the React Router project.
## Installation
```sh
npm i react-router
```
@@ -0,0 +1,7 @@
import { RouterProvider, RouterProviderProps } from "./lib/dom-export/dom-router-provider.js";
import { DecodeActionFunction, DecodeFormStateFunction, DecodeReplyFunction, RSCManifestPayload, RSCPayload, RSCRenderPayload } from "./lib/rsc/server.rsc.js";
import { RSCHydratedRouter, createCallServer } from "./lib/rsc/browser.js";
import { HydratedRouter, HydratedRouterProps } from "./lib/dom-export/hydrated-router.js";
import { getRSCStream } from "./lib/rsc/html-stream/browser.js";
export { HydratedRouter, type HydratedRouterProps, RouterProvider, type RouterProviderProps, type DecodeActionFunction as unstable_DecodeActionFunction, type DecodeFormStateFunction as unstable_DecodeFormStateFunction, type DecodeReplyFunction as unstable_DecodeReplyFunction, RSCHydratedRouter as unstable_RSCHydratedRouter, type RSCManifestPayload as unstable_RSCManifestPayload, type RSCPayload as unstable_RSCPayload, type RSCRenderPayload as unstable_RSCRenderPayload, createCallServer as unstable_createCallServer, getRSCStream as unstable_getRSCStream };
@@ -0,0 +1,21 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
"use client";
import { RouterProvider } from "./lib/dom-export/dom-router-provider.js";
import { HydratedRouter } from "./lib/dom-export/hydrated-router.js";
import { RSCHydratedRouter, createCallServer } from "./lib/rsc/browser.js";
import { getRSCStream } from "./lib/rsc/html-stream/browser.js";
//#region dom-export.ts
/**
* @module dom
*/
//#endregion
export { HydratedRouter, RouterProvider, RSCHydratedRouter as unstable_RSCHydratedRouter, createCallServer as unstable_createCallServer, getRSCStream as unstable_getRSCStream };
@@ -0,0 +1,7 @@
import { MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, WithComponentProps, WithErrorBoundaryProps, WithHydrateFallbackProps } from "./lib/components.js";
import { AwaitContextProvider } from "./lib/context.js";
import { Links, Meta } from "./lib/dom/ssr/components.js";
import { BrowserRouter, Form, HashRouter, HistoryRouter, Link, NavLink, ScrollRestoration } from "./lib/dom/lib.js";
import { StaticRouter, StaticRouterProvider } from "./lib/dom/server.js";
export { BrowserRouter, Form, HashRouter, Link, Links, MemoryRouter, Meta, NavLink, Navigate, Outlet, Route, Router, RouterProvider, Routes, ScrollRestoration, StaticRouter, StaticRouterProvider, AwaitContextProvider as UNSAFE_AwaitContextProvider, WithComponentProps as UNSAFE_WithComponentProps, WithErrorBoundaryProps as UNSAFE_WithErrorBoundaryProps, WithHydrateFallbackProps as UNSAFE_WithHydrateFallbackProps, HistoryRouter as unstable_HistoryRouter };
@@ -0,0 +1,17 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
"use client";
import { AwaitContextProvider } from "./lib/context.js";
import { MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, WithComponentProps, WithErrorBoundaryProps, WithHydrateFallbackProps } from "./lib/components.js";
import { Links, Meta } from "./lib/dom/ssr/components.js";
import { BrowserRouter, Form, HashRouter, HistoryRouter, Link, NavLink, ScrollRestoration } from "./lib/dom/lib.js";
import { StaticRouter, StaticRouterProvider } from "./lib/dom/server.js";
export { BrowserRouter, Form, HashRouter, Link, Links, MemoryRouter, Meta, NavLink, Navigate, Outlet, Route, Router, RouterProvider, Routes, ScrollRestoration, StaticRouter, StaticRouterProvider, AwaitContextProvider as UNSAFE_AwaitContextProvider, WithComponentProps as UNSAFE_WithComponentProps, WithErrorBoundaryProps as UNSAFE_WithErrorBoundaryProps, WithHydrateFallbackProps as UNSAFE_WithHydrateFallbackProps, HistoryRouter as unstable_HistoryRouter };
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
"use client";
import { Action, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, invariant, parsePath } from "./lib/router/history.js";
import { ErrorResponseImpl, RouterContextProvider, createContext, data, defaultMapRouteProperties, generatePath, isRouteErrorResponse, matchPath, matchRoutes, redirect, redirectDocument, replace, resolvePath } from "./lib/router/utils.js";
import { IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, createRouter, createStaticHandler } from "./lib/router/router.js";
import { AwaitContextProvider, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigationContext, RouteContext, ViewTransitionContext } from "./lib/context.js";
import { useActionData, useAsyncError, useAsyncValue, useBlocker, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRoute, useRouteError, useRouteLoaderData, useRouterState, useRoutes } from "./lib/hooks.js";
import { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, WithComponentProps, WithErrorBoundaryProps, WithHydrateFallbackProps, createMemoryRouter, createRoutesFromChildren, createRoutesFromElements, hydrationRouteProperties, renderMatches, withComponentProps, withErrorBoundaryProps, withHydrateFallbackProps } from "./lib/components.js";
import { createSearchParams } from "./lib/dom/dom.js";
import { SingleFetchRedirectSymbol, decodeViaTurboStream, getTurboStreamSingleFetchDataStrategy } from "./lib/dom/ssr/single-fetch.js";
import { RemixErrorBoundary } from "./lib/dom/ssr/errorBoundaries.js";
import { createClientRoutes, createClientRoutesWithHMRRevalidationOptOut, shouldHydrateRouteLoader } from "./lib/dom/ssr/routes.js";
import { getPatchRoutesOnNavigationFunction, useFogOFWarDiscovery } from "./lib/dom/ssr/fog-of-war.js";
import { FrameworkContext, Links, Meta, PrefetchPageLinks, Scripts } from "./lib/dom/ssr/components.js";
import { BrowserRouter, Form, HashRouter, HistoryRouter, Link, NavLink, ScrollRestoration, createBrowserRouter, createHashRouter, useBeforeUnload, useFetcher, useFetchers, useFormAction, useLinkClickHandler, usePrompt, useScrollRestoration, useSearchParams, useSubmit, useViewTransitionState } from "./lib/dom/lib.js";
import { StaticRouter, StaticRouterProvider, createStaticRouter } from "./lib/dom/server.js";
import { ServerRouter } from "./lib/dom/ssr/server.js";
import { createRoutesStub } from "./lib/dom/ssr/routes-test-stub.js";
import { createCookie, isCookie } from "./lib/server-runtime/cookies.js";
import { ServerMode } from "./lib/server-runtime/mode.js";
import { setDevServerHooks } from "./lib/server-runtime/dev.js";
import { createRequestHandler } from "./lib/server-runtime/server.js";
import { createSession, createSessionStorage, isSession } from "./lib/server-runtime/sessions.js";
import { createCookieSessionStorage } from "./lib/server-runtime/sessions/cookieStorage.js";
import { createMemorySessionStorage } from "./lib/server-runtime/sessions/memoryStorage.js";
import { href } from "./lib/href.js";
import { RSCDefaultRootErrorBoundary } from "./lib/rsc/errorBoundaries.js";
import { RSCStaticRouter, routeRSCServerRequest } from "./lib/rsc/server.ssr.js";
import { getHydrationData } from "./lib/dom/ssr/hydration.js";
//#region index.ts
/**
* @module index
* @mergeModuleWith react-router
*/
//#endregion
export { Await, BrowserRouter, Form, HashRouter, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, Link, Links, MemoryRouter, Meta, NavLink, Navigate, Action as NavigationType, Outlet, PrefetchPageLinks, Route, Router, RouterContextProvider, RouterProvider, Routes, Scripts, ScrollRestoration, ServerRouter, StaticRouter, StaticRouterProvider, AwaitContextProvider as UNSAFE_AwaitContextProvider, DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, FetchersContext as UNSAFE_FetchersContext, FrameworkContext as UNSAFE_FrameworkContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RSCDefaultRootErrorBoundary as UNSAFE_RSCDefaultRootErrorBoundary, RemixErrorBoundary as UNSAFE_RemixErrorBoundary, RouteContext as UNSAFE_RouteContext, ServerMode as UNSAFE_ServerMode, SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, ViewTransitionContext as UNSAFE_ViewTransitionContext, WithComponentProps as UNSAFE_WithComponentProps, WithErrorBoundaryProps as UNSAFE_WithErrorBoundaryProps, WithHydrateFallbackProps as UNSAFE_WithHydrateFallbackProps, createBrowserHistory as UNSAFE_createBrowserHistory, createClientRoutes as UNSAFE_createClientRoutes, createClientRoutesWithHMRRevalidationOptOut as UNSAFE_createClientRoutesWithHMRRevalidationOptOut, createHashHistory as UNSAFE_createHashHistory, createMemoryHistory as UNSAFE_createMemoryHistory, createRouter as UNSAFE_createRouter, decodeViaTurboStream as UNSAFE_decodeViaTurboStream, defaultMapRouteProperties as UNSAFE_defaultMapRouteProperties, getHydrationData as UNSAFE_getHydrationData, getPatchRoutesOnNavigationFunction as UNSAFE_getPatchRoutesOnNavigationFunction, getTurboStreamSingleFetchDataStrategy as UNSAFE_getTurboStreamSingleFetchDataStrategy, hydrationRouteProperties as UNSAFE_hydrationRouteProperties, invariant as UNSAFE_invariant, shouldHydrateRouteLoader as UNSAFE_shouldHydrateRouteLoader, useFogOFWarDiscovery as UNSAFE_useFogOFWarDiscovery, useScrollRestoration as UNSAFE_useScrollRestoration, withComponentProps as UNSAFE_withComponentProps, withErrorBoundaryProps as UNSAFE_withErrorBoundaryProps, withHydrateFallbackProps as UNSAFE_withHydrateFallbackProps, createBrowserRouter, createContext, createCookie, createCookieSessionStorage, createHashRouter, createMemoryRouter, createMemorySessionStorage, createPath, createRequestHandler, createRoutesFromChildren, createRoutesFromElements, createRoutesStub, createSearchParams, createSession, createSessionStorage, createStaticHandler, createStaticRouter, data, generatePath, href, isCookie, isRouteErrorResponse, isSession, matchPath, matchRoutes, parsePath, redirect, redirectDocument, renderMatches, replace, resolvePath, HistoryRouter as unstable_HistoryRouter, RSCStaticRouter as unstable_RSCStaticRouter, routeRSCServerRequest as unstable_routeRSCServerRequest, setDevServerHooks as unstable_setDevServerHooks, usePrompt as unstable_usePrompt, useRoute as unstable_useRoute, useRouterState as unstable_useRouterState, useActionData, useAsyncError, useAsyncValue, useBeforeUnload, useBlocker, useFetcher, useFetchers, useFormAction, useHref, useInRouterContext, useLinkClickHandler, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes, useSearchParams, useSubmit, useViewTransitionState };
@@ -0,0 +1,50 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/actions.ts
function throwIfPotentialCSRFAttack(request, allowedActionOrigins) {
let originHeader = request.headers.get("origin");
let originDomain = null;
try {
originDomain = typeof originHeader === "string" && originHeader !== "null" ? new URL(originHeader).host : originHeader;
} catch {
throw new Error(`\`origin\` header is not a valid URL. Aborting the action.`);
}
let host = new URL(request.url).host;
if (originDomain && originDomain !== host) {
if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` host does not match `origin` header from a forwarded action request. Aborting the action.");
}
}
function matchWildcardDomain(domain, pattern) {
const domainParts = domain.split(".");
const patternParts = pattern.split(".");
if (patternParts.length < 1) return false;
if (domainParts.length < patternParts.length) return false;
while (patternParts.length) {
const patternPart = patternParts.pop();
const domainPart = domainParts.pop();
switch (patternPart) {
case "": return false;
case "*": if (domainPart) continue;
else return false;
case "**":
if (patternParts.length > 0) return false;
return domainPart !== void 0;
case void 0:
default: if (domainPart !== patternPart) return false;
}
}
return domainParts.length === 0;
}
function isAllowedOrigin(originDomain, allowedActionOrigins = []) {
return allowedActionOrigins.some((allowedOrigin) => allowedOrigin && (allowedOrigin === originDomain || matchWildcardDomain(originDomain, allowedOrigin)));
}
//#endregion
export { throwIfPotentialCSRFAttack };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,841 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createMemoryHistory, invariant, parsePath, warning } from "./router/history.js";
import { defaultMapRouteProperties, getResolveToMatches, getRoutePattern, resolveTo, stripBasename } from "./router/utils.js";
import { createRouter } from "./router/router.js";
import { AwaitContext, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigationContext, RouteContext, ViewTransitionContext, useIsRSCRouterContext } from "./context.js";
import { _renderMatches, useActionData, useAsyncValue, useInRouterContext, useLoaderData, useLocation, useMatches, useNavigate, useOutlet, useParams, useRouteError, useRoutes, useRoutesImpl } from "./hooks.js";
import { warnOnce } from "./server-runtime/warnings.js";
import * as React$1 from "react";
import { useOptimistic } from "react";
//#region lib/components.tsx
const hydrationRouteProperties = ["HydrateFallback", "hydrateFallbackElement"];
/**
* Create a new {@link DataRouter} that manages the application path using an
* in-memory [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
* stack. Useful for non-browser environments without a DOM API.
*
* Data Routers should not be held in React state. You should create your router
* once outside of the React tree and pass it to {@link RouterProvider | `<RouterProvider>`}.
* You can use `patchRoutesOnNavigation` to add additional routes programmatically.
*
* @public
* @category Data Routers
* @mode data
* @param routes Application routes
* @param opts Options
* @param {MemoryRouterOpts.basename} opts.basename n/a
* @param {MemoryRouterOpts.dataStrategy} opts.dataStrategy n/a
* @param {MemoryRouterOpts.future} opts.future n/a
* @param {MemoryRouterOpts.getContext} opts.getContext n/a
* @param {MemoryRouterOpts.hydrationData} opts.hydrationData n/a
* @param {MemoryRouterOpts.initialEntries} opts.initialEntries n/a
* @param {MemoryRouterOpts.initialIndex} opts.initialIndex n/a
* @param {MemoryRouterOpts.instrumentations} opts.instrumentations n/a
* @param {MemoryRouterOpts.patchRoutesOnNavigation} opts.patchRoutesOnNavigation n/a
* @returns An initialized {@link DataRouter} to pass to {@link RouterProvider | `<RouterProvider>`}
*/
function createMemoryRouter(routes, opts) {
return createRouter({
basename: opts?.basename,
getContext: opts?.getContext,
future: opts?.future,
history: createMemoryHistory({
initialEntries: opts?.initialEntries,
initialIndex: opts?.initialIndex
}),
hydrationData: opts?.hydrationData,
routes,
mapRouteProperties: defaultMapRouteProperties,
hydrationRouteProperties,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
instrumentations: opts?.instrumentations
}).initialize();
}
var Deferred = class {
status = "pending";
promise;
resolve;
reject;
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = (value) => {
if (this.status === "pending") {
this.status = "resolved";
resolve(value);
}
};
this.reject = (reason) => {
if (this.status === "pending") {
this.status = "rejected";
reject(reason);
}
};
});
}
};
/**
* Render the UI for the given {@link DataRouter}. This component should
* typically be at the top of an app's element tree. The router prop should
* be a single router instance created outside of the React tree. Avoid
* creating new routers during React renders/re-renders.
*
* ```tsx
* import { createBrowserRouter } from "react-router";
* import { RouterProvider } from "react-router/dom";
* import { createRoot } from "react-dom/client";
*
* const router = createBrowserRouter(routes);
* createRoot(document.getElementById("root")).render(
* <RouterProvider router={router} />
* );
* ```
*
* <docs-info>Please note that this component is exported both from
* `react-router` and `react-router/dom` with the only difference being that the
* latter automatically wires up `react-dom`'s [`flushSync`](https://react.dev/reference/react-dom/flushSync)
* implementation. You _almost always_ want to use the version from
* `react-router/dom` unless you're running in a non-DOM environment.</docs-info>
*
*
* @public
* @category Data Routers
* @mode data
* @param props Props
* @param {RouterProviderProps.flushSync} props.flushSync n/a
* @param {RouterProviderProps.onError} props.onError n/a
* @param {RouterProviderProps.router} props.router n/a
* @param {RouterProviderProps.useTransitions} props.useTransitions n/a
* @returns React element for the rendered router
*/
function RouterProvider({ router, flushSync: reactDomFlushSyncImpl, onError, useTransitions }) {
useTransitions = useIsRSCRouterContext() || useTransitions;
let [_state, setStateImpl] = React$1.useState(router.state);
let [state, setOptimisticState] = useOptimistic(_state);
let [pendingState, setPendingState] = React$1.useState();
let [vtContext, setVtContext] = React$1.useState({ isTransitioning: false });
let [renderDfd, setRenderDfd] = React$1.useState();
let [transition, setTransition] = React$1.useState();
let [interruption, setInterruption] = React$1.useState();
let fetcherData = React$1.useRef(/* @__PURE__ */ new Map());
let setState = React$1.useCallback((newState, { deletedFetchers, newErrors, flushSync, viewTransitionOpts }) => {
if (newErrors && onError) Object.values(newErrors).forEach((error) => onError(error, {
location: newState.location,
params: newState.matches[0]?.params ?? {},
pattern: getRoutePattern(newState.matches)
}));
newState.fetchers.forEach((fetcher, key) => {
if (fetcher.data !== void 0) fetcherData.current.set(key, fetcher.data);
});
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
warnOnce(flushSync === false || reactDomFlushSyncImpl != null, "You provided the `flushSync` option to a router update, but you are not using the `<RouterProvider>` from `react-router/dom` so `ReactDOM.flushSync()` is unavailable. Please update your app to `import { RouterProvider } from \"react-router/dom\"` and ensure you have `react-dom` installed as a dependency to use the `flushSync` option.");
let isViewTransitionAvailable = router.window != null && router.window.document != null && typeof router.window.document.startViewTransition === "function";
warnOnce(viewTransitionOpts == null || isViewTransitionAvailable, "You provided the `viewTransition` option to a router update, but you do not appear to be running in a DOM environment as `window.startViewTransition` is not available.");
if (!viewTransitionOpts || !isViewTransitionAvailable) {
if (reactDomFlushSyncImpl && flushSync) reactDomFlushSyncImpl(() => setStateImpl(newState));
else if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => {
if (useTransitions === true) setOptimisticState((s) => getOptimisticRouterState(s, newState));
setStateImpl(newState);
});
return;
}
if (reactDomFlushSyncImpl && flushSync) {
reactDomFlushSyncImpl(() => {
if (transition) {
renderDfd?.resolve();
transition.skipTransition();
}
setVtContext({
isTransitioning: true,
flushSync: true,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
});
let t = router.window.document.startViewTransition(() => {
reactDomFlushSyncImpl(() => setStateImpl(newState));
});
t.finished.finally(() => {
reactDomFlushSyncImpl(() => {
setRenderDfd(void 0);
setTransition(void 0);
setPendingState(void 0);
setVtContext({ isTransitioning: false });
});
});
reactDomFlushSyncImpl(() => setTransition(t));
return;
}
if (transition) {
renderDfd?.resolve();
transition.skipTransition();
setInterruption({
state: newState,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
} else {
setPendingState(newState);
setVtContext({
isTransitioning: true,
flushSync: false,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
}
}, [
router.window,
reactDomFlushSyncImpl,
transition,
renderDfd,
useTransitions,
setOptimisticState,
onError
]);
React$1.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
React$1.useEffect(() => {
if (vtContext.isTransitioning && !vtContext.flushSync) setRenderDfd(new Deferred());
}, [vtContext]);
React$1.useEffect(() => {
if (renderDfd && pendingState && router.window) {
let newState = pendingState;
let renderPromise = renderDfd.promise;
let transition = router.window.document.startViewTransition(async () => {
if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => {
if (useTransitions === true) setOptimisticState((s) => getOptimisticRouterState(s, newState));
setStateImpl(newState);
});
await renderPromise;
});
transition.finished.finally(() => {
setRenderDfd(void 0);
setTransition(void 0);
setPendingState(void 0);
setVtContext({ isTransitioning: false });
});
setTransition(transition);
}
}, [
pendingState,
renderDfd,
router.window,
useTransitions,
setOptimisticState
]);
React$1.useEffect(() => {
if (renderDfd && pendingState && state.location.key === pendingState.location.key) renderDfd.resolve();
}, [
renderDfd,
transition,
state.location,
pendingState
]);
React$1.useEffect(() => {
if (!vtContext.isTransitioning && interruption) {
setPendingState(interruption.state);
setVtContext({
isTransitioning: true,
flushSync: false,
currentLocation: interruption.currentLocation,
nextLocation: interruption.nextLocation
});
setInterruption(void 0);
}
}, [vtContext.isTransitioning, interruption]);
let navigator = React$1.useMemo(() => {
return {
createHref: router.createHref,
encodeLocation: router.encodeLocation,
go: (n) => router.navigate(n),
push: (to, state, opts) => router.navigate(to, {
state,
preventScrollReset: opts?.preventScrollReset
}),
replace: (to, state, opts) => router.navigate(to, {
replace: true,
state,
preventScrollReset: opts?.preventScrollReset
})
};
}, [router]);
let basename = router.basename || "/";
let dataRouterContext = React$1.useMemo(() => ({
router,
navigator,
static: false,
basename,
onError
}), [
router,
navigator,
basename,
onError
]);
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React$1.createElement(DataRouterStateContext.Provider, { value: state }, /* @__PURE__ */ React$1.createElement(FetchersContext.Provider, { value: fetcherData.current }, /* @__PURE__ */ React$1.createElement(ViewTransitionContext.Provider, { value: vtContext }, /* @__PURE__ */ React$1.createElement(Router, {
basename,
location: state.location,
navigationType: state.historyAction,
navigator,
useTransitions
}, /* @__PURE__ */ React$1.createElement(MemoizedDataRoutes, {
routes: router.routes,
manifest: router.manifest,
future: router.future,
state,
isStatic: false,
onError
})))))), null);
}
function getOptimisticRouterState(currentState, newState) {
return {
...currentState,
navigation: newState.navigation.state !== "idle" ? newState.navigation : currentState.navigation,
revalidation: newState.revalidation !== "idle" ? newState.revalidation : currentState.revalidation,
actionData: newState.navigation.state !== "submitting" ? newState.actionData : currentState.actionData,
fetchers: newState.fetchers
};
}
const MemoizedDataRoutes = React$1.memo(DataRoutes);
function DataRoutes({ routes, manifest, future, state, isStatic, onError }) {
return useRoutesImpl(routes, void 0, {
manifest,
state,
isStatic,
onError,
future
});
}
/**
* A declarative {@link Router | `<Router>`} that stores all entries in memory.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {MemoryRouterProps.basename} props.basename n/a
* @param {MemoryRouterProps.children} props.children n/a
* @param {MemoryRouterProps.initialEntries} props.initialEntries n/a
* @param {MemoryRouterProps.initialIndex} props.initialIndex n/a
* @param {MemoryRouterProps.useTransitions} props.useTransitions n/a
* @returns A declarative in-memory {@link Router | `<Router>`} for client-side
* routing.
*/
function MemoryRouter({ basename, children, initialEntries, initialIndex, useTransitions }) {
let historyRef = React$1.useRef(null);
if (historyRef.current == null) historyRef.current = createMemoryHistory({
initialEntries,
initialIndex,
v5Compat: true
});
let history = historyRef.current;
let [state, setStateImpl] = React$1.useState({
action: history.action,
location: history.location
});
let setState = React$1.useCallback((newState) => {
if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => setStateImpl(newState));
}, [useTransitions]);
React$1.useLayoutEffect(() => history.listen(setState), [history, setState]);
return /* @__PURE__ */ React$1.createElement(Router, {
basename,
children,
location: state.location,
navigationType: state.action,
navigator: history,
useTransitions
});
}
/**
* A component-based version of {@link useNavigate} to use in a
* [`React.Component` class](https://react.dev/reference/react/Component) where
* hooks cannot be used.
*
* It's recommended to avoid using this component in favor of {@link useNavigate}.
*
* @example
* <Navigate to="/tasks" />
*
* @public
* @category Components
* @param props Props
* @param {NavigateProps.relative} props.relative n/a
* @param {NavigateProps.replace} props.replace n/a
* @param {NavigateProps.state} props.state n/a
* @param {NavigateProps.to} props.to n/a
* @returns {void}
*
*/
function Navigate({ to, replace, state, relative }) {
invariant(useInRouterContext(), `<Navigate> may be used only in the context of a <Router> component.`);
let { static: isStatic } = React$1.useContext(NavigationContext);
warning(!isStatic, "<Navigate> must not be used on the initial render in a <StaticRouter>. This is a no-op, but you should modify your code so the <Navigate> is only ever rendered in response to some user interaction or state change.");
let { matches } = React$1.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
let navigate = useNavigate();
let path = resolveTo(to, getResolveToMatches(matches), locationPathname, relative === "path");
let jsonPath = JSON.stringify(path);
React$1.useEffect(() => {
navigate(JSON.parse(jsonPath), {
replace,
state,
relative
});
}, [
navigate,
jsonPath,
relative,
replace,
state
]);
return null;
}
/**
* Renders the matching child route of a parent route or nothing if no child
* route matches.
*
* @example
* import { Outlet } from "react-router";
*
* export default function SomeParent() {
* return (
* <div>
* <h1>Parent Content</h1>
* <Outlet />
* </div>
* );
* }
*
* @public
* @category Components
* @param props Props
* @param {OutletProps.context} props.context n/a
* @returns React element for the rendered outlet or `null` if no child route matches.
*/
function Outlet(props) {
return useOutlet(props.context);
}
/**
* Configures an element to render when a pattern matches the current location.
* It must be rendered within a {@link Routes} element. Note that these routes
* do not participate in data loading, actions, code splitting, or any other
* route module features.
*
* @example
* // Usually used in a declarative router
* function App() {
* return (
* <BrowserRouter>
* <Routes>
* <Route index element={<StepOne />} />
* <Route path="step-2" element={<StepTwo />} />
* <Route path="step-3" element={<StepThree />} />
* </Routes>
* </BrowserRouter>
* );
* }
*
* // But can be used with a data router as well if you prefer the JSX notation
* const routes = createRoutesFromElements(
* <>
* <Route index loader={step1Loader} Component={StepOne} />
* <Route path="step-2" loader={step2Loader} Component={StepTwo} />
* <Route path="step-3" loader={step3Loader} Component={StepThree} />
* </>
* );
*
* const router = createBrowserRouter(routes);
*
* function App() {
* return <RouterProvider router={router} />;
* }
*
* @public
* @category Components
* @param props Props
* @param {PathRouteProps.action} props.action n/a
* @param {PathRouteProps.caseSensitive} props.caseSensitive n/a
* @param {PathRouteProps.Component} props.Component n/a
* @param {PathRouteProps.children} props.children n/a
* @param {PathRouteProps.element} props.element n/a
* @param {PathRouteProps.ErrorBoundary} props.ErrorBoundary n/a
* @param {PathRouteProps.errorElement} props.errorElement n/a
* @param {PathRouteProps.handle} props.handle n/a
* @param {PathRouteProps.HydrateFallback} props.HydrateFallback n/a
* @param {PathRouteProps.hydrateFallbackElement} props.hydrateFallbackElement n/a
* @param {PathRouteProps.id} props.id n/a
* @param {PathRouteProps.index} props.index n/a
* @param {PathRouteProps.lazy} props.lazy n/a
* @param {PathRouteProps.loader} props.loader n/a
* @param {PathRouteProps.path} props.path n/a
* @param {PathRouteProps.shouldRevalidate} props.shouldRevalidate n/a
* @returns {void}
*/
function Route(props) {
invariant(false, "A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.");
}
/**
* Provides location context for the rest of the app.
*
* Note: You usually won't render a `<Router>` directly. Instead, you'll render a
* router that is more specific to your environment such as a {@link BrowserRouter}
* in web browsers or a {@link ServerRouter} for server rendering.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {RouterProps.basename} props.basename n/a
* @param {RouterProps.children} props.children n/a
* @param {RouterProps.location} props.location n/a
* @param {RouterProps.navigationType} props.navigationType n/a
* @param {RouterProps.navigator} props.navigator n/a
* @param {RouterProps.static} props.static n/a
* @param {RouterProps.useTransitions} props.useTransitions n/a
* @returns React element for the rendered router or `null` if the location does
* not match the {@link props.basename}
*/
function Router({ basename: basenameProp = "/", children = null, location: locationProp, navigationType = "POP", navigator, static: staticProp = false, useTransitions }) {
invariant(!useInRouterContext(), "You cannot render a <Router> inside another <Router>. You should never have more than one in your app.");
let basename = basenameProp.replace(/^\/*/, "/");
let navigationContext = React$1.useMemo(() => ({
basename,
navigator,
static: staticProp,
useTransitions,
future: {}
}), [
basename,
navigator,
staticProp,
useTransitions
]);
if (typeof locationProp === "string") locationProp = parsePath(locationProp);
let { pathname = "/", search = "", hash = "", state = null, key = "default", mask } = locationProp;
let locationContext = React$1.useMemo(() => {
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) return null;
return {
location: {
pathname: trailingPathname,
search,
hash,
state,
key,
mask
},
navigationType
};
}, [
basename,
pathname,
search,
hash,
state,
key,
navigationType,
mask
]);
warning(locationContext != null, `<Router basename="${basename}"> is not able to match the URL "${pathname}${search}${hash}" because it does not start with the basename, so the <Router> won't render anything.`);
if (locationContext == null) return null;
return /* @__PURE__ */ React$1.createElement(NavigationContext.Provider, { value: navigationContext }, /* @__PURE__ */ React$1.createElement(LocationContext.Provider, {
children,
value: locationContext
}));
}
/**
* Renders a branch of {@link Route | `<Route>`s} that best matches the current
* location. Note that these routes do not participate in [data loading](../../start/framework/route-module#loader),
* [`action`](../../start/framework/route-module#action), code splitting, or
* any other [route module](../../start/framework/route-module) features.
*
* @example
* import { Route, Routes } from "react-router";
*
* <Routes>
* <Route index element={<StepOne />} />
* <Route path="step-2" element={<StepTwo />} />
* <Route path="step-3" element={<StepThree />} />
* </Routes>
*
* @public
* @category Components
* @param props Props
* @param {RoutesProps.children} props.children n/a
* @param {RoutesProps.location} props.location n/a
* @returns React element for the rendered routes or `null` if no route matches
*/
function Routes({ children, location }) {
return useRoutes(createRoutesFromChildren(children), location);
}
/**
* Used to render promise values with automatic error handling.
*
* **Note:** `<Await>` expects to be rendered inside a [`<React.Suspense>`](https://react.dev/reference/react/Suspense)
*
* @example
* import { Await, useLoaderData } from "react-router";
*
* export async function loader() {
* // not awaited
* const reviews = getReviews();
* // awaited (blocks the transition)
* const book = await fetch("/api/book").then((res) => res.json());
* return { book, reviews };
* }
*
* function Book() {
* const { book, reviews } = useLoaderData();
* return (
* <div>
* <h1>{book.title}</h1>
* <p>{book.description}</p>
* <React.Suspense fallback={<ReviewsSkeleton />}>
* <Await
* resolve={reviews}
* errorElement={
* <div>Could not load reviews 😬</div>
* }
* children={(resolvedReviews) => (
* <Reviews items={resolvedReviews} />
* )}
* />
* </React.Suspense>
* </div>
* );
* }
*
* @public
* @category Components
* @mode framework
* @mode data
* @param props Props
* @param {AwaitProps.children} props.children n/a
* @param {AwaitProps.errorElement} props.errorElement n/a
* @param {AwaitProps.resolve} props.resolve n/a
* @returns React element for the rendered awaited value
*/
function Await({ children, errorElement, resolve }) {
let dataRouterContext = React$1.useContext(DataRouterContext);
let dataRouterStateContext = React$1.useContext(DataRouterStateContext);
let onError = React$1.useCallback((error, errorInfo) => {
if (dataRouterContext && dataRouterContext.onError && dataRouterStateContext) dataRouterContext.onError(error, {
location: dataRouterStateContext.location,
params: dataRouterStateContext.matches[0]?.params || {},
pattern: getRoutePattern(dataRouterStateContext.matches),
errorInfo
});
}, [dataRouterContext, dataRouterStateContext]);
return /* @__PURE__ */ React$1.createElement(AwaitErrorBoundary, {
resolve,
errorElement,
onError
}, /* @__PURE__ */ React$1.createElement(ResolveAwait, null, children));
}
var AwaitErrorBoundary = class extends React$1.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, errorInfo) {
if (this.props.onError) this.props.onError(error, errorInfo);
else console.error("<Await> caught the following error during render", error, errorInfo);
}
render() {
let { children, errorElement, resolve } = this.props;
let promise = null;
let status = 0;
if (!(resolve instanceof Promise)) {
status = 1;
promise = Promise.resolve();
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_data", { get: () => resolve });
} else if (this.state.error) {
status = 2;
let renderError = this.state.error;
promise = Promise.reject().catch(() => {});
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_error", { get: () => renderError });
} else if (resolve._tracked) {
promise = resolve;
status = "_error" in promise ? 2 : "_data" in promise ? 1 : 0;
} else {
status = 0;
Object.defineProperty(resolve, "_tracked", { get: () => true });
promise = resolve.then((data) => Object.defineProperty(resolve, "_data", { get: () => data }), (error) => {
this.props.onError?.(error);
Object.defineProperty(resolve, "_error", { get: () => error });
});
}
if (status === 2 && !errorElement) throw promise._error;
if (status === 2) return /* @__PURE__ */ React$1.createElement(AwaitContext.Provider, {
value: promise,
children: errorElement
});
if (status === 1) return /* @__PURE__ */ React$1.createElement(AwaitContext.Provider, {
value: promise,
children
});
throw promise;
}
};
function ResolveAwait({ children }) {
let data = useAsyncValue();
let toRender = typeof children === "function" ? children(data) : children;
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, toRender);
}
/**
* Creates a route config from a React "children" object, which is usually
* either a `<Route>` element or an array of them. Used internally by
* `<Routes>` to create a route config from its children.
*
* @category Utils
* @mode data
* @param children The React children to convert into a route config
* @param parentPath The path of the parent route, used to generate unique IDs.
* @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter}
*/
function createRoutesFromChildren(children, parentPath = []) {
let routes = [];
React$1.Children.forEach(children, (element, index) => {
if (!React$1.isValidElement(element)) return;
let treePath = [...parentPath, index];
if (element.type === React$1.Fragment) {
routes.push.apply(routes, createRoutesFromChildren(element.props.children, treePath));
return;
}
invariant(element.type === Route, `[${typeof element.type === "string" ? element.type : element.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`);
let props = element.props;
invariant(!props.index || !props.children, "An index route cannot have child routes.");
let route = {
id: props.id || treePath.join("-"),
caseSensitive: props.caseSensitive,
element: props.element,
Component: props.Component,
index: props.index,
path: props.path,
middleware: props.middleware,
loader: props.loader,
action: props.action,
hydrateFallbackElement: props.hydrateFallbackElement,
HydrateFallback: props.HydrateFallback,
errorElement: props.errorElement,
ErrorBoundary: props.ErrorBoundary,
shouldRevalidate: props.shouldRevalidate,
handle: props.handle,
lazy: props.lazy
};
if (props.children) route.children = createRoutesFromChildren(props.children, treePath);
routes.push(route);
});
return routes;
}
/**
* Create route objects from JSX elements instead of arrays of objects.
*
* @example
* const routes = createRoutesFromElements(
* <>
* <Route index loader={step1Loader} Component={StepOne} />
* <Route path="step-2" loader={step2Loader} Component={StepTwo} />
* <Route path="step-3" loader={step3Loader} Component={StepThree} />
* </>
* );
*
* const router = createBrowserRouter(routes);
*
* function App() {
* return <RouterProvider router={router} />;
* }
*
* @name createRoutesFromElements
* @public
* @category Utils
* @mode data
* @param children The React children to convert into a route config
* @param parentPath The path of the parent route, used to generate unique IDs.
* This is used for internal recursion and is not intended to be used by the
* application developer.
* @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter}
*/
const createRoutesFromElements = createRoutesFromChildren;
/**
* Renders the result of {@link matchRoutes} into a React element.
*
* @public
* @category Utils
* @param matches The array of {@link RouteMatch | route matches} to render
* @returns A React element that renders the matched routes or `null` if no matches
*/
function renderMatches(matches) {
return _renderMatches(matches);
}
function useRouteComponentProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData(),
matches: useMatches()
};
}
function WithComponentProps({ children }) {
const props = useRouteComponentProps();
return React$1.cloneElement(children, props);
}
function withComponentProps(Component) {
return function WithComponentProps() {
const props = useRouteComponentProps();
return React$1.createElement(Component, props);
};
}
function useHydrateFallbackProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData()
};
}
function WithHydrateFallbackProps({ children }) {
const props = useHydrateFallbackProps();
return React$1.cloneElement(children, props);
}
function withHydrateFallbackProps(HydrateFallback) {
return function WithHydrateFallbackProps() {
const props = useHydrateFallbackProps();
return React$1.createElement(HydrateFallback, props);
};
}
function useErrorBoundaryProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData(),
error: useRouteError()
};
}
function WithErrorBoundaryProps({ children }) {
const props = useErrorBoundaryProps();
return React$1.cloneElement(children, props);
}
function withErrorBoundaryProps(ErrorBoundary) {
return function WithErrorBoundaryProps() {
const props = useErrorBoundaryProps();
return React$1.createElement(ErrorBoundary, props);
};
}
//#endregion
export { Await, DataRoutes, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, WithComponentProps, WithErrorBoundaryProps, WithHydrateFallbackProps, createMemoryRouter, createRoutesFromChildren, createRoutesFromElements, hydrationRouteProperties, renderMatches, withComponentProps, withErrorBoundaryProps, withHydrateFallbackProps };
@@ -0,0 +1,83 @@
import { Action, History, Location, To } from "./router/history.js";
import { RouteMatch, TrackedPromise } from "./router/utils.js";
import { RelativeRoutingType, Router, RouterState, StaticHandlerContext } from "./router/router.js";
import { ClientOnErrorFunction } from "./components.js";
import * as React$1 from "react";
//#region lib/context.d.ts
interface DataRouterContextObject extends Omit<NavigationContextObject, "future" | "useTransitions"> {
router: Router;
staticContext?: StaticHandlerContext;
onError?: ClientOnErrorFunction;
}
declare const DataRouterContext: React$1.Context<DataRouterContextObject | null>;
declare const DataRouterStateContext: React$1.Context<RouterState | null>;
type ViewTransitionContextObject = {
isTransitioning: false;
} | {
isTransitioning: true;
flushSync: boolean;
currentLocation: Location;
nextLocation: Location;
};
declare const ViewTransitionContext: React$1.Context<ViewTransitionContextObject>;
type FetchersContextObject = Map<string, any>;
declare const FetchersContext: React$1.Context<FetchersContextObject>;
declare const AwaitContext: React$1.Context<TrackedPromise | null>;
declare const AwaitContextProvider: (props: React$1.ComponentProps<typeof AwaitContext.Provider>) => React$1.FunctionComponentElement<React$1.ProviderProps<TrackedPromise | null>>;
interface NavigateOptions {
/** Replace the current entry in the history stack instead of pushing a new one */
replace?: boolean;
/** Masked URL */
mask?: To;
/** Adds persistent client side routing state to the next location */
state?: any;
/** If you are using {@link ScrollRestoration `<ScrollRestoration>`}, prevent the scroll position from being reset to the top of the window when navigating */
preventScrollReset?: boolean;
/** Defines the relative path behavior for the link. "route" will use the route hierarchy so ".." will remove all URL segments of the current route pattern while "path" will use the URL path so ".." will remove one URL segment. */
relative?: RelativeRoutingType;
/** Wraps the initial state update for this navigation in a {@link https://react.dev/reference/react-dom/flushSync ReactDOM.flushSync} call instead of the default {@link https://react.dev/reference/react/startTransition React.startTransition} */
flushSync?: boolean;
/** Enables a {@link https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API View Transition} for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the {@link useViewTransitionState `useViewTransitionState()`} hook. */
viewTransition?: boolean;
/** Specifies the default revalidation behavior after this submission */
defaultShouldRevalidate?: boolean;
}
/**
* A Navigator is a "location changer"; it's how you get to different locations.
*
* Every history instance conforms to the Navigator interface, but the
* distinction is useful primarily when it comes to the low-level `<Router>` API
* where both the location and a navigator must be provided separately in order
* to avoid "tearing" that may occur in a suspense-enabled app if the action
* and/or location were to be read directly from the history instance.
*/
interface Navigator {
createHref: History["createHref"];
encodeLocation?: History["encodeLocation"];
go: History["go"];
push(to: To, state?: any, opts?: NavigateOptions): void;
replace(to: To, state?: any, opts?: NavigateOptions): void;
}
interface NavigationContextObject {
basename: string;
navigator: Navigator;
static: boolean;
useTransitions: boolean | undefined;
future: {};
}
declare const NavigationContext: React$1.Context<NavigationContextObject>;
interface LocationContextObject {
location: Location;
navigationType: Action;
}
declare const LocationContext: React$1.Context<LocationContextObject>;
interface RouteContextObject {
outlet: React$1.ReactElement | null;
matches: RouteMatch[];
isDataRoute: boolean;
}
declare const RouteContext: React$1.Context<RouteContextObject>;
//#endregion
export { AwaitContextProvider, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigateOptions, NavigationContext, Navigator, RouteContext, ViewTransitionContext };
@@ -0,0 +1,41 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import * as React$1 from "react";
//#region lib/context.ts
const DataRouterContext = React$1.createContext(null);
DataRouterContext.displayName = "DataRouter";
const DataRouterStateContext = React$1.createContext(null);
DataRouterStateContext.displayName = "DataRouterState";
const RSCRouterContext = React$1.createContext(false);
function useIsRSCRouterContext() {
return React$1.useContext(RSCRouterContext);
}
const ViewTransitionContext = React$1.createContext({ isTransitioning: false });
ViewTransitionContext.displayName = "ViewTransition";
const FetchersContext = React$1.createContext(/* @__PURE__ */ new Map());
FetchersContext.displayName = "Fetchers";
const AwaitContext = React$1.createContext(null);
AwaitContext.displayName = "Await";
const AwaitContextProvider = (props) => React$1.createElement(AwaitContext.Provider, props);
const NavigationContext = React$1.createContext(null);
NavigationContext.displayName = "Navigation";
const LocationContext = React$1.createContext(null);
LocationContext.displayName = "Location";
const RouteContext = React$1.createContext({
outlet: null,
matches: [],
isDataRoute: false
});
RouteContext.displayName = "Route";
const RouteErrorContext = React$1.createContext(null);
RouteErrorContext.displayName = "RouteError";
//#endregion
export { AwaitContext, AwaitContextProvider, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigationContext, RSCRouterContext, RouteContext, RouteErrorContext, ViewTransitionContext, useIsRSCRouterContext };
@@ -0,0 +1,9 @@
import * as React$1 from "react";
import { RouterProviderProps } from "react-router";
//#region lib/dom-export/dom-router-provider.d.ts
type RouterProviderProps$1 = Omit<RouterProviderProps, "flushSync">;
declare function RouterProvider$1(props: RouterProviderProps$1): React$1.JSX.Element;
//#endregion
export { RouterProvider$1 as RouterProvider, RouterProviderProps$1 as RouterProviderProps };
@@ -0,0 +1,22 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import * as React$1 from "react";
import * as ReactDOM from "react-dom";
import { RouterProvider } from "react-router";
//#region lib/dom-export/dom-router-provider.tsx
function RouterProvider$1(props) {
return /* @__PURE__ */ React$1.createElement(RouterProvider, {
flushSync: ReactDOM.flushSync,
...props
});
}
//#endregion
export { RouterProvider$1 as RouterProvider };
@@ -0,0 +1,125 @@
import { ClientInstrumentation } from "../router/instrumentation.js";
import * as React$1 from "react";
import { ClientOnErrorFunction, RouterInit } from "react-router";
//#region lib/dom-export/hydrated-router.d.ts
/**
* Props for the {@link dom.HydratedRouter} component.
*
* @category Types
*/
interface HydratedRouterProps {
/**
* Context factory function to be passed through to {@link createBrowserRouter}.
* This function will be called to create a fresh `context` instance on each
* navigation/fetch and made available to
* [`clientAction`](../../start/framework/route-module#clientAction)/[`clientLoader`](../../start/framework/route-module#clientLoader)
* functions.
*/
getContext?: RouterInit["getContext"];
/**
* Array of instrumentation objects allowing you to instrument the router and
* individual routes prior to router initialization (and on any subsequently
* added routes via `route.lazy` or `patchRoutesOnNavigation`). This is
* mostly useful for observability such as wrapping navigations, fetches,
* as well as route loaders/actions/middlewares with logging and/or performance
* tracing. See the [docs](../../how-to/instrumentation) for more information.
*
* ```tsx
* const logging = {
* router({ instrument }) {
* instrument({
* navigate: (impl, { to }) => logExecution(`navigate ${to}`, impl),
* fetch: (impl, { to }) => logExecution(`fetch ${to}`, impl)
* });
* },
* route({ instrument, id }) {
* instrument({
* middleware: (impl, { request }) => logExecution(
* `middleware ${request.url} (route ${id})`,
* impl
* ),
* loader: (impl, { request }) => logExecution(
* `loader ${request.url} (route ${id})`,
* impl
* ),
* action: (impl, { request }) => logExecution(
* `action ${request.url} (route ${id})`,
* impl
* ),
* })
* }
* };
*
* async function logExecution(label: string, impl: () => Promise<void>) {
* let start = performance.now();
* console.log(`start ${label}`);
* await impl();
* let duration = Math.round(performance.now() - start);
* console.log(`end ${label} (${duration}ms)`);
* }
*
* startTransition(() => {
* hydrateRoot(
* document,
* <HydratedRouter instrumentations={[logging]} />
* );
* });
* ```
*/
instrumentations?: ClientInstrumentation[];
/**
* An error handler function that will be called for any middleware, loader, action,
* or render errors that are encountered in your application. This is useful for
* logging or reporting errors instead of in the {@link ErrorBoundary} because it's not
* subject to re-rendering and will only run one time per error.
*
* The `errorInfo` parameter is passed along from
* [`componentDidCatch`](https://react.dev/reference/react/Component#componentdidcatch)
* and is only present for render errors.
*
* ```tsx
* <HydratedRouter onError={(error, info) => {
* let { location, params, pattern, errorInfo } = info;
* console.error(error, location, errorInfo);
* reportToErrorService(error, location, errorInfo);
* }} />
* ```
*/
onError?: ClientOnErrorFunction;
/**
* Control whether router state updates are internally wrapped in
* [`React.startTransition`](https://react.dev/reference/react/startTransition).
*
* - When left `undefined`, all state updates are wrapped in
* `React.startTransition`
* - This can lead to buggy behaviors if you are wrapping your own
* navigations/fetchers in `startTransition`.
* - When set to `true`, {@link Link} and {@link Form} navigations will be wrapped
* in `React.startTransition` and router state changes will be wrapped in
* `React.startTransition` and also sent through
* [`useOptimistic`](https://react.dev/reference/react/useOptimistic) to
* surface mid-navigation router state changes to the UI.
* - When set to `false`, the router will not leverage `React.startTransition` or
* `React.useOptimistic` on any navigations or state changes.
*
* For more information, please see the [docs](../../explanation/react-transitions).
*/
useTransitions?: boolean;
}
/**
* Framework-mode router component to be used to hydrate a router from a
* {@link ServerRouter}. See [`entry.client.tsx`](../framework-conventions/entry.client.tsx).
*
* @public
* @category Framework Routers
* @mode framework
* @param props Props
* @param {dom.HydratedRouterProps.getContext} props.getContext n/a
* @param {dom.HydratedRouterProps.onError} props.onError n/a
* @returns A React element that represents the hydrated application.
*/
declare function HydratedRouter(props: HydratedRouterProps): React$1.JSX.Element;
//#endregion
export { HydratedRouter, HydratedRouterProps };
@@ -0,0 +1,150 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { CRITICAL_CSS_DATA_ATTRIBUTE } from "../dom/ssr/components.js";
import { RouterProvider as RouterProvider$1 } from "./dom-router-provider.js";
import * as React$1 from "react";
import { UNSAFE_FrameworkContext, UNSAFE_RemixErrorBoundary, UNSAFE_createBrowserHistory, UNSAFE_createClientRoutes, UNSAFE_createClientRoutesWithHMRRevalidationOptOut, UNSAFE_createRouter, UNSAFE_decodeViaTurboStream, UNSAFE_defaultMapRouteProperties, UNSAFE_getHydrationData, UNSAFE_getPatchRoutesOnNavigationFunction, UNSAFE_getTurboStreamSingleFetchDataStrategy, UNSAFE_hydrationRouteProperties, UNSAFE_invariant, UNSAFE_useFogOFWarDiscovery } from "react-router";
//#region lib/dom-export/hydrated-router.tsx
let ssrInfo = null;
let router = null;
function initSsrInfo() {
if (!ssrInfo && window.__reactRouterContext && window.__reactRouterManifest && window.__reactRouterRouteModules) {
if (window.__reactRouterManifest.sri === true) {
const importMap = document.querySelector("script[rr-importmap]");
if (importMap?.textContent) try {
window.__reactRouterManifest.sri = JSON.parse(importMap.textContent).integrity;
} catch (err) {
console.error("Failed to parse import map", err);
}
}
ssrInfo = {
context: window.__reactRouterContext,
manifest: window.__reactRouterManifest,
routeModules: window.__reactRouterRouteModules,
stateDecodingPromise: void 0,
router: void 0,
routerInitialized: false
};
}
}
function createHydratedRouter({ getContext, instrumentations }) {
initSsrInfo();
if (!ssrInfo) throw new Error("You must be using the SSR features of React Router in order to skip passing a `router` prop to `<RouterProvider>`");
let localSsrInfo = ssrInfo;
if (!ssrInfo.stateDecodingPromise) {
let stream = ssrInfo.context.stream;
UNSAFE_invariant(stream, "No stream found for single fetch decoding");
ssrInfo.context.stream = void 0;
ssrInfo.stateDecodingPromise = UNSAFE_decodeViaTurboStream(stream, window).then((value) => {
ssrInfo.context.state = value.value;
localSsrInfo.stateDecodingPromise.value = true;
}).catch((e) => {
localSsrInfo.stateDecodingPromise.error = e;
});
}
if (ssrInfo.stateDecodingPromise.error) throw ssrInfo.stateDecodingPromise.error;
if (!ssrInfo.stateDecodingPromise.value) throw ssrInfo.stateDecodingPromise;
let routes = UNSAFE_createClientRoutes(ssrInfo.manifest.routes, ssrInfo.routeModules, ssrInfo.context.state, ssrInfo.context.ssr, ssrInfo.context.isSpaMode);
let hydrationData = void 0;
if (ssrInfo.context.isSpaMode) {
let { loaderData } = ssrInfo.context.state;
if (ssrInfo.manifest.routes.root?.hasLoader && loaderData && "root" in loaderData) hydrationData = { loaderData: { root: loaderData.root } };
} else hydrationData = UNSAFE_getHydrationData({
state: ssrInfo.context.state,
routes,
getRouteInfo: (routeId) => ({
clientLoader: ssrInfo.routeModules[routeId]?.clientLoader,
hasLoader: ssrInfo.manifest.routes[routeId]?.hasLoader === true,
hasHydrateFallback: ssrInfo.routeModules[routeId]?.HydrateFallback != null
}),
location: window.location,
basename: window.__reactRouterContext?.basename,
isSpaMode: ssrInfo.context.isSpaMode
});
if (window.history.state && window.history.state.masked) window.history.replaceState({
...window.history.state,
masked: void 0
}, "");
let router = UNSAFE_createRouter({
routes,
history: UNSAFE_createBrowserHistory(),
basename: ssrInfo.context.basename,
getContext,
hydrationData,
mapRouteProperties: UNSAFE_defaultMapRouteProperties,
hydrationRouteProperties: UNSAFE_hydrationRouteProperties,
instrumentations,
dataStrategy: UNSAFE_getTurboStreamSingleFetchDataStrategy(() => router, ssrInfo.manifest, ssrInfo.routeModules, ssrInfo.context.ssr),
patchRoutesOnNavigation: UNSAFE_getPatchRoutesOnNavigationFunction(() => router, ssrInfo.manifest, ssrInfo.routeModules, ssrInfo.context.ssr, ssrInfo.context.routeDiscovery, ssrInfo.context.isSpaMode, ssrInfo.context.basename)
});
ssrInfo.router = router;
if (router.state.initialized) {
ssrInfo.routerInitialized = true;
router.initialize();
}
router.createRoutesForHMR = UNSAFE_createClientRoutesWithHMRRevalidationOptOut;
window.__reactRouterDataRouter = router;
return router;
}
/**
* Framework-mode router component to be used to hydrate a router from a
* {@link ServerRouter}. See [`entry.client.tsx`](../framework-conventions/entry.client.tsx).
*
* @public
* @category Framework Routers
* @mode framework
* @param props Props
* @param {dom.HydratedRouterProps.getContext} props.getContext n/a
* @param {dom.HydratedRouterProps.onError} props.onError n/a
* @returns A React element that represents the hydrated application.
*/
function HydratedRouter(props) {
if (!router) router = createHydratedRouter({
getContext: props.getContext,
instrumentations: props.instrumentations
});
let [criticalCss, setCriticalCss] = React$1.useState(process.env.NODE_ENV === "development" ? ssrInfo?.context.criticalCss : void 0);
React$1.useEffect(() => {
if (process.env.NODE_ENV === "development") setCriticalCss(void 0);
}, []);
React$1.useEffect(() => {
if (process.env.NODE_ENV === "development" && criticalCss === void 0) document.querySelectorAll(`[${CRITICAL_CSS_DATA_ATTRIBUTE}]`).forEach((element) => element.remove());
}, [criticalCss]);
let [location, setLocation] = React$1.useState(router.state.location);
React$1.useLayoutEffect(() => {
if (ssrInfo && ssrInfo.router && !ssrInfo.routerInitialized) {
ssrInfo.routerInitialized = true;
ssrInfo.router.initialize();
}
}, []);
React$1.useLayoutEffect(() => {
if (ssrInfo && ssrInfo.router) return ssrInfo.router.subscribe((newState) => {
if (newState.location !== location) setLocation(newState.location);
});
}, [location]);
UNSAFE_invariant(ssrInfo, "ssrInfo unavailable for HydratedRouter");
UNSAFE_useFogOFWarDiscovery(router, ssrInfo.manifest, ssrInfo.routeModules, ssrInfo.context.ssr, ssrInfo.context.routeDiscovery, ssrInfo.context.isSpaMode);
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(UNSAFE_FrameworkContext.Provider, { value: {
manifest: ssrInfo.manifest,
routeModules: ssrInfo.routeModules,
future: ssrInfo.context.future,
criticalCss,
ssr: ssrInfo.context.ssr,
isSpaMode: ssrInfo.context.isSpaMode,
routeDiscovery: ssrInfo.context.routeDiscovery
} }, /* @__PURE__ */ React$1.createElement(UNSAFE_RemixErrorBoundary, { location }, /* @__PURE__ */ React$1.createElement(RouterProvider$1, {
router,
useTransitions: props.useTransitions,
onError: props.onError
}))), /* @__PURE__ */ React$1.createElement(React$1.Fragment, null));
}
//#endregion
export { HydratedRouter };
@@ -0,0 +1,119 @@
import { FormEncType, HTMLFormMethod } from "../router/utils.js";
import { RelativeRoutingType } from "../router/router.js";
//#region lib/dom/dom.d.ts
type ParamKeyValuePair = [string, string];
type URLSearchParamsInit = string | ParamKeyValuePair[] | Record<string, string | string[]> | URLSearchParams;
/**
* Creates a URLSearchParams object using the given initializer.
*
* This is identical to `new URLSearchParams(init)` except it also supports
* arrays as values in the object form of the initializer instead of just
* strings. This is convenient when you need multiple values for a given key,
* but don't want to use an array initializer.
*
* @example
* // Instead of:
* let searchParams = new URLSearchParams([
* ["sort", "name"],
* ["sort", "price"],
* ]);
*
* // You can do:
* let searchParams = createSearchParams({
* sort: ["name", "price"],
* });
*
* @public
* @category Utils
* @param init The value used to initialize the URL search parameters.
* @returns A URLSearchParams object containing the initialized search
* parameters.
*/
declare function createSearchParams(init?: URLSearchParamsInit): URLSearchParams;
type JsonObject = { [Key in string]: JsonValue } & { [Key in string]?: JsonValue | undefined };
type JsonArray = JsonValue[] | readonly JsonValue[];
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
type SubmitTarget = HTMLFormElement | HTMLButtonElement | HTMLInputElement | FormData | URLSearchParams | JsonValue | null;
/**
* Submit options shared by both navigations and fetchers
*/
interface SharedSubmitOptions {
/**
* The HTTP method used to submit the form. Overrides `<form method>`.
* Defaults to "GET".
*/
method?: HTMLFormMethod;
/**
* The action URL path used to submit the form. Overrides `<form action>`.
* Defaults to the path of the current route.
*/
action?: string;
/**
* The encoding used to submit the form. Overrides `<form encType>`.
* Defaults to "application/x-www-form-urlencoded".
*/
encType?: FormEncType;
/**
* Determines whether the form action is relative to the route hierarchy or
* the pathname. Use this if you want to opt out of navigating the route
* hierarchy and want to instead route based on /-delimited URL segments
*/
relative?: RelativeRoutingType;
/**
* In browser-based environments, prevent resetting scroll after this
* navigation when using the <ScrollRestoration> component
*/
preventScrollReset?: boolean;
/**
* Enable flushSync for this submission's state updates
*/
flushSync?: boolean;
/**
* Specify the default revalidation behavior after this submission
*
* If no `shouldRevalidate` functions are present on the active routes, then this
* value will be used directly. Otherwise it will be passed into `shouldRevalidate`
* so the route can make the final determination on revalidation. This can be
* useful when updating search params and you don't want to trigger a revalidation.
*
* By default (when not specified), loaders will revalidate according to the routers
* standard revalidation behavior.
*/
defaultShouldRevalidate?: boolean;
}
/**
* Submit options available to fetchers
*/
interface FetcherSubmitOptions extends SharedSubmitOptions {}
/**
* Submit options available to navigations
*/
interface SubmitOptions extends FetcherSubmitOptions {
/**
* Set `true` to replace the current entry in the browser's history stack
* instead of creating a new one (i.e. stay on "the same page"). Defaults
* to `false`.
*/
replace?: boolean;
/**
* State object to add to the history stack entry for this navigation
*/
state?: any;
/**
* Indicate a specific fetcherKey to use when using navigate=false
*/
fetcherKey?: string;
/**
* navigate=false will use a fetcher instead of a navigation
*/
navigate?: boolean;
/**
* Enable view transitions on this submission navigation
*/
viewTransition?: boolean;
}
//#endregion
export { FetcherSubmitOptions, ParamKeyValuePair, SubmitOptions, SubmitTarget, URLSearchParamsInit, createSearchParams };
@@ -0,0 +1,143 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { warning } from "../router/history.js";
import { stripBasename } from "../router/utils.js";
const defaultEncType = "application/x-www-form-urlencoded";
function isHtmlElement(object) {
return typeof HTMLElement !== "undefined" && object instanceof HTMLElement;
}
function isButtonElement(object) {
return isHtmlElement(object) && object.tagName.toLowerCase() === "button";
}
function isFormElement(object) {
return isHtmlElement(object) && object.tagName.toLowerCase() === "form";
}
function isInputElement(object) {
return isHtmlElement(object) && object.tagName.toLowerCase() === "input";
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
function shouldProcessLinkClick(event, target) {
return event.button === 0 && (!target || target === "_self") && !isModifiedEvent(event);
}
/**
* Creates a URLSearchParams object using the given initializer.
*
* This is identical to `new URLSearchParams(init)` except it also supports
* arrays as values in the object form of the initializer instead of just
* strings. This is convenient when you need multiple values for a given key,
* but don't want to use an array initializer.
*
* @example
* // Instead of:
* let searchParams = new URLSearchParams([
* ["sort", "name"],
* ["sort", "price"],
* ]);
*
* // You can do:
* let searchParams = createSearchParams({
* sort: ["name", "price"],
* });
*
* @public
* @category Utils
* @param init The value used to initialize the URL search parameters.
* @returns A URLSearchParams object containing the initialized search
* parameters.
*/
function createSearchParams(init = "") {
return new URLSearchParams(typeof init === "string" || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).reduce((memo, key) => {
let value = init[key];
return memo.concat(Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]);
}, []));
}
function getSearchParamsForLocation(locationSearch, defaultSearchParams) {
let searchParams = createSearchParams(locationSearch);
if (defaultSearchParams) defaultSearchParams.forEach((_, key) => {
if (!searchParams.has(key)) defaultSearchParams.getAll(key).forEach((value) => {
searchParams.append(key, value);
});
});
return searchParams;
}
let _formDataSupportsSubmitter = null;
function isFormDataSubmitterSupported() {
if (_formDataSupportsSubmitter === null) try {
new FormData(document.createElement("form"), 0);
_formDataSupportsSubmitter = false;
} catch (e) {
_formDataSupportsSubmitter = true;
}
return _formDataSupportsSubmitter;
}
const supportedFormEncTypes = new Set([
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain"
]);
function getFormEncType(encType) {
if (encType != null && !supportedFormEncTypes.has(encType)) {
warning(false, `"${encType}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` and will default to "${defaultEncType}"`);
return null;
}
return encType;
}
function getFormSubmissionInfo(target, basename) {
let method;
let action;
let encType;
let formData;
let body;
if (isFormElement(target)) {
let attr = target.getAttribute("action");
action = attr ? stripBasename(attr, basename) : null;
method = target.getAttribute("method") || "get";
encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
formData = new FormData(target);
} else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
let form = target.form;
if (form == null) throw new Error(`Cannot submit a <button> or <input type="submit"> without a <form>`);
let attr = target.getAttribute("formaction") || form.getAttribute("action");
action = attr ? stripBasename(attr, basename) : null;
method = target.getAttribute("formmethod") || form.getAttribute("method") || "get";
encType = getFormEncType(target.getAttribute("formenctype")) || getFormEncType(form.getAttribute("enctype")) || defaultEncType;
formData = new FormData(form, target);
if (!isFormDataSubmitterSupported()) {
let { name, type, value } = target;
if (type === "image") {
let prefix = name ? `${name}.` : "";
formData.append(`${prefix}x`, "0");
formData.append(`${prefix}y`, "0");
} else if (name) formData.append(name, value);
}
} else if (isHtmlElement(target)) throw new Error("Cannot submit element that is not <form>, <button>, or <input type=\"submit|image\">");
else {
method = "get";
action = null;
encType = defaultEncType;
body = target;
}
if (formData && encType === "text/plain") {
body = formData;
formData = void 0;
}
return {
action,
method: method.toLowerCase(),
encType,
formData,
body
};
}
//#endregion
export { createSearchParams, getFormSubmissionInfo, getSearchParamsForLocation, shouldProcessLinkClick };
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,138 @@
import { Location } from "../router/history.js";
import { DataRouteObject, RouteBranch, RouteObject } from "../router/utils.js";
import { FutureConfig, Router, StaticHandlerContext } from "../router/router.js";
import * as React$1 from "react";
//#region lib/dom/server.d.ts
/**
* @category Types
*/
interface StaticRouterProps {
/**
* The base URL for the static router (default: `/`)
*/
basename?: string;
/**
* The child elements to render inside the static router
*/
children?: React$1.ReactNode;
/**
* The {@link Location} to render the static router at (default: `/`)
*/
location: Partial<Location> | string;
}
/**
* A {@link Router | `<Router>`} that may not navigate to any other {@link Location}.
* This is useful on the server where there is no stateful UI.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {StaticRouterProps.basename} props.basename n/a
* @param {StaticRouterProps.children} props.children n/a
* @param {StaticRouterProps.location} props.location n/a
* @returns A React element that renders the static {@link Router | `<Router>`}
*/
declare function StaticRouter({
basename,
children,
location: locationProp
}: StaticRouterProps): React$1.JSX.Element;
/**
* @category Types
*/
interface StaticRouterProviderProps {
/**
* The {@link StaticHandlerContext} returned from {@link StaticHandler}'s
* `query`
*/
context: StaticHandlerContext;
/**
* The static {@link DataRouter} from {@link createStaticRouter}
*/
router: Router;
/**
* Whether to hydrate the router on the client (default `true`)
*/
hydrate?: boolean;
/**
* The [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* to use for the hydration [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tag
*/
nonce?: string;
}
/**
* A {@link DataRouter} that may not navigate to any other {@link Location}.
* This is useful on the server where there is no stateful UI.
*
* @example
* export async function handleRequest(request: Request) {
* let { query, dataRoutes } = createStaticHandler(routes);
* let context = await query(request));
*
* if (context instanceof Response) {
* return context;
* }
*
* let router = createStaticRouter(dataRoutes, context);
* return new Response(
* ReactDOMServer.renderToString(<StaticRouterProvider ... />),
* { headers: { "Content-Type": "text/html" } }
* );
* }
*
* @public
* @category Data Routers
* @mode data
* @param props Props
* @param {StaticRouterProviderProps.context} props.context n/a
* @param {StaticRouterProviderProps.hydrate} props.hydrate n/a
* @param {StaticRouterProviderProps.nonce} props.nonce n/a
* @param {StaticRouterProviderProps.router} props.router n/a
* @returns A React element that renders the static router provider
*/
declare function StaticRouterProvider({
context,
router,
hydrate,
nonce
}: StaticRouterProviderProps): React$1.JSX.Element;
/**
* Create a static {@link DataRouter} for server-side rendering
*
* @example
* export async function handleRequest(request: Request) {
* let { query, dataRoutes } = createStaticHandler(routes);
* let context = await query(request);
*
* if (context instanceof Response) {
* return context;
* }
*
* let router = createStaticRouter(dataRoutes, context);
* return new Response(
* ReactDOMServer.renderToString(<StaticRouterProvider ... />),
* { headers: { "Content-Type": "text/html" } }
* );
* }
*
* @public
* @category Data Routers
* @mode data
* @param routes The route objects to create a static {@link DataRouter} for
* @param context The {@link StaticHandlerContext} returned from {@link StaticHandler}'s
* `query`
* @param opts Options
* @param opts.future Future flags for the static {@link DataRouter}
* @param opts.branches Optional pre-computed route branches
* @returns A static {@link DataRouter} that can be used to render the provided routes
*/
declare function createStaticRouter(routes: RouteObject[], context: StaticHandlerContext, opts?: {
branches?: RouteBranch<DataRouteObject>[];
future?: Partial<FutureConfig>;
}): Router;
//#endregion
export { StaticRouter, StaticRouterProps, StaticRouterProvider, StaticRouterProviderProps, createStaticRouter };
@@ -0,0 +1,301 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ABSOLUTE_URL_REGEX } from "../router/url.js";
import { createPath, invariant, parsePath } from "../router/history.js";
import { convertRoutesToDataRoutes, isRouteErrorResponse } from "../router/utils.js";
import { IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION } from "../router/router.js";
import { DataRouterContext, DataRouterStateContext, FetchersContext, ViewTransitionContext } from "../context.js";
import { DataRoutes, Router } from "../components.js";
import { escapeHtml } from "./ssr/markup.js";
import * as React$1 from "react";
//#region lib/dom/server.tsx
/**
* A {@link Router | `<Router>`} that may not navigate to any other {@link Location}.
* This is useful on the server where there is no stateful UI.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {StaticRouterProps.basename} props.basename n/a
* @param {StaticRouterProps.children} props.children n/a
* @param {StaticRouterProps.location} props.location n/a
* @returns A React element that renders the static {@link Router | `<Router>`}
*/
function StaticRouter({ basename, children, location: locationProp = "/" }) {
if (typeof locationProp === "string") locationProp = parsePath(locationProp);
let action = "POP";
let location = {
pathname: locationProp.pathname || "/",
search: locationProp.search || "",
hash: locationProp.hash || "",
state: locationProp.state != null ? locationProp.state : null,
key: locationProp.key || "default",
mask: void 0
};
let staticNavigator = getStatelessNavigator();
return /* @__PURE__ */ React$1.createElement(Router, {
basename,
children,
location,
navigationType: action,
navigator: staticNavigator,
static: true,
useTransitions: false
});
}
/**
* A {@link DataRouter} that may not navigate to any other {@link Location}.
* This is useful on the server where there is no stateful UI.
*
* @example
* export async function handleRequest(request: Request) {
* let { query, dataRoutes } = createStaticHandler(routes);
* let context = await query(request));
*
* if (context instanceof Response) {
* return context;
* }
*
* let router = createStaticRouter(dataRoutes, context);
* return new Response(
* ReactDOMServer.renderToString(<StaticRouterProvider ... />),
* { headers: { "Content-Type": "text/html" } }
* );
* }
*
* @public
* @category Data Routers
* @mode data
* @param props Props
* @param {StaticRouterProviderProps.context} props.context n/a
* @param {StaticRouterProviderProps.hydrate} props.hydrate n/a
* @param {StaticRouterProviderProps.nonce} props.nonce n/a
* @param {StaticRouterProviderProps.router} props.router n/a
* @returns A React element that renders the static router provider
*/
function StaticRouterProvider({ context, router, hydrate = true, nonce }) {
invariant(router && context, "You must provide `router` and `context` to <StaticRouterProvider>");
let dataRouterContext = {
router,
navigator: getStatelessNavigator(),
static: true,
staticContext: context,
basename: context.basename || "/"
};
let fetchersContext = /* @__PURE__ */ new Map();
let hydrateScript = "";
if (hydrate !== false) {
let data = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: serializeErrors(context.errors)
};
hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${escapeHtml(JSON.stringify(JSON.stringify(data)))});`;
}
let { state } = dataRouterContext.router;
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React$1.createElement(DataRouterStateContext.Provider, { value: state }, /* @__PURE__ */ React$1.createElement(FetchersContext.Provider, { value: fetchersContext }, /* @__PURE__ */ React$1.createElement(ViewTransitionContext.Provider, { value: { isTransitioning: false } }, /* @__PURE__ */ React$1.createElement(Router, {
basename: dataRouterContext.basename,
location: state.location,
navigationType: state.historyAction,
navigator: dataRouterContext.navigator,
static: dataRouterContext.static,
useTransitions: false
}, /* @__PURE__ */ React$1.createElement(DataRoutes, {
manifest: router.manifest,
routes: router.routes,
future: router.future,
state,
isStatic: true
})))))), hydrateScript ? /* @__PURE__ */ React$1.createElement("script", {
suppressHydrationWarning: true,
nonce,
dangerouslySetInnerHTML: { __html: hydrateScript }
}) : null);
}
function serializeErrors(errors) {
if (!errors) return null;
let entries = Object.entries(errors);
let serialized = {};
for (let [key, val] of entries) if (isRouteErrorResponse(val)) serialized[key] = {
...val,
__type: "RouteErrorResponse"
};
else if (val instanceof Error) serialized[key] = {
message: val.message,
__type: "Error",
...val.name !== "Error" ? { __subType: val.name } : {}
};
else serialized[key] = val;
return serialized;
}
function getStatelessNavigator() {
return {
createHref,
encodeLocation,
push(to) {
throw new Error(`You cannot use navigator.push() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${JSON.stringify(to)})\` somewhere in your app.`);
},
replace(to) {
throw new Error(`You cannot use navigator.replace() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere in your app.`);
},
go(delta) {
throw new Error(`You cannot use navigator.go() on the server because it is a stateless environment. This error was probably triggered when you did a \`navigate(${delta})\` somewhere in your app.`);
},
back() {
throw new Error("You cannot use navigator.back() on the server because it is a stateless environment.");
},
forward() {
throw new Error("You cannot use navigator.forward() on the server because it is a stateless environment.");
}
};
}
/**
* Create a static {@link DataRouter} for server-side rendering
*
* @example
* export async function handleRequest(request: Request) {
* let { query, dataRoutes } = createStaticHandler(routes);
* let context = await query(request);
*
* if (context instanceof Response) {
* return context;
* }
*
* let router = createStaticRouter(dataRoutes, context);
* return new Response(
* ReactDOMServer.renderToString(<StaticRouterProvider ... />),
* { headers: { "Content-Type": "text/html" } }
* );
* }
*
* @public
* @category Data Routers
* @mode data
* @param routes The route objects to create a static {@link DataRouter} for
* @param context The {@link StaticHandlerContext} returned from {@link StaticHandler}'s
* `query`
* @param opts Options
* @param opts.future Future flags for the static {@link DataRouter}
* @param opts.branches Optional pre-computed route branches
* @returns A static {@link DataRouter} that can be used to render the provided routes
*/
function createStaticRouter(routes, context, opts = {}) {
let manifest = {};
let dataRoutes = convertRoutesToDataRoutes(routes, void 0, void 0, manifest);
let matches = context.matches.map((match) => {
let route = manifest[match.route.id] || match.route;
return {
...match,
route
};
});
let msg = (method) => `You cannot use router.${method}() on the server because it is a stateless environment`;
return {
get basename() {
return context.basename;
},
get future() {
return { ...opts?.future };
},
get state() {
return {
historyAction: "POP",
location: context.location,
matches,
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors,
initialized: true,
renderFallback: false,
navigation: IDLE_NAVIGATION,
restoreScrollPosition: null,
preventScrollReset: false,
revalidation: "idle",
fetchers: /* @__PURE__ */ new Map(),
blockers: /* @__PURE__ */ new Map()
};
},
get routes() {
return dataRoutes;
},
get branches() {
return opts.branches;
},
get manifest() {
return manifest;
},
get window() {},
initialize() {
throw msg("initialize");
},
subscribe() {
throw msg("subscribe");
},
enableScrollRestoration() {
throw msg("enableScrollRestoration");
},
navigate() {
throw msg("navigate");
},
fetch() {
throw msg("fetch");
},
revalidate() {
throw msg("revalidate");
},
createHref,
encodeLocation,
getFetcher() {
return IDLE_FETCHER;
},
deleteFetcher() {
throw msg("deleteFetcher");
},
resetFetcher() {
throw msg("resetFetcher");
},
dispose() {
throw msg("dispose");
},
getBlocker() {
return IDLE_BLOCKER;
},
deleteBlocker() {
throw msg("deleteBlocker");
},
patchRoutes() {
throw msg("patchRoutes");
},
_internalFetchControllers: /* @__PURE__ */ new Map(),
_internalSetRoutes() {
throw msg("_internalSetRoutes");
},
_internalSetStateDoNotUseOrYouWillBreakYourApp() {
throw msg("_internalSetStateDoNotUseOrYouWillBreakYourApp");
}
};
}
function createHref(to) {
return typeof to === "string" ? to : createPath(to);
}
function encodeLocation(to) {
let href = typeof to === "string" ? to : createPath(to);
href = href.replace(/ $/, "%20");
let encoded = ABSOLUTE_URL_REGEX.test(href) ? new URL(href) : new URL(href, "http://localhost");
return {
pathname: encoded.pathname,
search: encoded.search,
hash: encoded.hash
};
}
//#endregion
export { StaticRouter, StaticRouterProvider, createStaticRouter };
@@ -0,0 +1,198 @@
import { PageLinkDescriptor } from "../../router/links.js";
import { FrameworkContextObject } from "./entry.js";
import * as React$1 from "react";
//#region lib/dom/ssr/components.d.ts
declare const FrameworkContext: React$1.Context<FrameworkContextObject | undefined>;
/**
* Defines the [lazy route discovery](../../explanation/lazy-route-discovery)
* behavior of the link/form:
*
* - "render" - default, discover the route when the link renders
* - "none" - don't eagerly discover, only discover if the link is clicked
*/
type DiscoverBehavior = "render" | "none";
/**
* Defines the prefetching behavior of the link:
*
* - "none": Never fetched
* - "intent": Fetched when the user focuses or hovers the link
* - "render": Fetched when the link is rendered
* - "viewport": Fetched when the link is in the viewport
*/
type PrefetchBehavior = "intent" | "render" | "none" | "viewport";
/**
* Props for the {@link Links} component.
*
* @category Types
*/
interface LinksProps {
/**
* A [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* attribute to render on the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* element. If not provided in Framework Mode, it will default to any
* {@link ServerRouter | `<ServerRouter nonce>`} prop.
*/
nonce?: string | undefined;
/**
* A [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin)
* attribute to render on the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* element
*/
crossOrigin?: "anonymous" | "use-credentials";
}
/**
* Renders all the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags created by the route module's [`links`](../../start/framework/route-module#links)
* export. You should render it inside the [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
* of your document.
*
* @example
* import { Links } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head>
* <Links />
* </head>
* <body></body>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @param props Props
* @param {LinksProps.nonce} props.nonce n/a
* @param {LinksProps.crossOrigin} props.crossOrigin n/a
* @returns A collection of React elements for [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags
*/
declare function Links({
nonce,
crossOrigin
}: LinksProps): React$1.JSX.Element;
/**
* Renders [`<link rel=prefetch|modulepreload>`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/rel)
* tags for modules and data of another page to enable an instant navigation to
* that page. [`<Link prefetch>`](./Link#prefetch) uses this internally, but you
* can render it to prefetch a page for any other reason.
*
* For example, you may render one of this as the user types into a search field
* to prefetch search results before they click through to their selection.
*
* @example
* import { PrefetchPageLinks } from "react-router";
*
* <PrefetchPageLinks page="/absolute/path" />
*
* @public
* @category Components
* @mode framework
* @param props Props
* @param {PageLinkDescriptor.page} props.page n/a
* @param props.linkProps Additional props to spread onto the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags, such as [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/crossOrigin),
* [`integrity`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/integrity),
* [`rel`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/rel),
* etc.
* @returns A collection of React elements for [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags
*/
declare function PrefetchPageLinks({
page,
...linkProps
}: PageLinkDescriptor): React$1.JSX.Element | null;
/**
* Renders all the [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
* tags created by the route module's [`meta`](../../start/framework/route-module#meta)
* export. You should render it inside the [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
* of your document.
*
* @example
* import { Meta } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head>
* <Meta />
* </head>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @returns A collection of React elements for [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
* tags
*/
declare function Meta(): React$1.JSX.Element;
/**
* A couple common attributes:
*
* - `<Scripts crossOrigin>` for hosting your static assets on a different
* server than your app.
* - `<Scripts nonce>` to support a [content security policy for scripts](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src)
* with [nonce-sources](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources)
* for your [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tags.
*
* You cannot pass through attributes such as [`async`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/async),
* [`defer`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/defer),
* [`noModule`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/noModule),
* [`src`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/src),
* or [`type`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/type),
* because they are managed by React Router internally.
*
* @category Types
*/
type ScriptsProps = Omit<React$1.HTMLProps<HTMLScriptElement>, "async" | "children" | "dangerouslySetInnerHTML" | "defer" | "noModule" | "src" | "suppressHydrationWarning" | "type"> & {
/**
* A [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* attribute to render on the [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* element. If not provided in Framework Mode, it will default to any
* {@link ServerRouter | `<ServerRouter nonce>`} prop.
*/
nonce?: string | undefined;
};
/**
* Renders the client runtime of your app. It should be rendered inside the
* [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
* of the document.
*
* If server rendering, you can omit `<Scripts/>` and the app will work as a
* traditional web app without JavaScript, relying solely on HTML and browser
* behaviors.
*
* @example
* import { Scripts } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head />
* <body>
* <Scripts />
* </body>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @param scriptProps Additional props to spread onto the [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tags, such as [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/crossOrigin),
* [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce),
* etc.
* @returns A collection of React elements for [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tags
*/
declare function Scripts(scriptProps: ScriptsProps): React$1.JSX.Element | null;
//#endregion
export { DiscoverBehavior, FrameworkContext, Links, LinksProps, Meta, PrefetchBehavior, PrefetchPageLinks, Scripts, ScriptsProps };
@@ -0,0 +1,589 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { matchRoutes } from "../../router/utils.js";
import { DataRouterContext, DataRouterStateContext, useIsRSCRouterContext } from "../../context.js";
import { useLocation } from "../../hooks.js";
import { warnOnce } from "../../server-runtime/warnings.js";
import invariant from "./invariant.js";
import { getKeyedLinksForMatches, getKeyedPrefetchLinks, getModuleLinkHrefs, getNewMatchesForLinks, isPageLinkDescriptor } from "./links.js";
import { escapeHtml } from "./markup.js";
import { singleFetchUrl } from "./single-fetch.js";
import { getPartialManifest, isFogOfWarEnabled } from "./fog-of-war.js";
import * as React$1 from "react";
//#region lib/dom/ssr/components.tsx
function useDataRouterContext() {
let context = React$1.useContext(DataRouterContext);
invariant(context, "You must render this element inside a <DataRouterContext.Provider> element");
return context;
}
function useDataRouterStateContext() {
let context = React$1.useContext(DataRouterStateContext);
invariant(context, "You must render this element inside a <DataRouterStateContext.Provider> element");
return context;
}
const FrameworkContext = React$1.createContext(void 0);
FrameworkContext.displayName = "FrameworkContext";
function useFrameworkContext() {
let context = React$1.useContext(FrameworkContext);
invariant(context, "You must render this element inside a <HydratedRouter> element");
return context;
}
function usePrefetchBehavior(prefetch, theirElementProps) {
let frameworkContext = React$1.useContext(FrameworkContext);
let [maybePrefetch, setMaybePrefetch] = React$1.useState(false);
let [shouldPrefetch, setShouldPrefetch] = React$1.useState(false);
let { onFocus, onBlur, onMouseEnter, onMouseLeave, onTouchStart } = theirElementProps;
let ref = React$1.useRef(null);
React$1.useEffect(() => {
if (prefetch === "render") setShouldPrefetch(true);
if (prefetch === "viewport") {
let callback = (entries) => {
entries.forEach((entry) => {
setShouldPrefetch(entry.isIntersecting);
});
};
let observer = new IntersectionObserver(callback, { threshold: .5 });
if (ref.current) observer.observe(ref.current);
return () => {
observer.disconnect();
};
}
}, [prefetch]);
React$1.useEffect(() => {
if (maybePrefetch) {
let id = setTimeout(() => {
setShouldPrefetch(true);
}, 100);
return () => {
clearTimeout(id);
};
}
}, [maybePrefetch]);
let setIntent = () => {
setMaybePrefetch(true);
};
let cancelIntent = () => {
setMaybePrefetch(false);
setShouldPrefetch(false);
};
if (!frameworkContext) return [
false,
ref,
{}
];
if (prefetch !== "intent") return [
shouldPrefetch,
ref,
{}
];
return [
shouldPrefetch,
ref,
{
onFocus: composeEventHandlers(onFocus, setIntent),
onBlur: composeEventHandlers(onBlur, cancelIntent),
onMouseEnter: composeEventHandlers(onMouseEnter, setIntent),
onMouseLeave: composeEventHandlers(onMouseLeave, cancelIntent),
onTouchStart: composeEventHandlers(onTouchStart, setIntent)
}
];
}
function composeEventHandlers(theirHandler, ourHandler) {
return (event) => {
theirHandler && theirHandler(event);
if (!event.defaultPrevented) ourHandler(event);
};
}
function getActiveMatches(matches, errors, isSpaMode) {
if (isSpaMode && !isHydrated) return [matches[0]];
if (errors) {
let errorIdx = matches.findIndex((m) => errors[m.route.id] !== void 0);
return matches.slice(0, errorIdx + 1);
}
return matches;
}
const CRITICAL_CSS_DATA_ATTRIBUTE = "data-react-router-critical-css";
/**
* Renders all the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags created by the route module's [`links`](../../start/framework/route-module#links)
* export. You should render it inside the [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
* of your document.
*
* @example
* import { Links } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head>
* <Links />
* </head>
* <body></body>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @param props Props
* @param {LinksProps.nonce} props.nonce n/a
* @param {LinksProps.crossOrigin} props.crossOrigin n/a
* @returns A collection of React elements for [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags
*/
function Links({ nonce, crossOrigin }) {
let { isSpaMode, manifest, routeModules, criticalCss, nonce: contextNonce } = useFrameworkContext();
let { errors, matches: routerMatches } = useDataRouterStateContext();
let matches = getActiveMatches(routerMatches, errors, isSpaMode);
let keyedLinks = React$1.useMemo(() => getKeyedLinksForMatches(matches, routeModules, manifest), [
matches,
routeModules,
manifest
]);
if (nonce == null && contextNonce) nonce = contextNonce;
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, typeof criticalCss === "string" ? /* @__PURE__ */ React$1.createElement("style", {
[CRITICAL_CSS_DATA_ATTRIBUTE]: "",
nonce,
dangerouslySetInnerHTML: { __html: criticalCss }
}) : null, typeof criticalCss === "object" ? /* @__PURE__ */ React$1.createElement("link", {
[CRITICAL_CSS_DATA_ATTRIBUTE]: "",
rel: "stylesheet",
href: criticalCss.href,
nonce,
crossOrigin
}) : null, keyedLinks.map(({ key, link }) => isPageLinkDescriptor(link) ? /* @__PURE__ */ React$1.createElement(PrefetchPageLinks, {
key,
nonce,
...link,
crossOrigin: link.crossOrigin ?? crossOrigin
}) : /* @__PURE__ */ React$1.createElement("link", {
key,
nonce,
...link,
crossOrigin: link.crossOrigin ?? crossOrigin
})));
}
/**
* Renders [`<link rel=prefetch|modulepreload>`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/rel)
* tags for modules and data of another page to enable an instant navigation to
* that page. [`<Link prefetch>`](./Link#prefetch) uses this internally, but you
* can render it to prefetch a page for any other reason.
*
* For example, you may render one of this as the user types into a search field
* to prefetch search results before they click through to their selection.
*
* @example
* import { PrefetchPageLinks } from "react-router";
*
* <PrefetchPageLinks page="/absolute/path" />
*
* @public
* @category Components
* @mode framework
* @param props Props
* @param {PageLinkDescriptor.page} props.page n/a
* @param props.linkProps Additional props to spread onto the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags, such as [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/crossOrigin),
* [`integrity`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/integrity),
* [`rel`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/rel),
* etc.
* @returns A collection of React elements for [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* tags
*/
function PrefetchPageLinks({ page, ...linkProps }) {
let rsc = useIsRSCRouterContext();
let { nonce: contextNonce } = useFrameworkContext();
let { router } = useDataRouterContext();
let matches = React$1.useMemo(() => matchRoutes(router.routes, page, router.basename), [
router.routes,
page,
router.basename
]);
if (!matches) return null;
if (linkProps.nonce == null && contextNonce) linkProps = {
...linkProps,
nonce: contextNonce
};
if (rsc) return /* @__PURE__ */ React$1.createElement(RSCPrefetchPageLinksImpl, {
page,
matches,
...linkProps
});
return /* @__PURE__ */ React$1.createElement(PrefetchPageLinksImpl, {
page,
matches,
...linkProps
});
}
function useKeyedPrefetchLinks(matches) {
let { manifest, routeModules } = useFrameworkContext();
let [keyedPrefetchLinks, setKeyedPrefetchLinks] = React$1.useState([]);
React$1.useEffect(() => {
let interrupted = false;
getKeyedPrefetchLinks(matches, manifest, routeModules).then((links) => {
if (!interrupted) setKeyedPrefetchLinks(links);
});
return () => {
interrupted = true;
};
}, [
matches,
manifest,
routeModules
]);
return keyedPrefetchLinks;
}
function RSCPrefetchPageLinksImpl({ page, matches: nextMatches, ...linkProps }) {
let location = useLocation();
let dataHrefs = React$1.useMemo(() => {
if (page === location.pathname + location.search + location.hash) return [];
let url = singleFetchUrl(page, "rsc");
let hasSomeRoutesWithShouldRevalidate = false;
let targetRoutes = [];
for (let match of nextMatches) if (typeof match.route.shouldRevalidate === "function") hasSomeRoutesWithShouldRevalidate = true;
else targetRoutes.push(match.route.id);
if (hasSomeRoutesWithShouldRevalidate && targetRoutes.length > 0) url.searchParams.set("_routes", targetRoutes.join(","));
return [url.pathname + url.search];
}, [
page,
location,
nextMatches
]);
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, dataHrefs.map((href) => /* @__PURE__ */ React$1.createElement("link", {
key: href,
rel: "prefetch",
as: "fetch",
href,
...linkProps
})));
}
function PrefetchPageLinksImpl({ page, matches: nextMatches, ...linkProps }) {
let location = useLocation();
let { manifest, routeModules } = useFrameworkContext();
let { loaderData, matches } = useDataRouterStateContext();
let newMatchesForData = React$1.useMemo(() => getNewMatchesForLinks(page, nextMatches, matches, manifest, location, "data"), [
page,
nextMatches,
matches,
manifest,
location
]);
let newMatchesForAssets = React$1.useMemo(() => getNewMatchesForLinks(page, nextMatches, matches, manifest, location, "assets"), [
page,
nextMatches,
matches,
manifest,
location
]);
let dataHrefs = React$1.useMemo(() => {
if (page === location.pathname + location.search + location.hash) return [];
let routesParams = /* @__PURE__ */ new Set();
let foundOptOutRoute = false;
nextMatches.forEach((m) => {
let manifestRoute = manifest.routes[m.route.id];
if (!manifestRoute || !manifestRoute.hasLoader) return;
if (!newMatchesForData.some((m2) => m2.route.id === m.route.id) && m.route.id in loaderData && routeModules[m.route.id]?.shouldRevalidate) foundOptOutRoute = true;
else if (manifestRoute.hasClientLoader) foundOptOutRoute = true;
else routesParams.add(m.route.id);
});
if (routesParams.size === 0) return [];
let url = singleFetchUrl(page, "data");
if (foundOptOutRoute && routesParams.size > 0) url.searchParams.set("_routes", nextMatches.filter((m) => routesParams.has(m.route.id)).map((m) => m.route.id).join(","));
return [url.pathname + url.search];
}, [
loaderData,
location,
manifest,
newMatchesForData,
nextMatches,
page,
routeModules
]);
let moduleHrefs = React$1.useMemo(() => getModuleLinkHrefs(newMatchesForAssets, manifest), [newMatchesForAssets, manifest]);
let keyedPrefetchLinks = useKeyedPrefetchLinks(newMatchesForAssets);
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, dataHrefs.map((href) => /* @__PURE__ */ React$1.createElement("link", {
key: href,
rel: "prefetch",
as: "fetch",
href,
...linkProps
})), moduleHrefs.map((href) => /* @__PURE__ */ React$1.createElement("link", {
key: href,
rel: "modulepreload",
href,
...linkProps
})), keyedPrefetchLinks.map(({ key, link }) => /* @__PURE__ */ React$1.createElement("link", {
key,
nonce: linkProps.nonce,
...link,
crossOrigin: link.crossOrigin ?? linkProps.crossOrigin
})));
}
/**
* Renders all the [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
* tags created by the route module's [`meta`](../../start/framework/route-module#meta)
* export. You should render it inside the [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
* of your document.
*
* @example
* import { Meta } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head>
* <Meta />
* </head>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @returns A collection of React elements for [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
* tags
*/
function Meta() {
let { isSpaMode, routeModules } = useFrameworkContext();
let { errors, matches: routerMatches, loaderData } = useDataRouterStateContext();
let location = useLocation();
let _matches = getActiveMatches(routerMatches, errors, isSpaMode);
let error = null;
if (errors) error = errors[_matches[_matches.length - 1].route.id];
let meta = [];
let leafMeta = null;
let matches = [];
for (let i = 0; i < _matches.length; i++) {
let _match = _matches[i];
let routeId = _match.route.id;
let data = loaderData[routeId];
let params = _match.params;
let routeModule = routeModules[routeId];
let routeMeta = [];
let match = {
id: routeId,
loaderData: data,
meta: [],
params: _match.params,
pathname: _match.pathname,
handle: _match.route.handle,
error
};
matches[i] = match;
if (routeModule?.meta) routeMeta = typeof routeModule.meta === "function" ? routeModule.meta({
loaderData: data,
params,
location,
matches,
error
}) : Array.isArray(routeModule.meta) ? [...routeModule.meta] : routeModule.meta;
else if (leafMeta) routeMeta = [...leafMeta];
routeMeta = routeMeta || [];
if (!Array.isArray(routeMeta)) throw new Error("The route at " + _match.route.path + " returns an invalid value. All route meta functions must return an array of meta objects.\n\nTo reference the meta function API, see https://reactrouter.com/start/framework/route-module#meta");
match.meta = routeMeta;
matches[i] = match;
meta = [...routeMeta];
leafMeta = meta;
}
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, meta.flat().map((metaProps) => {
if (!metaProps) return null;
if ("tagName" in metaProps) {
let { tagName, ...rest } = metaProps;
if (!isValidMetaTag(tagName)) {
console.warn(`A meta object uses an invalid tagName: ${tagName}. Expected either 'link' or 'meta'`);
return null;
}
let Comp = tagName;
return /* @__PURE__ */ React$1.createElement(Comp, {
key: JSON.stringify(rest),
...rest
});
}
if ("title" in metaProps) return /* @__PURE__ */ React$1.createElement("title", { key: "title" }, String(metaProps.title));
if ("charset" in metaProps) {
metaProps.charSet ??= metaProps.charset;
delete metaProps.charset;
}
if ("charSet" in metaProps && metaProps.charSet != null) return typeof metaProps.charSet === "string" ? /* @__PURE__ */ React$1.createElement("meta", {
key: "charSet",
charSet: metaProps.charSet
}) : null;
if ("script:ld+json" in metaProps) try {
let json = JSON.stringify(metaProps["script:ld+json"]);
return /* @__PURE__ */ React$1.createElement("script", {
key: `script:ld+json:${json}`,
type: "application/ld+json",
dangerouslySetInnerHTML: { __html: escapeHtml(json) }
});
} catch (e) {
return null;
}
return /* @__PURE__ */ React$1.createElement("meta", {
key: JSON.stringify(metaProps),
...metaProps
});
}));
}
function isValidMetaTag(tagName) {
return typeof tagName === "string" && /^(meta|link)$/.test(tagName);
}
/**
* Tracks whether hydration is finished, so scripts can be skipped
* during client-side updates.
*/
let isHydrated = false;
function setIsHydrated() {
isHydrated = true;
}
/**
* Renders the client runtime of your app. It should be rendered inside the
* [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
* of the document.
*
* If server rendering, you can omit `<Scripts/>` and the app will work as a
* traditional web app without JavaScript, relying solely on HTML and browser
* behaviors.
*
* @example
* import { Scripts } from "react-router";
*
* export default function Root() {
* return (
* <html>
* <head />
* <body>
* <Scripts />
* </body>
* </html>
* );
* }
*
* @public
* @category Components
* @mode framework
* @param scriptProps Additional props to spread onto the [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tags, such as [`crossOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/crossOrigin),
* [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce),
* etc.
* @returns A collection of React elements for [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
* tags
*/
function Scripts(scriptProps) {
let { manifest, serverHandoffString, isSpaMode, renderMeta, routeDiscovery, ssr, nonce: contextNonce } = useFrameworkContext();
let { router, static: isStatic, staticContext } = useDataRouterContext();
let { matches: routerMatches } = useDataRouterStateContext();
let isRSCRouterContext = useIsRSCRouterContext();
let enableFogOfWar = isFogOfWarEnabled(routeDiscovery, ssr);
if (scriptProps.nonce == null && contextNonce) scriptProps = {
...scriptProps,
nonce: contextNonce
};
if (renderMeta) renderMeta.didRenderScripts = true;
let matches = getActiveMatches(routerMatches, null, isSpaMode);
React$1.useEffect(() => {
setIsHydrated();
}, []);
let initialScripts = React$1.useMemo(() => {
if (isRSCRouterContext) return null;
let contextScript = staticContext ? `window.__reactRouterContext = ${serverHandoffString};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());` : " ";
let routeModulesScript = !isStatic ? " " : `${manifest.hmr?.runtime ? `import ${JSON.stringify(manifest.hmr.runtime)};` : ""}${!enableFogOfWar ? `import ${JSON.stringify(manifest.url)}` : ""};
${matches.map((match, routeIndex) => {
let routeVarName = `route${routeIndex}`;
let manifestEntry = manifest.routes[match.route.id];
invariant(manifestEntry, `Route ${match.route.id} not found in manifest`);
let { clientActionModule, clientLoaderModule, clientMiddlewareModule, hydrateFallbackModule, module } = manifestEntry;
let chunks = [
...clientActionModule ? [{
module: clientActionModule,
varName: `${routeVarName}_clientAction`
}] : [],
...clientLoaderModule ? [{
module: clientLoaderModule,
varName: `${routeVarName}_clientLoader`
}] : [],
...clientMiddlewareModule ? [{
module: clientMiddlewareModule,
varName: `${routeVarName}_clientMiddleware`
}] : [],
...hydrateFallbackModule ? [{
module: hydrateFallbackModule,
varName: `${routeVarName}_HydrateFallback`
}] : [],
{
module,
varName: `${routeVarName}_main`
}
];
if (chunks.length === 1) return `import * as ${routeVarName} from ${JSON.stringify(module)};`;
return [chunks.map((chunk) => `import * as ${chunk.varName} from "${chunk.module}";`).join("\n"), `const ${routeVarName} = {${chunks.map((chunk) => `...${chunk.varName}`).join(",")}};`].join("\n");
}).join("\n")}
${enableFogOfWar ? `window.__reactRouterManifest = ${JSON.stringify(getPartialManifest(manifest, router), null, 2)};` : ""}
window.__reactRouterRouteModules = {${matches.map((match, index) => `${JSON.stringify(match.route.id)}:route${index}`).join(",")}};
import(${JSON.stringify(manifest.entry.module)});`;
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement("script", {
...scriptProps,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: contextScript },
type: void 0
}), /* @__PURE__ */ React$1.createElement("script", {
...scriptProps,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: routeModulesScript },
type: "module",
async: true
}));
}, []);
let preloads = isHydrated || isRSCRouterContext ? [] : [...new Set(manifest.entry.imports.concat(getModuleLinkHrefs(matches, manifest, { includeHydrateFallback: true })))];
let sri = typeof manifest.sri === "object" ? manifest.sri : {};
warnOnce(!isRSCRouterContext, "The <Scripts /> element is a no-op when using RSC and can be safely removed.");
return isHydrated || isRSCRouterContext ? null : /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, typeof manifest.sri === "object" ? /* @__PURE__ */ React$1.createElement("script", {
...scriptProps,
"rr-importmap": "",
type: "importmap",
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: JSON.stringify({ integrity: sri }) }
}) : null, !enableFogOfWar ? /* @__PURE__ */ React$1.createElement("link", {
rel: "modulepreload",
href: manifest.url,
crossOrigin: scriptProps.crossOrigin,
integrity: sri[manifest.url],
nonce: scriptProps.nonce,
suppressHydrationWarning: true
}) : null, /* @__PURE__ */ React$1.createElement("link", {
rel: "modulepreload",
href: manifest.entry.module,
crossOrigin: scriptProps.crossOrigin,
integrity: sri[manifest.entry.module],
nonce: scriptProps.nonce,
suppressHydrationWarning: true
}), preloads.map((path) => /* @__PURE__ */ React$1.createElement("link", {
key: path,
rel: "modulepreload",
href: path,
crossOrigin: scriptProps.crossOrigin,
integrity: sri[path],
nonce: scriptProps.nonce,
suppressHydrationWarning: true
})), initialScripts);
}
function mergeRefs(...refs) {
return (value) => {
refs.forEach((ref) => {
if (typeof ref === "function") ref(value);
else if (ref != null) ref.current = value;
});
};
}
//#endregion
export { CRITICAL_CSS_DATA_ATTRIBUTE, FrameworkContext, Links, Meta, PrefetchPageLinks, Scripts, mergeRefs, setIsHydrated, useFrameworkContext, usePrefetchBehavior };
@@ -0,0 +1,29 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/dom/ssr/data.ts
async function createRequestInit(request) {
let init = { signal: request.signal };
if (request.method !== "GET") {
init.method = request.method;
let contentType = request.headers.get("Content-Type");
if (contentType && /\bapplication\/json\b/.test(contentType)) {
init.headers = { "Content-Type": contentType };
init.body = JSON.stringify(await request.json());
} else if (contentType && /\btext\/plain\b/.test(contentType)) {
init.headers = { "Content-Type": contentType };
init.body = await request.text();
} else if (contentType && /\bapplication\/x-www-form-urlencoded\b/.test(contentType)) init.body = new URLSearchParams(await request.text());
else init.body = await request.formData();
}
return init;
}
//#endregion
export { createRequestInit };
@@ -0,0 +1,60 @@
import { DataRouteObject, RouteBranch, RouteManifest } from "../../router/utils.js";
import { StaticHandlerContext } from "../../router/router.js";
import { RouteModules } from "./routeModules.js";
import { EntryRoute } from "./routes.js";
import { ServerBuild } from "../../server-runtime/build.js";
//#region lib/dom/ssr/entry.d.ts
type SerializedError = {
message: string;
stack?: string;
};
interface FrameworkContextObject {
manifest: AssetsManifest;
routeModules: RouteModules;
criticalCss?: CriticalCss;
serverHandoffString?: string;
future: FutureConfig;
ssr: boolean;
isSpaMode: boolean;
routeDiscovery: ServerBuild["routeDiscovery"];
nonce?: string;
serializeError?(error: Error): SerializedError;
renderMeta?: {
didRenderScripts?: boolean;
streamCache?: Record<number, Promise<void> & {
result?: {
done: boolean;
value: string;
};
error?: unknown;
}>;
};
}
interface EntryContext extends FrameworkContextObject {
branches: RouteBranch<DataRouteObject>[];
staticHandlerContext: StaticHandlerContext;
serverHandoffStream?: ReadableStream<Uint8Array>;
}
type FutureConfig = Record<string, never>;
type CriticalCss = string | {
rel: "stylesheet";
href: string;
};
interface AssetsManifest {
entry: {
imports: string[];
module: string;
};
routes: RouteManifest<EntryRoute>;
url: string;
version: string;
hmr?: {
timestamp?: number;
runtime: string;
};
sri?: Record<string, string> | true;
}
//#endregion
export { AssetsManifest, CriticalCss, EntryContext, FrameworkContextObject, FutureConfig };
@@ -0,0 +1,27 @@
import { Location } from "../../router/history.js";
import * as React$1 from "react";
//#region lib/dom/ssr/errorBoundaries.d.ts
type RemixErrorBoundaryProps = React$1.PropsWithChildren<{
location: Location;
isOutsideRemixApp?: boolean;
error?: Error;
}>;
type RemixErrorBoundaryState = {
error: null | Error;
location: Location;
};
declare class RemixErrorBoundary extends React$1.Component<RemixErrorBoundaryProps, RemixErrorBoundaryState> {
constructor(props: RemixErrorBoundaryProps);
static getDerivedStateFromError(error: Error): {
error: Error;
};
static getDerivedStateFromProps(props: RemixErrorBoundaryProps, state: RemixErrorBoundaryState): {
error: Error | null;
location: Location<any>;
};
render(): string | number | bigint | boolean | Iterable<React$1.ReactNode> | Promise<string | number | bigint | boolean | React$1.ReactPortal | React$1.ReactElement<unknown, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined> | React$1.JSX.Element | null | undefined;
}
//#endregion
export { RemixErrorBoundary };
@@ -0,0 +1,87 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { isRouteErrorResponse } from "../../router/utils.js";
import { Scripts, useFrameworkContext } from "./components.js";
import * as React$1 from "react";
//#region lib/dom/ssr/errorBoundaries.tsx
var RemixErrorBoundary = class extends React$1.Component {
constructor(props) {
super(props);
this.state = {
error: props.error || null,
location: props.location
};
}
static getDerivedStateFromError(error) {
return { error };
}
static getDerivedStateFromProps(props, state) {
if (state.location !== props.location) return {
error: props.error || null,
location: props.location
};
return {
error: props.error || state.error,
location: state.location
};
}
render() {
if (this.state.error) return /* @__PURE__ */ React$1.createElement(RemixRootDefaultErrorBoundary, {
error: this.state.error,
isOutsideRemixApp: true
});
else return this.props.children;
}
};
/**
* When app's don't provide a root level ErrorBoundary, we default to this.
*/
function RemixRootDefaultErrorBoundary({ error, isOutsideRemixApp }) {
let { nonce } = useFrameworkContext();
console.error(error);
let heyDeveloper = /* @__PURE__ */ React$1.createElement("script", {
nonce,
dangerouslySetInnerHTML: { __html: `
console.log(
"💿 Hey developer 👋. You can provide a way better UX than this when your app throws errors. Check out https://reactrouter.com/how-to/error-boundary for more information."
);
` }
});
if (isRouteErrorResponse(error)) return /* @__PURE__ */ React$1.createElement(BoundaryShell, { title: "Unhandled Thrown Response!" }, /* @__PURE__ */ React$1.createElement("h1", { style: { fontSize: "24px" } }, error.status, " ", error.statusText), heyDeveloper);
let errorInstance;
if (error instanceof Error) errorInstance = error;
else {
let errorString = error == null ? "Unknown Error" : typeof error === "object" && "toString" in error ? error.toString() : JSON.stringify(error);
errorInstance = new Error(errorString);
}
return /* @__PURE__ */ React$1.createElement(BoundaryShell, {
title: "Application Error!",
isOutsideRemixApp
}, /* @__PURE__ */ React$1.createElement("h1", { style: { fontSize: "24px" } }, "Application Error"), /* @__PURE__ */ React$1.createElement("pre", { style: {
padding: "2rem",
background: "hsla(10, 50%, 50%, 0.1)",
color: "red",
overflow: "auto"
} }, errorInstance.stack), heyDeveloper);
}
function BoundaryShell({ title, renderScripts, isOutsideRemixApp, children }) {
let { routeModules } = useFrameworkContext();
if (routeModules.root?.Layout && !isOutsideRemixApp) return children;
return /* @__PURE__ */ React$1.createElement("html", { lang: "en" }, /* @__PURE__ */ React$1.createElement("head", null, /* @__PURE__ */ React$1.createElement("meta", { charSet: "utf-8" }), /* @__PURE__ */ React$1.createElement("meta", {
name: "viewport",
content: "width=device-width,initial-scale=1,viewport-fit=cover"
}), /* @__PURE__ */ React$1.createElement("title", null, title)), /* @__PURE__ */ React$1.createElement("body", null, /* @__PURE__ */ React$1.createElement("main", { style: {
fontFamily: "system-ui, sans-serif",
padding: "2rem"
} }, children, renderScripts ? /* @__PURE__ */ React$1.createElement(Scripts, null) : null)));
}
//#endregion
export { BoundaryShell, RemixErrorBoundary, RemixRootDefaultErrorBoundary };
@@ -0,0 +1,33 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { BoundaryShell } from "./errorBoundaries.js";
import { useFrameworkContext } from "./components.js";
import * as React$1 from "react";
//#region lib/dom/ssr/fallback.tsx
function RemixRootDefaultHydrateFallback() {
let { nonce } = useFrameworkContext();
return /* @__PURE__ */ React$1.createElement(BoundaryShell, {
title: "Loading...",
renderScripts: true
}, /* @__PURE__ */ React$1.createElement("script", {
nonce,
dangerouslySetInnerHTML: { __html: `
console.log(
"💿 Hey developer 👋. You can provide a way better UX than this " +
"when your app is loading JS modules and/or running \`clientLoader\` " +
"functions. Check out https://reactrouter.com/start/framework/route-module#hydratefallback " +
"for more information."
);
` }
}));
}
//#endregion
export { RemixRootDefaultHydrateFallback };
@@ -0,0 +1,12 @@
import { PatchRoutesOnNavigationFunction } from "../../router/utils.js";
import { Router } from "../../router/router.js";
import { RouteModules } from "./routeModules.js";
import { AssetsManifest } from "./entry.js";
import { ServerBuild } from "../../server-runtime/build.js";
//#region lib/dom/ssr/fog-of-war.d.ts
declare function getPatchRoutesOnNavigationFunction(getRouter: () => Router, manifest: AssetsManifest, routeModules: RouteModules, ssr: boolean, routeDiscovery: ServerBuild["routeDiscovery"], isSpaMode: boolean, basename: string | undefined): PatchRoutesOnNavigationFunction | undefined;
declare function useFogOFWarDiscovery(router: Router, manifest: AssetsManifest, routeModules: RouteModules, ssr: boolean, routeDiscovery: ServerBuild["routeDiscovery"], isSpaMode: boolean): void;
//#endregion
export { getPatchRoutesOnNavigationFunction, useFogOFWarDiscovery };
@@ -0,0 +1,185 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createPath } from "../../router/history.js";
import { joinPaths, matchRoutesImpl } from "../../router/utils.js";
import { createClientRoutes } from "./routes.js";
import * as React$1 from "react";
//#region lib/dom/ssr/fog-of-war.ts
const nextPaths = /* @__PURE__ */ new Set();
const discoveredPathsMaxSize = 1e3;
const discoveredPaths = /* @__PURE__ */ new Set();
const URL_LIMIT = 7680;
function getPathsWithAncestors(paths) {
let result = /* @__PURE__ */ new Set();
paths.forEach((path) => {
if (!path.startsWith("/")) path = `/${path}`;
for (let i = 1; i < path.length; i++) if (path[i] === "/") result.add(path.slice(0, i));
result.add(path);
});
return Array.from(result);
}
function isFogOfWarEnabled(routeDiscovery, ssr) {
return routeDiscovery.mode === "lazy" && ssr === true;
}
function getPartialManifest({ sri, ...manifest }, router) {
let routeIds = new Set(router.state.matches.map((m) => m.route.id));
let segments = router.state.location.pathname.split("/").filter(Boolean);
let paths = ["/"];
segments.pop();
while (segments.length > 0) {
paths.push(`/${segments.join("/")}`);
segments.pop();
}
paths.forEach((path) => {
let matches = matchRoutesImpl(router.routes, path, router.basename || "/", false, router.branches);
if (matches) matches.forEach((m) => routeIds.add(m.route.id));
});
let initialRoutes = [...routeIds].reduce((acc, id) => Object.assign(acc, { [id]: manifest.routes[id] }), {});
return {
...manifest,
routes: initialRoutes,
sri: sri ? true : void 0
};
}
function getPatchRoutesOnNavigationFunction(getRouter, manifest, routeModules, ssr, routeDiscovery, isSpaMode, basename) {
if (!isFogOfWarEnabled(routeDiscovery, ssr)) return;
return async ({ path, patch, signal, fetcherKey }) => {
if (discoveredPaths.has(path)) return;
let { state } = getRouter();
await fetchAndApplyManifestPatches([path], fetcherKey ? window.location.href : createPath(state.navigation.location || state.location), manifest, routeModules, ssr, isSpaMode, basename, routeDiscovery.manifestPath, patch, signal);
};
}
function useFogOFWarDiscovery(router, manifest, routeModules, ssr, routeDiscovery, isSpaMode) {
React$1.useEffect(() => {
if (!isFogOfWarEnabled(routeDiscovery, ssr) || window.navigator?.connection?.saveData === true) return;
function registerElement(el) {
let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
if (!path) return;
let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
if (!discoveredPaths.has(pathname)) nextPaths.add(pathname);
}
async function fetchPatches() {
document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
if (discoveredPaths.has(path)) {
nextPaths.delete(path);
return false;
}
return true;
});
if (lazyPaths.length === 0) return;
try {
await fetchAndApplyManifestPatches(lazyPaths, null, manifest, routeModules, ssr, isSpaMode, router.basename, routeDiscovery.manifestPath, router.patchRoutes);
} catch (e) {
console.error("Failed to fetch manifest patches", e);
}
}
let debouncedFetchPatches = debounce(fetchPatches, 100);
fetchPatches();
let observer = new MutationObserver(() => debouncedFetchPatches());
observer.observe(document.documentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: [
"data-discover",
"href",
"action"
]
});
return () => observer.disconnect();
}, [
ssr,
isSpaMode,
manifest,
routeModules,
router,
routeDiscovery
]);
}
function getManifestPath(_manifestPath, basename) {
let manifestPath = _manifestPath || "/__manifest";
return basename == null ? manifestPath : joinPaths([basename, manifestPath]);
}
const MANIFEST_VERSION_STORAGE_KEY = "react-router-manifest-version";
async function handleClientVersionMismatch(needsReload, version, errorReloadPath) {
if (!needsReload) {
try {
sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
} catch {}
return false;
}
if (!errorReloadPath) {
console.warn("Detected a manifest version mismatch during eager route discovery. The next navigation/fetch to an undiscovered route will result in a new document navigation to sync up with the latest manifest.");
return true;
}
try {
if (sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === version) {
console.error("Unable to discover routes due to manifest version mismatch.");
return true;
}
sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, version);
} catch {}
window.location.href = errorReloadPath;
console.warn("Detected manifest version mismatch, reloading...");
await new Promise(() => {});
return true;
}
async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, routeModules, ssr, isSpaMode, basename, manifestPath, patchRoutes, signal) {
paths = getPathsWithAncestors(paths);
const searchParams = new URLSearchParams();
searchParams.set("paths", paths.sort().join(","));
searchParams.set("version", manifest.version);
let url = new URL(getManifestPath(manifestPath, basename), window.location.origin);
url.search = searchParams.toString();
if (url.toString().length > 7680) {
nextPaths.clear();
return;
}
let serverPatches;
try {
let res = await fetch(url, { signal });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
if (await handleClientVersionMismatch(res.status === 204 && res.headers.has("X-Remix-Reload-Document"), manifest.version, errorReloadPath)) return;
serverPatches = await res.json();
} catch (e) {
if (signal?.aborted) return;
throw e;
}
let knownRoutes = new Set(Object.keys(manifest.routes));
let patches = Object.values(serverPatches).reduce((acc, route) => {
if (route && !knownRoutes.has(route.id)) acc[route.id] = route;
return acc;
}, {});
Object.assign(manifest.routes, patches);
paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
let parentIds = /* @__PURE__ */ new Set();
Object.values(patches).forEach((patch) => {
if (patch && (!patch.parentId || !patches[patch.parentId])) parentIds.add(patch.parentId);
});
parentIds.forEach((parentId) => patchRoutes(parentId || null, createClientRoutes(patches, routeModules, null, ssr, isSpaMode, parentId)));
}
function addToFifoQueue(path, queue) {
if (queue.size >= discoveredPathsMaxSize) {
let first = queue.values().next().value;
if (first !== void 0) queue.delete(first);
}
queue.add(path);
}
function debounce(callback, wait) {
let timeoutId;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(...args), wait);
};
}
//#endregion
export { URL_LIMIT, getManifestPath, getPartialManifest, getPatchRoutesOnNavigationFunction, getPathsWithAncestors, handleClientVersionMismatch, isFogOfWarEnabled, useFogOFWarDiscovery };
@@ -0,0 +1,32 @@
import { Path } from "../../router/history.js";
import { DataRouteObject } from "../../router/utils.js";
import { HydrationState, Router } from "../../router/router.js";
import { ClientLoaderFunction } from "./routeModules.js";
//#region lib/dom/ssr/hydration.d.ts
declare function getHydrationData({
state,
routes,
getRouteInfo,
location,
basename,
isSpaMode
}: {
state: {
loaderData?: Router["state"]["loaderData"];
actionData?: Router["state"]["actionData"];
errors?: Router["state"]["errors"];
};
routes: DataRouteObject[];
getRouteInfo: (routeId: string) => {
clientLoader: ClientLoaderFunction | undefined;
hasLoader: boolean;
hasHydrateFallback: boolean;
};
location: Path;
basename: string | undefined;
isSpaMode: boolean;
}): HydrationState;
//#endregion
export { getHydrationData };
@@ -0,0 +1,29 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { matchRoutes } from "../../router/utils.js";
import { shouldHydrateRouteLoader } from "./routes.js";
//#region lib/dom/ssr/hydration.tsx
function getHydrationData({ state, routes, getRouteInfo, location, basename, isSpaMode }) {
let hydrationData = {
...state,
loaderData: { ...state.loaderData }
};
let initialMatches = matchRoutes(routes, location, basename);
if (initialMatches) for (let match of initialMatches) {
let routeId = match.route.id;
let routeInfo = getRouteInfo(routeId);
if (shouldHydrateRouteLoader(routeId, routeInfo.clientLoader, routeInfo.hasLoader, isSpaMode) && (routeInfo.hasHydrateFallback || !routeInfo.hasLoader)) delete hydrationData.loaderData[routeId];
else if (!routeInfo.hasLoader) hydrationData.loaderData[routeId] = null;
}
return hydrationData;
}
//#endregion
export { getHydrationData };
@@ -0,0 +1,16 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/dom/ssr/invariant.ts
function invariant(value, message) {
if (value === false || value === null || typeof value === "undefined") throw new Error(message);
}
//#endregion
export { invariant as default };
@@ -0,0 +1,170 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { loadRouteModule } from "./routeModules.js";
//#region lib/dom/ssr/links.ts
/**
* Gets all the links for a set of matches. The modules are assumed to have been
* loaded already.
*/
function getKeyedLinksForMatches(matches, routeModules, manifest) {
return dedupeLinkDescriptors(matches.map((match) => {
let module = routeModules[match.route.id];
let route = manifest.routes[match.route.id];
return [route && route.css ? route.css.map((href) => ({
rel: "stylesheet",
href
})) : [], module?.links?.() || []];
}).flat(2), getModuleLinkHrefs(matches, manifest));
}
function getRouteCssDescriptors(route) {
if (!route.css) return [];
return route.css.map((href) => ({
rel: "stylesheet",
href
}));
}
async function prefetchRouteCss(route) {
if (!route.css) return;
let descriptors = getRouteCssDescriptors(route);
await Promise.all(descriptors.map(prefetchStyleLink));
}
async function prefetchStyleLinks(route, routeModule) {
if (!route.css && !routeModule.links || !isPreloadSupported()) return;
let descriptors = [];
if (route.css) descriptors.push(...getRouteCssDescriptors(route));
if (routeModule.links) descriptors.push(...routeModule.links());
if (descriptors.length === 0) return;
let styleLinks = [];
for (let descriptor of descriptors) if (!isPageLinkDescriptor(descriptor) && descriptor.rel === "stylesheet") styleLinks.push({
...descriptor,
rel: "preload",
as: "style"
});
await Promise.all(styleLinks.map(prefetchStyleLink));
}
async function prefetchStyleLink(descriptor) {
return new Promise((resolve) => {
if (descriptor.media && !window.matchMedia(descriptor.media).matches || document.querySelector(`link[rel="stylesheet"][href="${descriptor.href}"]`)) return resolve();
let link = document.createElement("link");
Object.assign(link, descriptor);
function removeLink() {
if (document.head.contains(link)) document.head.removeChild(link);
}
link.onload = () => {
removeLink();
resolve();
};
link.onerror = () => {
removeLink();
resolve();
};
document.head.appendChild(link);
});
}
function isPageLinkDescriptor(object) {
return object != null && typeof object.page === "string";
}
function isHtmlLinkDescriptor(object) {
if (object == null) return false;
if (object.href == null) return object.rel === "preload" && typeof object.imageSrcSet === "string" && typeof object.imageSizes === "string";
return typeof object.rel === "string" && typeof object.href === "string";
}
async function getKeyedPrefetchLinks(matches, manifest, routeModules) {
return dedupeLinkDescriptors((await Promise.all(matches.map(async (match) => {
let route = manifest.routes[match.route.id];
if (route) {
let mod = await loadRouteModule(route, routeModules);
return mod.links ? mod.links() : [];
}
return [];
}))).flat(1).filter(isHtmlLinkDescriptor).filter((link) => link.rel === "stylesheet" || link.rel === "preload").map((link) => link.rel === "stylesheet" ? {
...link,
rel: "prefetch",
as: "style"
} : {
...link,
rel: "prefetch"
}));
}
function getNewMatchesForLinks(page, nextMatches, currentMatches, manifest, location, mode) {
let isNew = (match, index) => {
if (!currentMatches[index]) return true;
return match.route.id !== currentMatches[index].route.id;
};
let matchPathChanged = (match, index) => {
return currentMatches[index].pathname !== match.pathname || currentMatches[index].route.path?.endsWith("*") && currentMatches[index].params["*"] !== match.params["*"];
};
if (mode === "assets") return nextMatches.filter((match, index) => isNew(match, index) || matchPathChanged(match, index));
if (mode === "data") return nextMatches.filter((match, index) => {
let manifestRoute = manifest.routes[match.route.id];
if (!manifestRoute || !manifestRoute.hasLoader) return false;
if (isNew(match, index) || matchPathChanged(match, index)) return true;
if (match.route.shouldRevalidate) {
let routeChoice = match.route.shouldRevalidate({
currentUrl: new URL(location.pathname + location.search + location.hash, window.origin),
currentParams: currentMatches[0]?.params || {},
nextUrl: new URL(page, window.origin),
nextParams: match.params,
defaultShouldRevalidate: true
});
if (typeof routeChoice === "boolean") return routeChoice;
}
return true;
});
return [];
}
function getModuleLinkHrefs(matches, manifest, { includeHydrateFallback } = {}) {
return dedupeHrefs(matches.map((match) => {
let route = manifest.routes[match.route.id];
if (!route) return [];
let hrefs = [route.module];
if (route.clientActionModule) hrefs = hrefs.concat(route.clientActionModule);
if (route.clientLoaderModule) hrefs = hrefs.concat(route.clientLoaderModule);
if (includeHydrateFallback && route.hydrateFallbackModule) hrefs = hrefs.concat(route.hydrateFallbackModule);
if (route.imports) hrefs = hrefs.concat(route.imports);
return hrefs;
}).flat(1));
}
function dedupeHrefs(hrefs) {
return [...new Set(hrefs)];
}
function sortKeys(obj) {
let sorted = {};
let keys = Object.keys(obj).sort();
for (let key of keys) sorted[key] = obj[key];
return sorted;
}
function dedupeLinkDescriptors(descriptors, preloads) {
let set = /* @__PURE__ */ new Set();
let preloadsSet = new Set(preloads);
return descriptors.reduce((deduped, descriptor) => {
if (preloads && !isPageLinkDescriptor(descriptor) && descriptor.as === "script" && descriptor.href && preloadsSet.has(descriptor.href)) return deduped;
let key = JSON.stringify(sortKeys(descriptor));
if (!set.has(key)) {
set.add(key);
deduped.push({
key,
link: descriptor
});
}
return deduped;
}, []);
}
let _isPreloadSupported;
function isPreloadSupported() {
if (_isPreloadSupported !== void 0) return _isPreloadSupported;
let el = document.createElement("link");
_isPreloadSupported = el.relList.supports("preload");
el = null;
return _isPreloadSupported;
}
//#endregion
export { getKeyedLinksForMatches, getKeyedPrefetchLinks, getModuleLinkHrefs, getNewMatchesForLinks, isPageLinkDescriptor, prefetchRouteCss, prefetchStyleLinks };
@@ -0,0 +1,24 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/dom/ssr/markup.ts
const ESCAPE_LOOKUP = {
"&": "\\u0026",
">": "\\u003e",
"<": "\\u003c",
"\u2028": "\\u2028",
"\u2029": "\\u2029"
};
const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
function escapeHtml(html) {
return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
}
//#endregion
export { escapeHtml };
@@ -0,0 +1,206 @@
import { Location } from "../../router/history.js";
import { ActionFunction, ActionFunctionArgs, DataRouteMatch, DataStrategyResult, LoaderFunction, LoaderFunctionArgs, MiddlewareFunction, Params, ShouldRevalidateFunction } from "../../router/utils.js";
import { LinkDescriptor } from "../../router/links.js";
import { SerializeFrom } from "../../types/route-data.js";
import { ComponentType, ReactElement } from "react";
//#region lib/dom/ssr/routeModules.d.ts
interface RouteModules {
[routeId: string]: RouteModule | undefined;
}
/**
* The shape of a route module shipped to the client
*/
interface RouteModule {
clientAction?: ClientActionFunction;
clientLoader?: ClientLoaderFunction;
clientMiddleware?: MiddlewareFunction<Record<string, DataStrategyResult>>[];
ErrorBoundary?: ErrorBoundaryComponent;
HydrateFallback?: HydrateFallbackComponent;
Layout?: LayoutComponent;
default: RouteComponent;
handle?: RouteHandle;
links?: LinksFunction;
meta?: MetaFunction;
shouldRevalidate?: ShouldRevalidateFunction;
}
/**
* The shape of a route module on the server
*/
interface ServerRouteModule extends RouteModule {
action?: ActionFunction;
headers?: HeadersFunction | {
[name: string]: string;
};
loader?: LoaderFunction;
middleware?: MiddlewareFunction<Response>[];
}
/**
* A function that handles data mutations for a route on the client
*/
type ClientActionFunction = (args: ClientActionFunctionArgs) => ReturnType<ActionFunction>;
/**
* Arguments passed to a route `clientAction` function
*/
type ClientActionFunctionArgs = ActionFunctionArgs & {
serverAction: <T = unknown>() => Promise<SerializeFrom<T>>;
};
/**
* A function that loads data for a route on the client
*/
type ClientLoaderFunction = ((args: ClientLoaderFunctionArgs) => ReturnType<LoaderFunction>) & {
hydrate?: boolean;
};
/**
* Arguments passed to a route `clientLoader` function
*/
type ClientLoaderFunctionArgs = LoaderFunctionArgs & {
serverLoader: <T = unknown>() => Promise<SerializeFrom<T>>;
};
/**
* ErrorBoundary to display for this route
*/
type ErrorBoundaryComponent = ComponentType;
type HeadersArgs = {
loaderHeaders: Headers;
parentHeaders: Headers;
actionHeaders: Headers;
errorHeaders: Headers | undefined;
};
/**
* A function that returns HTTP headers to be used for a route. These headers
* will be merged with (and take precedence over) headers from parent routes.
*/
interface HeadersFunction {
(args: HeadersArgs): Headers | HeadersInit;
}
/**
* `<Route HydrateFallback>` component to render on initial loads
* when client loaders are present
*/
type HydrateFallbackComponent = ComponentType;
/**
* Optional, root-only `<Route Layout>` component to wrap the root content in.
* Useful for defining the <html>/<head>/<body> document shell shared by the
* Component, HydrateFallback, and ErrorBoundary
*/
type LayoutComponent = ComponentType<{
children: ReactElement<unknown, ErrorBoundaryComponent | HydrateFallbackComponent | RouteComponent>;
}>;
/**
* A function that defines `<link>` tags to be inserted into the `<head>` of
* the document on route transitions.
*
* @see https://reactrouter.com/start/framework/route-module#meta
*/
interface LinksFunction {
(): LinkDescriptor[];
}
interface MetaMatch<RouteId extends string = string, Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown> {
id: RouteId;
pathname: DataRouteMatch["pathname"];
loaderData: Loader extends LoaderFunction | ClientLoaderFunction ? SerializeFrom<Loader> : unknown;
handle?: RouteHandle;
params: DataRouteMatch["params"];
meta: MetaDescriptor[];
error?: unknown;
}
type MetaMatches<MatchLoaders extends Record<string, LoaderFunction | ClientLoaderFunction | unknown> = Record<string, unknown>> = Array<{ [K in keyof MatchLoaders]: MetaMatch<Exclude<K, number | symbol>, MatchLoaders[K]> }[keyof MatchLoaders]>;
interface MetaArgs<Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown, MatchLoaders extends Record<string, LoaderFunction | ClientLoaderFunction | unknown> = Record<string, unknown>> {
loaderData: (Loader extends LoaderFunction | ClientLoaderFunction ? SerializeFrom<Loader> : unknown) | undefined;
params: Params;
location: Location;
matches: MetaMatches<MatchLoaders>;
error?: unknown;
}
/**
* A function that returns an array of data objects to use for rendering
* metadata HTML tags in a route. These tags are not rendered on descendant
* routes in the route hierarchy. In other words, they will only be rendered on
* the route in which they are exported.
*
* @param Loader - The type of the current route's loader function
* @param MatchLoaders - Mapping from a parent route's filepath to its loader
* function type
*
* Note that parent route filepaths are relative to the `app/` directory.
*
* For example, if this meta function is for `/sales/customers/$customerId`:
*
* ```ts
* // app/root.tsx
* const loader = () => ({ hello: "world" })
* export type Loader = typeof loader
*
* // app/routes/sales.tsx
* const loader = () => ({ salesCount: 1074 })
* export type Loader = typeof loader
*
* // app/routes/sales/customers.tsx
* const loader = () => ({ customerCount: 74 })
* export type Loader = typeof loader
*
* // app/routes/sales/customers/$customersId.tsx
* import type { Loader as RootLoader } from "../../../root"
* import type { Loader as SalesLoader } from "../../sales"
* import type { Loader as CustomersLoader } from "../../sales/customers"
*
* const loader = () => ({ name: "Customer name" })
*
* const meta: MetaFunction<typeof loader, {
* "root": RootLoader,
* "routes/sales": SalesLoader,
* "routes/sales/customers": CustomersLoader,
* }> = ({ loaderData, matches }) => {
* const { name } = loaderData
* // ^? string
* const { customerCount } = matches.find((match) => match.id === "routes/sales/customers").loaderData
* // ^? number
* const { salesCount } = matches.find((match) => match.id === "routes/sales").loaderData
* // ^? number
* const { hello } = matches.find((match) => match.id === "root").loaderData
* // ^? "world"
* }
* ```
*/
interface MetaFunction<Loader extends LoaderFunction | ClientLoaderFunction | unknown = unknown, MatchLoaders extends Record<string, LoaderFunction | ClientLoaderFunction | unknown> = Record<string, unknown>> {
(args: MetaArgs<Loader, MatchLoaders>): MetaDescriptor[] | undefined;
}
type MetaDescriptor = {
charSet: "utf-8";
} | {
title: string;
} | {
name: string;
content: string;
} | {
property: string;
content: string;
} | {
httpEquiv: string;
content: string;
} | {
"script:ld+json": LdJsonObject | LdJsonObject[];
} | {
tagName: "meta" | "link";
[name: string]: string;
} | {
[name: string]: unknown;
};
type LdJsonObject = { [Key in string]: LdJsonValue } & { [Key in string]?: LdJsonValue | undefined };
type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
type LdJsonPrimitive = string | number | boolean | null;
type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
/**
* A React component that is rendered for a route.
*/
type RouteComponent = ComponentType<{}>;
/**
* An arbitrary object that is associated with a route.
*
* @see https://reactrouter.com/how-to/using-handle
*/
type RouteHandle = unknown;
//#endregion
export { ClientActionFunction, ClientActionFunctionArgs, ClientLoaderFunction, ClientLoaderFunctionArgs, HeadersArgs, HeadersFunction, LinksFunction, MetaArgs, MetaDescriptor, MetaFunction, RouteModules, ServerRouteModule };
@@ -0,0 +1,31 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/dom/ssr/routeModules.ts
async function loadRouteModule(route, routeModulesCache) {
if (route.id in routeModulesCache) return routeModulesCache[route.id];
try {
let routeModule = await import(
/* @vite-ignore */
/* webpackIgnore: true */
route.module
);
routeModulesCache[route.id] = routeModule;
return routeModule;
} catch (error) {
console.error(`Error loading route module \`${route.module}\`, reloading page...`);
console.error(error);
if (window.__reactRouterContext && window.__reactRouterContext.isSpaMode && import.meta.hot) throw error;
window.location.reload();
return new Promise(() => {});
}
}
//#endregion
export { loadRouteModule };
@@ -0,0 +1,75 @@
import { InitialEntry } from "../../router/history.js";
import { ActionFunction, IndexRouteObject, LoaderFunction, NonIndexRouteObject, RouterContextProvider } from "../../router/utils.js";
import { HydrationState } from "../../router/router.js";
import { LinksFunction, MetaFunction } from "./routeModules.js";
import { FutureConfig } from "./entry.js";
import * as React$1 from "react";
//#region lib/dom/ssr/routes-test-stub.d.ts
interface StubRouteExtensions {
Component?: React$1.ComponentType<any>;
HydrateFallback?: React$1.ComponentType<any>;
ErrorBoundary?: React$1.ComponentType<any>;
loader?: LoaderFunction;
action?: ActionFunction;
children?: StubRouteObject[];
meta?: MetaFunction;
links?: LinksFunction;
}
interface StubIndexRouteObject extends Omit<IndexRouteObject, "Component" | "HydrateFallback" | "ErrorBoundary" | "loader" | "action" | "element" | "errorElement" | "children">, StubRouteExtensions {}
interface StubNonIndexRouteObject extends Omit<NonIndexRouteObject, "Component" | "HydrateFallback" | "ErrorBoundary" | "loader" | "action" | "element" | "errorElement" | "children">, StubRouteExtensions {}
type StubRouteObject = StubIndexRouteObject | StubNonIndexRouteObject;
interface RoutesTestStubProps {
/**
* The initial entries in the history stack. This allows you to start a test with
* multiple locations already in the history stack (for testing a back navigation, etc.)
* The test will default to the last entry in initialEntries if no initialIndex is provided.
* e.g. initialEntries={["/home", "/about", "/contact"]}
*/
initialEntries?: InitialEntry[];
/**
* The initial index in the history stack to render. This allows you to start a test at a specific entry.
* It defaults to the last entry in initialEntries.
* e.g.
* initialEntries: ["/", "/events/123"]
* initialIndex: 1 // start at "/events/123"
*/
initialIndex?: number;
/**
* Used to set the route's initial loader and action data.
* e.g. hydrationData={{
* loaderData: { "/contact": { locale: "en-US" } },
* actionData: { "/login": { errors: { email: "invalid email" } }}
* }}
*/
hydrationData?: HydrationState;
/**
* Future flags mimicking the settings in react-router.config.ts
*/
future?: Partial<FutureConfig>;
}
/**
* Creates a React component that renders the provided routes in a test-friendly
* React Router context.
*
* Use this to unit test components that rely on router context, such as
* `loaderData`, `actionData`, and route matches.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param routes The route objects to render in the test router.
* @param _context An optional {@link RouterContextProvider} for supplying
* application context values to route middleware, loaders, and actions.
* @returns A React component that renders the test router.
*/
declare function createRoutesStub(routes: StubRouteObject[], _context?: RouterContextProvider): ({
initialEntries,
initialIndex,
hydrationData,
future
}: RoutesTestStubProps) => React$1.JSX.Element;
//#endregion
export { RoutesTestStubProps, createRoutesStub };
@@ -0,0 +1,121 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { RouterContextProvider, convertRoutesToDataRoutes } from "../../router/utils.js";
import { Outlet, RouterProvider, createMemoryRouter, withComponentProps, withErrorBoundaryProps, withHydrateFallbackProps } from "../../components.js";
import { FrameworkContext } from "./components.js";
import * as React$1 from "react";
//#region lib/dom/ssr/routes-test-stub.tsx
/**
* Creates a React component that renders the provided routes in a test-friendly
* React Router context.
*
* Use this to unit test components that rely on router context, such as
* `loaderData`, `actionData`, and route matches.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param routes The route objects to render in the test router.
* @param _context An optional {@link RouterContextProvider} for supplying
* application context values to route middleware, loaders, and actions.
* @returns A React component that renders the test router.
*/
function createRoutesStub(routes, _context) {
return function RoutesTestStub({ initialEntries, initialIndex, hydrationData, future }) {
let routerRef = React$1.useRef(null);
let frameworkContextRef = React$1.useRef(null);
if (routerRef.current == null || frameworkContextRef.current == null) {
frameworkContextRef.current = {
future: {},
manifest: {
routes: {},
entry: {
imports: [],
module: ""
},
url: "",
version: ""
},
routeModules: {},
ssr: false,
isSpaMode: false,
routeDiscovery: {
mode: "lazy",
manifestPath: "/__manifest"
}
};
routerRef.current = createMemoryRouter(processRoutes(convertRoutesToDataRoutes(routes, (r) => r), _context ?? new RouterContextProvider(), frameworkContextRef.current.manifest, frameworkContextRef.current.routeModules), {
initialEntries,
initialIndex,
hydrationData
});
}
return /* @__PURE__ */ React$1.createElement(FrameworkContext.Provider, { value: frameworkContextRef.current }, /* @__PURE__ */ React$1.createElement(RouterProvider, { router: routerRef.current }));
};
}
function processRoutes(routes, context, manifest, routeModules, parentId) {
return routes.map((route) => {
if (!route.id) throw new Error("Expected a route.id in react-router processRoutes() function");
let newRoute = {
id: route.id,
path: route.path,
index: route.index,
Component: route.Component ? withComponentProps(route.Component) : void 0,
HydrateFallback: route.HydrateFallback ? withHydrateFallbackProps(route.HydrateFallback) : void 0,
ErrorBoundary: route.ErrorBoundary ? withErrorBoundaryProps(route.ErrorBoundary) : void 0,
action: route.action ? (args) => route.action({
...args,
context
}) : void 0,
loader: route.loader ? (args) => route.loader({
...args,
context
}) : void 0,
middleware: route.middleware ? route.middleware.map((mw) => (...args) => mw({
...args[0],
context
}, args[1])) : void 0,
handle: route.handle,
shouldRevalidate: route.shouldRevalidate
};
let entryRoute = {
id: route.id,
path: route.path,
index: route.index,
parentId,
hasAction: route.action != null,
hasLoader: route.loader != null,
hasClientAction: false,
hasClientLoader: false,
hasClientMiddleware: false,
hasErrorBoundary: route.ErrorBoundary != null,
module: "build/stub-path-to-module.js",
clientActionModule: void 0,
clientLoaderModule: void 0,
clientMiddlewareModule: void 0,
hydrateFallbackModule: void 0
};
manifest.routes[newRoute.id] = entryRoute;
routeModules[route.id] = {
default: newRoute.Component || Outlet,
ErrorBoundary: newRoute.ErrorBoundary || void 0,
handle: route.handle,
links: route.links,
meta: route.meta,
shouldRevalidate: route.shouldRevalidate
};
if (route.children) newRoute.children = processRoutes(route.children, context, manifest, routeModules, newRoute.id);
return newRoute;
});
}
//#endregion
export { createRoutesStub };
@@ -0,0 +1,33 @@
import { DataRouteObject, RouteManifest } from "../../router/utils.js";
import { HydrationState } from "../../router/router.js";
import { ClientLoaderFunction, RouteModules } from "./routeModules.js";
//#region lib/dom/ssr/routes.d.ts
interface Route {
index?: boolean;
caseSensitive?: boolean;
id: string;
parentId?: string;
path?: string;
}
interface EntryRoute extends Route {
hasAction: boolean;
hasLoader: boolean;
hasClientAction: boolean;
hasClientLoader: boolean;
hasClientMiddleware: boolean;
hasErrorBoundary: boolean;
imports?: string[];
css?: string[];
module: string;
clientActionModule: string | undefined;
clientLoaderModule: string | undefined;
clientMiddlewareModule: string | undefined;
hydrateFallbackModule: string | undefined;
parentId?: string;
}
declare function createClientRoutesWithHMRRevalidationOptOut(needsRevalidation: Set<string>, manifest: RouteManifest<EntryRoute>, routeModulesCache: RouteModules, initialState: HydrationState, ssr: boolean, isSpaMode: boolean): DataRouteObject[];
declare function createClientRoutes(manifest: RouteManifest<EntryRoute>, routeModulesCache: RouteModules, initialState: HydrationState | null, ssr: boolean, isSpaMode: boolean, parentId?: string, routesByParentId?: Record<string, Omit<EntryRoute, "children">[]>, needsRevalidation?: Set<string>): DataRouteObject[];
declare function shouldHydrateRouteLoader(routeId: string, clientLoader: ClientLoaderFunction | undefined, hasLoader: boolean, isSpaMode: boolean): boolean;
//#endregion
export { EntryRoute, Route, createClientRoutes, createClientRoutesWithHMRRevalidationOptOut, shouldHydrateRouteLoader };
@@ -0,0 +1,303 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl, compilePath } from "../../router/utils.js";
import { useRouteError } from "../../hooks.js";
import invariant from "./invariant.js";
import { loadRouteModule } from "./routeModules.js";
import { prefetchRouteCss, prefetchStyleLinks } from "./links.js";
import { RemixRootDefaultErrorBoundary } from "./errorBoundaries.js";
import { RemixRootDefaultHydrateFallback } from "./fallback.js";
import * as React$1 from "react";
//#region lib/dom/ssr/routes.tsx
function groupRoutesByParentId(manifest) {
let routes = {};
Object.values(manifest).forEach((route) => {
if (route) {
let parentId = route.parentId || "";
if (!routes[parentId]) routes[parentId] = [];
routes[parentId].push(route);
}
});
return routes;
}
function getRouteComponents(route, routeModule, isSpaMode) {
let Component = getRouteModuleComponent(routeModule);
let HydrateFallback = routeModule.HydrateFallback && (!isSpaMode || route.id === "root") ? routeModule.HydrateFallback : route.id === "root" ? RemixRootDefaultHydrateFallback : void 0;
let ErrorBoundary = routeModule.ErrorBoundary ? routeModule.ErrorBoundary : route.id === "root" ? () => /* @__PURE__ */ React$1.createElement(RemixRootDefaultErrorBoundary, { error: useRouteError() }) : void 0;
if (route.id === "root" && routeModule.Layout) return {
...Component ? { element: /* @__PURE__ */ React$1.createElement(routeModule.Layout, null, /* @__PURE__ */ React$1.createElement(Component, null)) } : { Component },
...ErrorBoundary ? { errorElement: /* @__PURE__ */ React$1.createElement(routeModule.Layout, null, /* @__PURE__ */ React$1.createElement(ErrorBoundary, null)) } : { ErrorBoundary },
...HydrateFallback ? { hydrateFallbackElement: /* @__PURE__ */ React$1.createElement(routeModule.Layout, null, /* @__PURE__ */ React$1.createElement(HydrateFallback, null)) } : { HydrateFallback }
};
return {
Component,
ErrorBoundary,
HydrateFallback
};
}
function createServerRoutes(manifest, routeModules, future, isSpaMode, parentId = "", routesByParentId = groupRoutesByParentId(manifest), spaModeLazyPromise = Promise.resolve({ Component: () => null })) {
return (routesByParentId[parentId] || []).map((route) => {
let routeModule = routeModules[route.id];
invariant(routeModule, "No `routeModule` available to create server routes");
let dataRoute = {
...getRouteComponents(route, routeModule, isSpaMode),
caseSensitive: route.caseSensitive,
id: route.id,
index: route.index,
path: route.path,
handle: routeModule.handle,
lazy: isSpaMode ? () => spaModeLazyPromise : void 0,
loader: route.hasLoader || route.hasClientLoader ? () => null : void 0
};
let children = createServerRoutes(manifest, routeModules, future, isSpaMode, route.id, routesByParentId, spaModeLazyPromise);
if (children.length > 0) dataRoute.children = children;
return dataRoute;
});
}
function createClientRoutesWithHMRRevalidationOptOut(needsRevalidation, manifest, routeModulesCache, initialState, ssr, isSpaMode) {
return createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSpaMode, "", groupRoutesByParentId(manifest), needsRevalidation);
}
function preventInvalidServerHandlerCall(type, route) {
if (type === "loader" && !route.hasLoader || type === "action" && !route.hasAction) {
let msg = `You are trying to call ${type === "action" ? "serverAction()" : "serverLoader()"} on a route that does not have a server ${type} (routeId: "${route.id}")`;
console.error(msg);
throw new ErrorResponseImpl(400, "Bad Request", new Error(msg), true);
}
}
function noActionDefinedError(type, routeId) {
let article = type === "clientAction" ? "a" : "an";
let msg = `Route "${routeId}" does not have ${article} ${type}, but you are trying to submit to it. To fix this, please add ${article} \`${type}\` function to the route`;
console.error(msg);
throw new ErrorResponseImpl(405, "Method Not Allowed", new Error(msg), true);
}
function createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSpaMode, parentId = "", routesByParentId = groupRoutesByParentId(manifest), needsRevalidation) {
return (routesByParentId[parentId] || []).map((route) => {
let routeModule = routeModulesCache[route.id];
function fetchServerHandler(singleFetch) {
invariant(typeof singleFetch === "function", "No single fetch function available for route handler");
return singleFetch();
}
function fetchServerLoader(singleFetch) {
if (!route.hasLoader) return Promise.resolve(null);
return fetchServerHandler(singleFetch);
}
function fetchServerAction(singleFetch) {
if (!route.hasAction) throw noActionDefinedError("action", route.id);
return fetchServerHandler(singleFetch);
}
function prefetchModule(modulePath) {
import(
/* @vite-ignore */
/* webpackIgnore: true */
modulePath
);
}
function prefetchRouteModuleChunks(route) {
if (route.clientActionModule) prefetchModule(route.clientActionModule);
if (route.clientLoaderModule) prefetchModule(route.clientLoaderModule);
}
async function prefetchStylesAndCallHandler(handler) {
let cachedModule = routeModulesCache[route.id];
let linkPrefetchPromise = cachedModule ? prefetchStyleLinks(route, cachedModule) : Promise.resolve();
try {
return handler();
} finally {
await linkPrefetchPromise;
}
}
let dataRoute = {
id: route.id,
index: route.index,
path: route.path
};
if (routeModule) {
Object.assign(dataRoute, {
...dataRoute,
...getRouteComponents(route, routeModule, isSpaMode),
middleware: routeModule.clientMiddleware,
handle: routeModule.handle,
shouldRevalidate: getShouldRevalidateFunction(dataRoute.path, routeModule, route, ssr, needsRevalidation)
});
let hasInitialData = initialState && initialState.loaderData && route.id in initialState.loaderData;
let initialData = hasInitialData ? initialState?.loaderData?.[route.id] : void 0;
let hasInitialError = initialState && initialState.errors && route.id in initialState.errors;
let initialError = hasInitialError ? initialState?.errors?.[route.id] : void 0;
let isHydrationRequest = needsRevalidation == null && (routeModule.clientLoader?.hydrate === true || !route.hasLoader);
dataRoute.loader = async ({ request, params, context, pattern, url }, singleFetch) => {
let _isHydrationRequest = isHydrationRequest;
isHydrationRequest = false;
return await prefetchStylesAndCallHandler(async () => {
invariant(routeModule, "No `routeModule` available for critical-route loader");
if (!routeModule.clientLoader) return fetchServerLoader(singleFetch);
return routeModule.clientLoader({
request,
params,
context,
pattern,
url,
async serverLoader() {
preventInvalidServerHandlerCall("loader", route);
if (_isHydrationRequest) {
if (hasInitialData) return initialData;
if (hasInitialError) throw initialError;
}
return fetchServerLoader(singleFetch);
}
});
});
};
dataRoute.loader.hydrate = shouldHydrateRouteLoader(route.id, routeModule.clientLoader, route.hasLoader, isSpaMode);
dataRoute.action = ({ request, params, context, pattern, url }, singleFetch) => {
return prefetchStylesAndCallHandler(async () => {
invariant(routeModule, "No `routeModule` available for critical-route action");
if (!routeModule.clientAction) {
if (isSpaMode) throw noActionDefinedError("clientAction", route.id);
return fetchServerAction(singleFetch);
}
return routeModule.clientAction({
request,
params,
context,
pattern,
url,
async serverAction() {
preventInvalidServerHandlerCall("action", route);
return fetchServerAction(singleFetch);
}
});
});
};
} else {
if (!route.hasClientLoader) dataRoute.loader = (_, singleFetch) => prefetchStylesAndCallHandler(() => {
return fetchServerLoader(singleFetch);
});
if (!route.hasClientAction) dataRoute.action = (_, singleFetch) => prefetchStylesAndCallHandler(() => {
if (isSpaMode) throw noActionDefinedError("clientAction", route.id);
return fetchServerAction(singleFetch);
});
let lazyRoutePromise;
async function getLazyRoute() {
if (lazyRoutePromise) return await lazyRoutePromise;
lazyRoutePromise = (async () => {
if (route.clientLoaderModule || route.clientActionModule) await new Promise((resolve) => setTimeout(resolve, 0));
let routeModulePromise = loadRouteModuleWithBlockingLinks(route, routeModulesCache);
prefetchRouteModuleChunks(route);
return await routeModulePromise;
})();
return await lazyRoutePromise;
}
dataRoute.lazy = {
loader: route.hasClientLoader ? async () => {
let { clientLoader } = route.clientLoaderModule ? await import(
/* @vite-ignore */
/* webpackIgnore: true */
route.clientLoaderModule
) : await getLazyRoute();
invariant(clientLoader, "No `clientLoader` export found");
return (args, singleFetch) => clientLoader({
...args,
async serverLoader() {
preventInvalidServerHandlerCall("loader", route);
return fetchServerLoader(singleFetch);
}
});
} : void 0,
action: route.hasClientAction ? async () => {
let clientActionPromise = route.clientActionModule ? import(
/* @vite-ignore */
/* webpackIgnore: true */
route.clientActionModule
) : getLazyRoute();
prefetchRouteModuleChunks(route);
let { clientAction } = await clientActionPromise;
invariant(clientAction, "No `clientAction` export found");
return (args, singleFetch) => clientAction({
...args,
async serverAction() {
preventInvalidServerHandlerCall("action", route);
return fetchServerAction(singleFetch);
}
});
} : void 0,
middleware: route.hasClientMiddleware ? async () => {
let { clientMiddleware } = route.clientMiddlewareModule ? await import(
/* @vite-ignore */
/* webpackIgnore: true */
route.clientMiddlewareModule
) : await getLazyRoute();
invariant(clientMiddleware, "No `clientMiddleware` export found");
return clientMiddleware;
} : void 0,
shouldRevalidate: async () => {
let lazyRoute = await getLazyRoute();
return getShouldRevalidateFunction(dataRoute.path, lazyRoute, route, ssr, needsRevalidation);
},
handle: async () => (await getLazyRoute()).handle,
Component: async () => (await getLazyRoute()).Component,
ErrorBoundary: route.hasErrorBoundary ? async () => (await getLazyRoute()).ErrorBoundary : void 0
};
}
let children = createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSpaMode, route.id, routesByParentId, needsRevalidation);
if (children.length > 0) dataRoute.children = children;
return dataRoute;
});
}
function getShouldRevalidateFunction(path, route, manifestRoute, ssr, needsRevalidation) {
if (needsRevalidation) return wrapShouldRevalidateForHdr(manifestRoute.id, route.shouldRevalidate, needsRevalidation);
if (!ssr && manifestRoute.hasLoader && !manifestRoute.hasClientLoader) {
let myParams = path ? compilePath(path)[1].map((p) => p.paramName) : [];
const didParamsChange = (opts) => myParams.some((p) => opts.currentParams[p] !== opts.nextParams[p]);
if (route.shouldRevalidate) {
let fn = route.shouldRevalidate;
return (opts) => fn({
...opts,
defaultShouldRevalidate: didParamsChange(opts)
});
} else return (opts) => didParamsChange(opts);
}
return route.shouldRevalidate;
}
function wrapShouldRevalidateForHdr(routeId, routeShouldRevalidate, needsRevalidation) {
let handledRevalidation = false;
return (arg) => {
if (!handledRevalidation) {
handledRevalidation = true;
return needsRevalidation.has(routeId);
}
return routeShouldRevalidate ? routeShouldRevalidate(arg) : arg.defaultShouldRevalidate;
};
}
async function loadRouteModuleWithBlockingLinks(route, routeModules) {
let routeModulePromise = loadRouteModule(route, routeModules);
let prefetchRouteCssPromise = prefetchRouteCss(route);
let routeModule = await routeModulePromise;
await Promise.all([prefetchRouteCssPromise, prefetchStyleLinks(route, routeModule)]);
return {
Component: getRouteModuleComponent(routeModule),
ErrorBoundary: routeModule.ErrorBoundary,
clientMiddleware: routeModule.clientMiddleware,
clientAction: routeModule.clientAction,
clientLoader: routeModule.clientLoader,
handle: routeModule.handle,
links: routeModule.links,
meta: routeModule.meta,
shouldRevalidate: routeModule.shouldRevalidate
};
}
function getRouteModuleComponent(routeModule) {
if (routeModule.default == null) return void 0;
if (!(typeof routeModule.default === "object" && Object.keys(routeModule.default).length === 0)) return routeModule.default;
}
function shouldHydrateRouteLoader(routeId, clientLoader, hasLoader, isSpaMode) {
return isSpaMode && routeId !== "root" || clientLoader != null && (clientLoader.hydrate === true || hasLoader !== true);
}
//#endregion
export { createClientRoutes, createClientRoutesWithHMRRevalidationOptOut, createServerRoutes, noActionDefinedError, shouldHydrateRouteLoader };
@@ -0,0 +1,48 @@
import { EntryContext } from "./entry.js";
import { ReactElement } from "react";
//#region lib/dom/ssr/server.d.ts
/**
* @category Types
*/
interface ServerRouterProps {
/**
* The entry context containing the manifest, route modules, and other data
* needed for rendering.
*/
context: EntryContext;
/**
* The URL of the request being handled.
*/
url: string | URL;
/**
* An optional `nonce` for [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP)
* compliance. This is applied to inline scripts rendered by React Router and
* used as the default for nonce-aware components such as {@link Links | `<Links>`},
* {@link Scripts | `<Scripts>`}, and {@link ScrollRestoration | `<ScrollRestoration>`}
* when they do not provide their own `nonce`.
*/
nonce?: string;
}
/**
* The server entry point for a React Router app in Framework Mode. This
* component is used to generate the HTML in the response from the server. See
* [`entry.server.tsx`](../framework-conventions/entry.server.tsx).
*
* @public
* @category Framework Routers
* @mode framework
* @param props Props
* @param {ServerRouterProps.context} props.context n/a
* @param {ServerRouterProps.nonce} props.nonce n/a
* @param {ServerRouterProps.url} props.url n/a
* @returns A React element that represents the server-rendered application.
*/
declare function ServerRouter({
context,
url,
nonce
}: ServerRouterProps): ReactElement;
//#endregion
export { ServerRouter, ServerRouterProps };
@@ -0,0 +1,69 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { StreamTransfer } from "./single-fetch.js";
import { RemixErrorBoundary } from "./errorBoundaries.js";
import { createServerRoutes, shouldHydrateRouteLoader } from "./routes.js";
import { FrameworkContext } from "./components.js";
import { StaticRouterProvider, createStaticRouter } from "../server.js";
import * as React$1 from "react";
//#region lib/dom/ssr/server.tsx
/**
* The server entry point for a React Router app in Framework Mode. This
* component is used to generate the HTML in the response from the server. See
* [`entry.server.tsx`](../framework-conventions/entry.server.tsx).
*
* @public
* @category Framework Routers
* @mode framework
* @param props Props
* @param {ServerRouterProps.context} props.context n/a
* @param {ServerRouterProps.nonce} props.nonce n/a
* @param {ServerRouterProps.url} props.url n/a
* @returns A React element that represents the server-rendered application.
*/
function ServerRouter({ context, url, nonce }) {
if (typeof url === "string") url = new URL(url);
let { manifest, routeModules, criticalCss, serverHandoffString } = context;
let routes = createServerRoutes(manifest.routes, routeModules, context.future, context.isSpaMode);
context.staticHandlerContext.loaderData = { ...context.staticHandlerContext.loaderData };
for (let match of context.staticHandlerContext.matches) {
let routeId = match.route.id;
let route = routeModules[routeId];
let manifestRoute = context.manifest.routes[routeId];
if (route && manifestRoute && shouldHydrateRouteLoader(routeId, route.clientLoader, manifestRoute.hasLoader, context.isSpaMode) && (route.HydrateFallback || !manifestRoute.hasLoader)) delete context.staticHandlerContext.loaderData[routeId];
}
let router = createStaticRouter(routes, context.staticHandlerContext, { branches: context.branches });
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(FrameworkContext.Provider, { value: {
manifest,
routeModules,
criticalCss,
serverHandoffString,
future: context.future,
ssr: context.ssr,
isSpaMode: context.isSpaMode,
routeDiscovery: context.routeDiscovery,
nonce,
serializeError: context.serializeError,
renderMeta: context.renderMeta
} }, /* @__PURE__ */ React$1.createElement(RemixErrorBoundary, { location: router.state.location }, /* @__PURE__ */ React$1.createElement(StaticRouterProvider, {
router,
context: context.staticHandlerContext,
hydrate: false
}))), context.serverHandoffStream ? /* @__PURE__ */ React$1.createElement(React$1.Suspense, null, /* @__PURE__ */ React$1.createElement(StreamTransfer, {
context,
identifier: 0,
reader: context.serverHandoffStream.getReader(),
textDecoder: new TextDecoder(),
nonce
})) : null);
}
//#endregion
export { ServerRouter };
@@ -0,0 +1,14 @@
import { DataStrategyFunction } from "../../router/utils.js";
import { Router } from "../../router/router.js";
import { RouteModules } from "./routeModules.js";
import { AssetsManifest } from "./entry.js";
//#region lib/dom/ssr/single-fetch.d.ts
declare const SingleFetchRedirectSymbol: unique symbol;
declare function getTurboStreamSingleFetchDataStrategy(getRouter: () => Router, manifest: AssetsManifest, routeModules: RouteModules, ssr: boolean): DataStrategyFunction;
declare function decodeViaTurboStream(body: ReadableStream<Uint8Array>, global: Window | typeof globalThis): Promise<{
done: Promise<void>;
value: unknown;
}>;
//#endregion
export { SingleFetchRedirectSymbol, decodeViaTurboStream, getTurboStreamSingleFetchDataStrategy };
@@ -0,0 +1,345 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl, SUPPORTED_ERROR_TYPES, data, isRouteErrorResponse, redirect } from "../../router/utils.js";
import { isDataWithResponseInit, isResponse } from "../../router/router.js";
import invariant from "./invariant.js";
import { escapeHtml } from "./markup.js";
import { decode } from "../../../vendor/turbo-stream-v2/turbo-stream.js";
import { createRequestInit } from "./data.js";
import * as React$1 from "react";
//#region lib/dom/ssr/single-fetch.tsx
const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");
var SingleFetchNoResultError = class extends Error {};
const NO_BODY_STATUS_CODES = new Set([
100,
101,
204,
205
]);
function StreamTransfer({ context, identifier, reader, textDecoder, nonce }) {
if (!context.renderMeta || !context.renderMeta.didRenderScripts) return null;
if (!context.renderMeta.streamCache) context.renderMeta.streamCache = {};
let { streamCache } = context.renderMeta;
let promise = streamCache[identifier];
if (!promise) promise = streamCache[identifier] = reader.read().then((result) => {
streamCache[identifier].result = {
done: result.done,
value: textDecoder.decode(result.value, { stream: true })
};
}).catch((e) => {
streamCache[identifier].error = e;
});
if (promise.error) throw promise.error;
if (promise.result === void 0) throw promise;
let { done, value } = promise.result;
let scriptTag = value ? /* @__PURE__ */ React$1.createElement("script", {
nonce,
dangerouslySetInnerHTML: { __html: `window.__reactRouterContext.streamController.enqueue(${escapeHtml(JSON.stringify(value))});` }
}) : null;
if (done) return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, scriptTag, /* @__PURE__ */ React$1.createElement("script", {
nonce,
dangerouslySetInnerHTML: { __html: `window.__reactRouterContext.streamController.close();` }
}));
else return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, scriptTag, /* @__PURE__ */ React$1.createElement(React$1.Suspense, null, /* @__PURE__ */ React$1.createElement(StreamTransfer, {
context,
identifier: identifier + 1,
reader,
textDecoder,
nonce
})));
}
function getTurboStreamSingleFetchDataStrategy(getRouter, manifest, routeModules, ssr) {
let dataStrategy = getSingleFetchDataStrategyImpl(getRouter, (match) => {
let manifestRoute = manifest.routes[match.route.id];
invariant(manifestRoute, "Route not found in manifest");
return {
hasLoader: manifestRoute.hasLoader,
hasClientLoader: manifestRoute.hasClientLoader
};
}, fetchAndDecodeViaTurboStream, ssr);
return async (args) => args.runClientMiddleware(dataStrategy);
}
function getSingleFetchDataStrategyImpl(getRouter, getRouteInfo, fetchAndDecode, ssr, shouldAllowOptOut = () => true) {
return async (args) => {
let { request, matches, fetcherKey } = args;
let router = getRouter();
if (request.method !== "GET") return singleFetchActionStrategy(args, fetchAndDecode);
let foundRevalidatingServerLoader = matches.some((m) => {
let { hasLoader, hasClientLoader } = getRouteInfo(m);
return m.shouldCallHandler() && hasLoader && !hasClientLoader;
});
if (!ssr && !foundRevalidatingServerLoader) return nonSsrStrategy(args, getRouteInfo, fetchAndDecode);
if (fetcherKey) return singleFetchLoaderFetcherStrategy(args, fetchAndDecode);
return singleFetchLoaderNavigationStrategy(args, router, getRouteInfo, fetchAndDecode, ssr, shouldAllowOptOut);
};
}
async function singleFetchActionStrategy(args, fetchAndDecode) {
let actionMatch = args.matches.find((m) => m.shouldCallHandler());
invariant(actionMatch, "No action match found");
let actionStatus = void 0;
let result = await actionMatch.resolve(async (handler) => {
return await handler(async () => {
let { data, status } = await fetchAndDecode(args, [actionMatch.route.id]);
actionStatus = status;
return unwrapSingleFetchResult(data, actionMatch.route.id);
});
});
if (isResponse(result.result) || isRouteErrorResponse(result.result) || isDataWithResponseInit(result.result)) return { [actionMatch.route.id]: result };
return { [actionMatch.route.id]: {
type: result.type,
result: data(result.result, actionStatus)
} };
}
async function nonSsrStrategy(args, getRouteInfo, fetchAndDecode) {
let matchesToLoad = args.matches.filter((m) => m.shouldCallHandler());
let results = {};
await Promise.all(matchesToLoad.map((m) => m.resolve(async (handler) => {
try {
let { hasClientLoader } = getRouteInfo(m);
let routeId = m.route.id;
let result = hasClientLoader ? await handler(async () => {
let { data } = await fetchAndDecode(args, [routeId]);
return unwrapSingleFetchResult(data, routeId);
}) : await handler();
results[m.route.id] = {
type: "data",
result
};
} catch (e) {
results[m.route.id] = {
type: "error",
result: e
};
}
})));
return results;
}
async function singleFetchLoaderNavigationStrategy(args, router, getRouteInfo, fetchAndDecode, ssr, shouldAllowOptOut = () => true) {
let routesParams = /* @__PURE__ */ new Set();
let foundOptOutRoute = false;
let routeDfds = args.matches.map(() => createDeferred());
let singleFetchDfd = createDeferred();
let results = {};
let resolvePromise = Promise.all(args.matches.map(async (m, i) => m.resolve(async (handler) => {
routeDfds[i].resolve();
let routeId = m.route.id;
let { hasLoader, hasClientLoader } = getRouteInfo(m);
let defaultShouldRevalidate = !m.shouldRevalidateArgs || m.shouldRevalidateArgs.actionStatus == null || m.shouldRevalidateArgs.actionStatus < 400;
if (!m.shouldCallHandler(defaultShouldRevalidate)) {
foundOptOutRoute ||= m.shouldRevalidateArgs != null && hasLoader;
return;
}
if (shouldAllowOptOut(m) && hasClientLoader) {
if (hasLoader) foundOptOutRoute = true;
try {
results[routeId] = {
type: "data",
result: await handler(async () => {
let { data } = await fetchAndDecode(args, [routeId]);
return unwrapSingleFetchResult(data, routeId);
})
};
} catch (e) {
results[routeId] = {
type: "error",
result: e
};
}
return;
}
if (hasLoader) routesParams.add(routeId);
try {
results[routeId] = {
type: "data",
result: await handler(async () => {
return unwrapSingleFetchResult(await singleFetchDfd.promise, routeId);
})
};
} catch (e) {
results[routeId] = {
type: "error",
result: e
};
}
})));
await Promise.all(routeDfds.map((d) => d.promise));
if ((!router.state.initialized && router.state.navigation.state === "idle" || routesParams.size === 0) && !window.__reactRouterHdrActive) singleFetchDfd.resolve({ routes: {} });
else {
let targetRoutes = ssr && foundOptOutRoute && routesParams.size > 0 ? [...routesParams.keys()] : void 0;
try {
let data = await fetchAndDecode(args, targetRoutes);
singleFetchDfd.resolve(data.data);
} catch (e) {
singleFetchDfd.reject(e);
}
}
await resolvePromise;
await bubbleMiddlewareErrors(singleFetchDfd.promise, args.matches, routesParams, results);
return results;
}
async function bubbleMiddlewareErrors(singleFetchPromise, matches, routesParams, results) {
try {
let middlewareError;
let fetchedData = await singleFetchPromise;
if ("routes" in fetchedData) {
for (let match of matches) if (match.route.id in fetchedData.routes) {
let routeResult = fetchedData.routes[match.route.id];
if ("error" in routeResult) {
middlewareError = routeResult.error;
if (results[match.route.id]?.result == null) results[match.route.id] = {
type: "error",
result: middlewareError
};
break;
}
}
}
if (middlewareError !== void 0) Array.from(routesParams.values()).forEach((routeId) => {
if (results[routeId].result instanceof SingleFetchNoResultError) results[routeId].result = middlewareError;
});
} catch (e) {}
}
async function singleFetchLoaderFetcherStrategy(args, fetchAndDecode) {
let fetcherMatch = args.matches.find((m) => m.shouldCallHandler());
invariant(fetcherMatch, "No fetcher match found");
let routeId = fetcherMatch.route.id;
let result = await fetcherMatch.resolve(async (handler) => handler(async () => {
let { data } = await fetchAndDecode(args, [routeId]);
return unwrapSingleFetchResult(data, routeId);
}));
return { [fetcherMatch.route.id]: result };
}
function stripIndexParam(url) {
let indexValues = url.searchParams.getAll("index");
url.searchParams.delete("index");
let indexValuesToKeep = [];
for (let indexValue of indexValues) if (indexValue) indexValuesToKeep.push(indexValue);
for (let toKeep of indexValuesToKeep) url.searchParams.append("index", toKeep);
return url;
}
function singleFetchUrl(reqUrl, extension) {
let url = typeof reqUrl === "string" ? new URL(reqUrl, typeof window === "undefined" ? "server://singlefetch/" : window.location.origin) : reqUrl;
if (url.pathname.endsWith("/")) url.pathname = `${url.pathname}_.${extension}`;
else url.pathname = `${url.pathname}.${extension}`;
return url;
}
async function fetchAndDecodeViaTurboStream(args, targetRoutes) {
let { request } = args;
let url = singleFetchUrl(request.url, "data");
if (request.method === "GET") {
url = stripIndexParam(url);
if (targetRoutes) url.searchParams.set("_routes", targetRoutes.join(","));
}
let res = await fetch(url, await createRequestInit(request));
if (res.status >= 400 && !res.headers.has("X-Remix-Response")) throw new ErrorResponseImpl(res.status, res.statusText, await res.text());
if (res.status === 204 && res.headers.has("X-Remix-Redirect")) return {
status: 202,
data: { redirect: {
redirect: res.headers.get("X-Remix-Redirect"),
status: Number(res.headers.get("X-Remix-Status") || "302"),
revalidate: res.headers.get("X-Remix-Revalidate") === "true",
reload: res.headers.get("X-Remix-Reload-Document") === "true",
replace: res.headers.get("X-Remix-Replace") === "true"
} }
};
if (NO_BODY_STATUS_CODES.has(res.status)) {
let routes = {};
if (targetRoutes && request.method !== "GET") routes[targetRoutes[0]] = { data: void 0 };
return {
status: res.status,
data: { routes }
};
}
invariant(res.body, "No response body to decode");
try {
let decoded = await decodeViaTurboStream(res.body, window);
let data;
if (request.method === "GET") {
let typed = decoded.value;
if (SingleFetchRedirectSymbol in typed) data = { redirect: typed[SingleFetchRedirectSymbol] };
else data = { routes: typed };
} else {
let typed = decoded.value;
let routeId = targetRoutes?.[0];
invariant(routeId, "No routeId found for single fetch call decoding");
if ("redirect" in typed) data = { redirect: typed };
else data = { routes: { [routeId]: typed } };
}
return {
status: res.status,
data
};
} catch (e) {
throw new Error("Unable to decode turbo-stream response");
}
}
function decodeViaTurboStream(body, global) {
return decode(body, { plugins: [(type, ...rest) => {
if (type === "SanitizedError") {
let [name, message, stack] = rest;
let Constructor = Error;
if (name && SUPPORTED_ERROR_TYPES.includes(name) && name in global && typeof global[name] === "function") Constructor = global[name];
let error = new Constructor(message);
error.stack = stack;
return { value: error };
}
if (type === "ErrorResponse") {
let [data, status, statusText] = rest;
return { value: new ErrorResponseImpl(status, statusText, data) };
}
if (type === "SingleFetchRedirect") return { value: { [SingleFetchRedirectSymbol]: rest[0] } };
if (type === "SingleFetchClassInstance") return { value: rest[0] };
if (type === "SingleFetchFallback") return { value: void 0 };
}] });
}
function unwrapSingleFetchResult(result, routeId) {
if ("redirect" in result) {
let { redirect: location, revalidate, reload, replace, status } = result.redirect;
throw redirect(location, {
status,
headers: {
...revalidate ? { "X-Remix-Revalidate": "yes" } : null,
...reload ? { "X-Remix-Reload-Document": "yes" } : null,
...replace ? { "X-Remix-Replace": "yes" } : null
}
});
}
let routeResult = result.routes[routeId];
if (routeResult == null) throw new SingleFetchNoResultError(`No result found for routeId "${routeId}"`);
else if ("error" in routeResult) throw routeResult.error;
else if ("data" in routeResult) return routeResult.data;
else throw new Error(`Invalid response found for routeId "${routeId}"`);
}
function createDeferred() {
let resolve;
let reject;
let promise = new Promise((res, rej) => {
resolve = async (val) => {
res(val);
try {
await promise;
} catch (e) {}
};
reject = async (error) => {
rej(error);
try {
await promise;
} catch (e) {}
};
});
return {
promise,
resolve,
reject
};
}
//#endregion
export { NO_BODY_STATUS_CODES, SingleFetchRedirectSymbol, StreamTransfer, decodeViaTurboStream, getSingleFetchDataStrategyImpl, getTurboStreamSingleFetchDataStrategy, singleFetchUrl, stripIndexParam };
@@ -0,0 +1,29 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl } from "./router/utils.js";
//#region lib/errors.ts
const ERROR_DIGEST_BASE = "REACT_ROUTER_ERROR";
const ERROR_DIGEST_REDIRECT = "REDIRECT";
const ERROR_DIGEST_ROUTE_ERROR_RESPONSE = "ROUTE_ERROR_RESPONSE";
function decodeRedirectErrorDigest(digest) {
if (digest.startsWith(`${ERROR_DIGEST_BASE}:${ERROR_DIGEST_REDIRECT}:{`)) try {
let parsed = JSON.parse(digest.slice(28));
if (typeof parsed === "object" && parsed && typeof parsed.status === "number" && typeof parsed.statusText === "string" && typeof parsed.location === "string" && typeof parsed.reloadDocument === "boolean" && typeof parsed.replace === "boolean") return parsed;
} catch {}
}
function decodeRouteErrorResponseDigest(digest) {
if (digest.startsWith(`${ERROR_DIGEST_BASE}:${ERROR_DIGEST_ROUTE_ERROR_RESPONSE}:{`)) try {
let parsed = JSON.parse(digest.slice(40));
if (typeof parsed === "object" && parsed && typeof parsed.status === "number" && typeof parsed.statusText === "string") return new ErrorResponseImpl(parsed.status, parsed.statusText, parsed.data);
} catch {}
}
//#endregion
export { decodeRedirectErrorDigest, decodeRouteErrorResponseDigest };
@@ -0,0 +1,974 @@
import { Action, Location, Path, To } from "./router/history.js";
import { ParamParseKey, Params, PathMatch, PathPattern, RouteObject, UIMatch } from "./router/utils.js";
import { Blocker, BlockerFunction, NavigationStates, RelativeRoutingType, Router } from "./router/router.js";
import { NavigateOptions } from "./context.js";
import { RouteModules } from "./types/register.js";
import { GetActionData, GetLoaderData, SerializeFrom } from "./types/route-data.js";
import * as React$1 from "react";
//#region lib/hooks.d.ts
/**
* Resolves a URL against the current {@link Location}.
*
* @example
* import { useHref } from "react-router";
*
* function SomeComponent() {
* let href = useHref("some/where");
* // "/resolved/some/where"
* }
*
* @public
* @category Hooks
* @param to The path to resolve
* @param options Options
* @param options.relative Defaults to `"route"` so routing is relative to the
* route tree.
* Set to `"path"` to make relative routing operate against path segments.
* @returns The resolved href string
*/
declare function useHref(to: To, {
relative
}?: {
relative?: RelativeRoutingType;
}): string;
/**
* Returns `true` if this component is a descendant of a {@link Router}, useful
* to ensure a component is used within a {@link Router}.
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns Whether the component is within a {@link Router} context
*/
declare function useInRouterContext(): boolean;
/**
* Returns the current {@link Location}. This can be useful if you'd like to
* perform some side effect whenever it changes.
*
* @example
* import * as React from 'react'
* import { useLocation } from 'react-router'
*
* function SomeComponent() {
* let location = useLocation()
*
* React.useEffect(() => {
* // Google Analytics
* ga('send', 'pageview')
* }, [location]);
*
* return (
* // ...
* );
* }
*
* @public
* @category Hooks
* @returns The current {@link Location} object
*/
declare function useLocation(): Location;
/**
* Returns the current {@link Navigation} action which describes how the router
* came to the current {@link Location}, either by a pop, push, or replace on
* the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) stack.
*
* @public
* @category Hooks
* @returns The current {@link NavigationType} (`"POP"`, `"PUSH"`, or `"REPLACE"`)
*/
declare function useNavigationType(): Action;
/**
* Returns a {@link PathMatch} object if the given pattern matches the current URL.
* This is useful for components that need to know "active" state, e.g.
* {@link NavLink | `<NavLink>`}.
*
* @public
* @category Hooks
* @param pattern The pattern to match against the current {@link Location}
* @returns The path match object if the pattern matches, `null` otherwise
*/
declare function useMatch<Path extends string>(pattern: PathPattern<Path> | Path): PathMatch<ParamParseKey<Path>> | null;
/**
* The interface for the `navigate` function returned from {@link useNavigate}.
*/
interface NavigateFunction {
(to: To, options?: NavigateOptions): void | Promise<void>;
(delta: number): void | Promise<void>;
}
/**
* Returns a function that lets you navigate programmatically in the browser in
* response to user interactions or effects.
*
* It's often better to use {@link redirect} in [`action`](../../start/framework/route-module#action)/[`loader`](../../start/framework/route-module#loader)
* functions than this hook.
*
* The returned function signature is `navigate(to, options?)`/`navigate(delta)` where:
*
* * `to` can be a string path, a {@link To} object, or a number (delta)
* * `options` contains options for modifying the navigation
* * These options work in all modes (Framework, Data, and Declarative):
* * `relative`: `"route"` or `"path"` to control relative routing logic
* * `replace`: Replace the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) stack
* * `state`: Optional [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) to include with the new {@link Location}
* * These options only work in Framework and Data modes:
* * `flushSync`: Wrap the DOM updates in [`ReactDom.flushSync`](https://react.dev/reference/react-dom/flushSync)
* * `preventScrollReset`: Do not scroll back to the top of the page after navigation
* * `viewTransition`: Enable [`document.startViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) for this navigation
*
* @example
* import { useNavigate } from "react-router";
*
* function SomeComponent() {
* let navigate = useNavigate();
* return (
* <button onClick={() => navigate(-1)}>
* Go Back
* </button>
* );
* }
*
* @additionalExamples
* ### Navigate to another path
*
* ```tsx
* navigate("/some/route");
* navigate("/some/route?search=param");
* ```
*
* ### Navigate with a {@link To} object
*
* All properties are optional.
*
* ```tsx
* navigate(
* {
* pathname: "/some/route",
* search: "?search=param",
* hash: "#hash",
* },
* {
* state: { some: "state" },
* },
* );
* ```
*
* If you use `state`, that will be available on the {@link Location} object on
* the next page. Access it with `useLocation().state` (see {@link useLocation}).
*
* ### Navigate back or forward in the history stack
*
* ```tsx
* // back
* // often used to close modals
* navigate(-1);
*
* // forward
* // often used in a multistep wizard workflows
* navigate(1);
* ```
*
* Be cautious with `navigate(number)`. If your application can load up to a
* route that has a button that tries to navigate forward/back, there may not be
* a [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
* entry to go back or forward to, or it can go somewhere you don't expect
* (like a different domain).
*
* Only use this if you're sure they will have an entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
* stack to navigate to.
*
* ### Replace the current entry in the history stack
*
* This will remove the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
* stack, replacing it with a new one, similar to a server side redirect.
*
* ```tsx
* navigate("/some/route", { replace: true });
* ```
*
* ### Prevent Scroll Reset
*
* [MODES: framework, data]
*
* <br/>
* <br/>
*
* To prevent {@link ScrollRestoration | `<ScrollRestoration>`} from resetting
* the scroll position, use the `preventScrollReset` option.
*
* ```tsx
* navigate("?some-tab=1", { preventScrollReset: true });
* ```
*
* For example, if you have a tab interface connected to search params in the
* middle of a page, and you don't want it to scroll to the top when a tab is
* clicked.
*
* ### Return Type Augmentation
*
* Internally, `useNavigate` uses a separate implementation when you are in
* Declarative mode versus Data/Framework mode - the primary difference being
* that the latter is able to return a stable reference that does not change
* identity across navigations. The implementation in Data/Framework mode also
* returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
* that resolves when the navigation is completed. This means the return type of
* `useNavigate` is `void | Promise<void>`. This is accurate, but can lead to
* some red squigglies based on the union in the return value:
*
* - If you're using `typescript-eslint`, you may see errors from
* [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises)
* - In Framework/Data mode, `React.use(navigate())` will show a false-positive
* `Argument of type 'void | Promise<void>' is not assignable to parameter of
* type 'Usable<void>'` error
*
* The easiest way to work around these issues is to augment the type based on the
* router you're using:
*
* ```ts
* // If using <BrowserRouter>
* declare module "react-router" {
* interface NavigateFunction {
* (to: To, options?: NavigateOptions): void;
* (delta: number): void;
* }
* }
*
* // If using <RouterProvider> or Framework mode
* declare module "react-router" {
* interface NavigateFunction {
* (to: To, options?: NavigateOptions): Promise<void>;
* (delta: number): Promise<void>;
* }
* }
* ```
*
* @public
* @category Hooks
* @returns A navigate function for programmatic navigation
*/
declare function useNavigate(): NavigateFunction;
/**
* Returns the parent route {@link Outlet | `<Outlet context>`}.
*
* Often parent routes manage state or other values you want shared with child
* routes. You can create your own [context provider](https://react.dev/learn/passing-data-deeply-with-context)
* if you like, but this is such a common situation that it's built-into
* {@link Outlet | `<Outlet>`}.
*
* ```tsx
* // Parent route
* function Parent() {
* const [count, setCount] = React.useState(0);
* return <Outlet context={[count, setCount]} />;
* }
* ```
*
* ```tsx
* // Child route
* import { useOutletContext } from "react-router";
*
* function Child() {
* const [count, setCount] = useOutletContext();
* const increment = () => setCount((c) => c + 1);
* return <button onClick={increment}>{count}</button>;
* }
* ```
*
* If you're using TypeScript, we recommend the parent component provide a
* custom hook for accessing the context value. This makes it easier for
* consumers to get nice typings, control consumers, and know who's consuming
* the context value.
*
* Here's a more realistic example:
*
* ```tsx filename=src/routes/dashboard.tsx lines=[14,20]
* import { useState } from "react";
* import { Outlet, useOutletContext } from "react-router";
*
* import type { User } from "./types";
*
* type ContextType = { user: User | null };
*
* export default function Dashboard() {
* const [user, setUser] = useState<User | null>(null);
*
* return (
* <div>
* <h1>Dashboard</h1>
* <Outlet context={{ user } satisfies ContextType} />
* </div>
* );
* }
*
* export function useUser() {
* return useOutletContext<ContextType>();
* }
* ```
*
* ```tsx filename=src/routes/dashboard/messages.tsx lines=[1,4]
* import { useUser } from "../dashboard";
*
* export default function DashboardMessages() {
* const { user } = useUser();
* return (
* <div>
* <h2>Messages</h2>
* <p>Hello, {user.name}!</p>
* </div>
* );
* }
* ```
*
* @public
* @category Hooks
* @returns The context value passed to the parent {@link Outlet} component
*/
declare function useOutletContext<Context = unknown>(): Context;
/**
* Returns the element for the child route at this level of the route
* hierarchy. Used internally by {@link Outlet | `<Outlet>`} to render child
* routes.
*
* @public
* @category Hooks
* @param context The context to pass to the outlet
* @returns The child route element or `null` if no child routes match
*/
declare function useOutlet(context?: unknown): React$1.ReactElement | null;
/**
* Returns an object of key/value-pairs of the dynamic params from the current
* URL that were matched by the routes. Child routes inherit all params from
* their parent routes.
*
* Assuming a route pattern like `/posts/:postId` is matched by `/posts/123`
* then `params.postId` will be `"123"`.
*
* @example
* import { useParams } from "react-router";
*
* function SomeComponent() {
* let params = useParams();
* params.postId;
* }
*
* @additionalExamples
* ### Basic Usage
*
* ```tsx
* import { useParams } from "react-router";
*
* // given a route like:
* <Route path="/posts/:postId" element={<Post />} />;
*
* // or a data route like:
* createBrowserRouter([
* {
* path: "/posts/:postId",
* component: Post,
* },
* ]);
*
* // or in routes.ts
* route("/posts/:postId", "routes/post.tsx");
* ```
*
* Access the params in a component:
*
* ```tsx
* import { useParams } from "react-router";
*
* export default function Post() {
* let params = useParams();
* return <h1>Post: {params.postId}</h1>;
* }
* ```
*
* ### Multiple Params
*
* Patterns can have multiple params:
*
* ```tsx
* "/posts/:postId/comments/:commentId";
* ```
*
* All will be available in the params object:
*
* ```tsx
* import { useParams } from "react-router";
*
* export default function Post() {
* let params = useParams();
* return (
* <h1>
* Post: {params.postId}, Comment: {params.commentId}
* </h1>
* );
* }
* ```
*
* ### Catchall Params
*
* Catchall params are defined with `*`:
*
* ```tsx
* "/files/*";
* ```
*
* The matched value will be available in the params object as follows:
*
* ```tsx
* import { useParams } from "react-router";
*
* export default function File() {
* let params = useParams();
* let catchall = params["*"];
* // ...
* }
* ```
*
* You can destructure the catchall param:
*
* ```tsx
* export default function File() {
* let { "*": catchall } = useParams();
* console.log(catchall);
* }
* ```
*
* @public
* @category Hooks
* @returns An object containing the dynamic route parameters
*/
declare function useParams<ParamsOrKey extends string | Record<string, string | undefined> = string>(): Readonly<[ParamsOrKey] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey>>;
/**
* Resolves the pathname of the given `to` value against the current
* {@link Location}. Similar to {@link useHref}, but returns a
* {@link Path} instead of a string.
*
* @example
* import { useResolvedPath } from "react-router";
*
* function SomeComponent() {
* // if the user is at /dashboard/profile
* let path = useResolvedPath("../accounts");
* path.pathname; // "/dashboard/accounts"
* path.search; // ""
* path.hash; // ""
* }
*
* @public
* @category Hooks
* @param to The path to resolve
* @param options Options
* @param options.relative Defaults to `"route"` so routing is relative to the route tree.
* Set to `"path"` to make relative routing operate against path segments.
* @returns The resolved {@link Path} object with `pathname`, `search`, and `hash`
*/
declare function useResolvedPath(to: To, {
relative
}?: {
relative?: RelativeRoutingType;
}): Path;
/**
* Hook version of {@link Routes | `<Routes>`} that uses objects instead of
* components. These objects have the same properties as the component props.
* The return value of `useRoutes` is either a valid React element you can use
* to render the route tree, or `null` if nothing matched.
*
* @example
* import { useRoutes } from "react-router";
*
* function App() {
* let element = useRoutes([
* {
* path: "/",
* element: <Dashboard />,
* children: [
* {
* path: "messages",
* element: <DashboardMessages />,
* },
* { path: "tasks", element: <DashboardTasks /> },
* ],
* },
* { path: "team", element: <AboutPage /> },
* ]);
*
* return element;
* }
*
* @public
* @category Hooks
* @param routes An array of {@link RouteObject}s that define the route hierarchy
* @param locationArg An optional {@link Location} object or pathname string to
* use instead of the current {@link Location}
* @returns A React element to render the matched route, or `null` if no routes matched
*/
declare function useRoutes(routes: RouteObject[], locationArg?: Partial<Location> | string): React$1.ReactElement | null;
type UseNavigationResult = UseNavigationResultStates[keyof UseNavigationResultStates];
type UseNavigationResultStates = {
Idle: Omit<NavigationStates["Idle"], "matches" | "historyAction">;
Loading: Omit<NavigationStates["Loading"], "matches" | "historyAction">;
Submitting: Omit<NavigationStates["Submitting"], "matches" | "historyAction">;
};
/**
* Returns the current {@link Navigation}, defaulting to an "idle" navigation
* when no navigation is in progress. You can use this to render pending UI
* (like a global spinner) or read [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
* from a form navigation.
*
* @example
* import { useNavigation } from "react-router";
*
* function SomeComponent() {
* let navigation = useNavigation();
* navigation.state;
* navigation.formData;
* // etc.
* }
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The current {@link Navigation} object
*/
declare function useNavigation(): UseNavigationResult;
/**
* Revalidate the data on the page for reasons outside of normal data mutations
* like [`Window` focus](https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event)
* or polling on an interval.
*
* Note that page data is already revalidated automatically after actions.
* If you find yourself using this for normal CRUD operations on your data in
* response to user interactions, you're probably not taking advantage of the
* other APIs like {@link useFetcher}, {@link Form}, {@link useSubmit} that do
* this automatically.
*
* @example
* import { useRevalidator } from "react-router";
*
* function WindowFocusRevalidator() {
* const revalidator = useRevalidator();
*
* useFakeWindowFocus(() => {
* revalidator.revalidate();
* });
*
* return (
* <div hidden={revalidator.state === "idle"}>
* Revalidating...
* </div>
* );
* }
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns An object with a `revalidate` function and the current revalidation
* `state`
*/
declare function useRevalidator(): {
revalidate: () => Promise<void>;
state: Router["state"]["revalidation"];
};
/**
* Returns the active route matches, useful for accessing `loaderData` for
* parent/child routes or the route [`handle`](../../start/framework/route-module#handle)
* property
*
* Pairing the route `handle` with `useMatches` gets very powerful since you can put
* whatever you want on a route handle and have access to `useMatches` anywhere.
* Please see the [handle](../../how-to/using-handle) documentation for an example
* of breadcrumbs via `useMatches`/`handle`.
*
* ```tsx
* import { useMatches } from "react-router";
*
* function SomeComponent() {
* const matches = useMatches();
* // matches[i].id // route id
* // matches[i].pathname // the portion of the URL the route matched
* // matches[i].params // the parsed params from the URL
* // matches[i].loaderData // the data from the loader
* // matches[i].handle // the route handle with any app specific data
* }
* ```
*
* <docs-info>useMatches only works with a data router like `createBrowserRouter`,
* since they know the full route tree up front and can provide all of the current
* matches. Additionally, `useMatches` will not match down into any descendant route
* trees since the router isn't aware of the descendant routes.</docs-info>
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns An array of {@link UIMatch | UI matches} for the current route hierarchy
*/
declare function useMatches(): UIMatch[];
/**
* Returns the data from the closest route
* [`loader`](../../start/framework/route-module#loader) or
* [`clientLoader`](../../start/framework/route-module#clientloader).
*
* @example
* import { useLoaderData } from "react-router";
*
* export async function loader() {
* return await fakeDb.invoices.findAll();
* }
*
* export default function Invoices() {
* let invoices = useLoaderData<typeof loader>();
* // ...
* }
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The data returned from the route's [`loader`](../../start/framework/route-module#loader) or [`clientLoader`](../../start/framework/route-module#clientloader) function
*/
declare function useLoaderData<T = any>(): SerializeFrom<T>;
/**
* Returns the [`loader`](../../start/framework/route-module#loader) data for a
* given route by route ID.
*
* Route IDs are created automatically. They are simply the path of the route file
* relative to the app folder without the extension.
*
* | Route Filename | Route ID |
* | ---------------------------- | ---------------------- |
* | `app/root.tsx` | `"root"` |
* | `app/routes/teams.tsx` | `"routes/teams"` |
* | `app/whatever/teams.$id.tsx` | `"whatever/teams.$id"` |
*
* @example
* import { useRouteLoaderData } from "react-router";
*
* function SomeComponent() {
* const { user } = useRouteLoaderData("root");
* }
*
* // You can also specify your own route ID's manually in your routes.ts file:
* route("/", "containers/app.tsx", { id: "app" })
* useRouteLoaderData("app");
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @param routeId The ID of the route to return loader data from
* @returns The data returned from the specified route's [`loader`](../../start/framework/route-module#loader)
* function, or `undefined` if not found
*/
declare function useRouteLoaderData<T = any>(routeId: string): SerializeFrom<T> | undefined;
/**
* Returns the [`action`](../../start/framework/route-module#action) data from
* the most recent `POST` navigation form submission or `undefined` if there
* hasn't been one.
*
* @example
* import { Form, useActionData } from "react-router";
*
* export async function action({ request }) {
* const body = await request.formData();
* const name = body.get("visitorsName");
* return { message: `Hello, ${name}` };
* }
*
* export default function Invoices() {
* const data = useActionData();
* return (
* <Form method="post">
* <input type="text" name="visitorsName" />
* {data ? data.message : "Waiting..."}
* </Form>
* );
* }
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The data returned from the route's [`action`](../../start/framework/route-module#action)
* function, or `undefined` if no [`action`](../../start/framework/route-module#action)
* has been called
*/
declare function useActionData<T = any>(): SerializeFrom<T> | undefined;
/**
* Accesses the error thrown during an
* [`action`](../../start/framework/route-module#action),
* [`loader`](../../start/framework/route-module#loader),
* or component render to be used in a route module
* [`ErrorBoundary`](../../start/framework/route-module#errorboundary).
*
* @example
* export function ErrorBoundary() {
* const error = useRouteError();
* return <div>{error.message}</div>;
* }
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The error that was thrown during route [loading](../../start/framework/route-module#loader),
* [`action`](../../start/framework/route-module#action) execution, or rendering
*/
declare function useRouteError(): unknown;
/**
* Returns the resolved promise value from the closest {@link Await | `<Await>`}.
*
* @example
* function SomeDescendant() {
* const value = useAsyncValue();
* // ...
* }
*
* // somewhere in your app
* <Await resolve={somePromise}>
* <SomeDescendant />
* </Await>;
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The resolved value from the nearest {@link Await} component
*/
declare function useAsyncValue(): unknown;
/**
* Returns the rejection value from the closest {@link Await | `<Await>`}.
*
* @example
* import { Await, useAsyncError } from "react-router";
*
* function ErrorElement() {
* const error = useAsyncError();
* return (
* <p>Uh Oh, something went wrong! {error.message}</p>
* );
* }
*
* // somewhere in your app
* <Await
* resolve={promiseThatRejects}
* errorElement={<ErrorElement />}
* />;
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The error that was thrown in the nearest {@link Await} component
*/
declare function useAsyncError(): unknown;
/**
* Allow the application to block navigations within the SPA and present the
* user a confirmation dialog to confirm the navigation. Mostly used to avoid
* using half-filled form data. This does not handle hard-reloads or
* cross-origin navigations.
*
* The {@link Blocker} object returned by the hook has the following properties:
*
* - **`state`**
* - `unblocked` - the blocker is idle and has not prevented any navigation
* - `blocked` - the blocker has prevented a navigation
* - `proceeding` - the blocker is proceeding through from a blocked navigation
* - **`location`**
* - When in a `blocked` state, this represents the {@link Location} to which
* we blocked a navigation. When in a `proceeding` state, this is the
* location being navigated to after a `blocker.proceed()` call.
* - **`proceed()`**
* - When in a `blocked` state, you may call `blocker.proceed()` to proceed to
* the blocked location.
* - **`reset()`**
* - When in a `blocked` state, you may call `blocker.reset()` to return the
* blocker to an `unblocked` state and leave the user at the current
* location.
*
* @example
* // Boolean version
* let blocker = useBlocker(value !== "");
*
* // Function version
* let blocker = useBlocker(
* ({ currentLocation, nextLocation, historyAction }) =>
* value !== "" &&
* currentLocation.pathname !== nextLocation.pathname
* );
*
* @additionalExamples
* ```tsx
* import { useCallback, useState } from "react";
* import { BlockerFunction, useBlocker } from "react-router";
*
* export function ImportantForm() {
* const [value, setValue] = useState("");
*
* const shouldBlock = useCallback<BlockerFunction>(
* () => value !== "",
* [value]
* );
* const blocker = useBlocker(shouldBlock);
*
* return (
* <form
* onSubmit={(e) => {
* e.preventDefault();
* setValue("");
* if (blocker.state === "blocked") {
* blocker.proceed();
* }
* }}
* >
* <input
* name="data"
* value={value}
* onChange={(e) => setValue(e.target.value)}
* />
*
* <button type="submit">Save</button>
*
* {blocker.state === "blocked" ? (
* <>
* <p style={{ color: "red" }}>
* Blocked the last navigation to
* </p>
* <button
* type="button"
* onClick={() => blocker.proceed()}
* >
* Let me through
* </button>
* <button
* type="button"
* onClick={() => blocker.reset()}
* >
* Keep me here
* </button>
* </>
* ) : blocker.state === "proceeding" ? (
* <p style={{ color: "orange" }}>
* Proceeding through blocked navigation
* </p>
* ) : (
* <p style={{ color: "green" }}>
* Blocker is currently unblocked
* </p>
* )}
* </form>
* );
* }
* ```
*
* @public
* @category Hooks
* @mode framework
* @mode data
* @param shouldBlock Either a boolean or a function returning a boolean which
* indicates whether the navigation should be blocked. The function format
* receives a single object parameter containing the `currentLocation`,
* `nextLocation`, and `historyAction` of the potential navigation.
* @returns A {@link Blocker} object with state and reset functionality
*/
declare function useBlocker(shouldBlock: boolean | BlockerFunction): Blocker;
type UseRouteArgs = [] | [routeId: keyof RouteModules];
type UseRouteResult<Args extends UseRouteArgs> = Args extends [] ? UseRoute<unknown> : Args extends ["root"] ? UseRoute<"root"> : Args extends [infer RouteId extends keyof RouteModules] ? UseRoute<RouteId> | undefined : never;
type UseRoute<RouteId extends keyof RouteModules | unknown> = {
handle: RouteId extends keyof RouteModules ? RouteModules[RouteId] extends {
handle: infer handle;
} ? handle : unknown : unknown;
loaderData: RouteId extends keyof RouteModules ? GetLoaderData<RouteModules[RouteId]> | undefined : unknown;
actionData: RouteId extends keyof RouteModules ? GetActionData<RouteModules[RouteId]> | undefined : unknown;
};
declare function useRoute<Args extends UseRouteArgs>(...args: Args): UseRouteResult<Args>;
/**
* A single route match returned from {@link unstable_useRouterState}. Mirrors
* {@link UIMatch} minus the data-related fields (`data`, `loaderData`).
*/
type unstable_RouterStateMatch<Handle = unknown> = Omit<UIMatch<unknown, Handle>, "data" | "loaderData">;
/**
* The shape of the `active` variant returned from
* {@link unstable_useRouterState}.
*/
type unstable_RouterStateActiveVariant = {
location: Location;
searchParams: URLSearchParams;
params: Params;
matches: unstable_RouterStateMatch[];
type: Action;
};
/**
* The shape of the `pending` variant returned from
* {@link unstable_useRouterState}. Extends
* {@link unstable_RouterStateActiveVariant} with the navigation `state` and
* submission fields mirroring {@link useNavigation} — submission fields are
* populated when the in-flight navigation was triggered by a form submission,
* otherwise `undefined`.
*/
type unstable_RouterStatePendingVariant = unstable_RouterStatePendingVariants[keyof unstable_RouterStatePendingVariants];
type unstable_RouterStatePendingVariants = {
Loading: unstable_RouterStateActiveVariant & Omit<NavigationStates["Loading"], "matches" | "historyAction">;
Submitting: unstable_RouterStateActiveVariant & Omit<NavigationStates["Submitting"], "matches" | "historyAction">;
};
/**
* The return shape of {@link unstable_useRouterState}.
*
* `active` reflects the currently-committed location. `pending` reflects the
* in-flight navigation (if any).
*/
type unstable_RouterState = {
active: unstable_RouterStateActiveVariant;
pending: unstable_RouterStatePendingVariant | null;
};
/**
* A unified hook for reading router state: current (`active`) and in-flight
* (`pending`) locations, search params, params, matches, and navigation type.
*
* This hook consolidates the information you used to get from {@link useLocation},
* {@link useSearchParams}, {@link useParams}, {@link useMatches}, {@link useNavigation},
* and {@link useNavigationType} into a single hook.
*
*
* @example
* import { unstable_useRouterState as useRouterState } from "react-router";
*
* let { active, pending } = unstable_useRouterState();
*
* // Active is always populated with the current location
* active.location; // replaces `useLocation()`
* active.searchParams; // replaces `useSearchParams()[0]`
* active.params; // replaces `useParams()`
* active.matches; // replaces `useMatches()`
* active.type; // replaces `useNavigationType()`
*
* // Pending is only populated during a navigation
* pending.location; // replaces `useNavigation().location`
* pending.searchParams; // equivalent to `new URLSearchParams(useNavigation().search)`
* pending.params; // Not directly accessible today
* pending.matches; // Not directly accessible today
* pending.type; // Not directly accessible today
* pending.state; // replaces `useNavigation().state`
* pending.formMethod; // replaces useNavigation().formMethod
* pending.formAction; // replaces useNavigation().formAction
* pending.formEncType; // replaces useNavigation().formEncType
* pending.formData; // replaces useNavigation().formData
* pending.json; // replaces useNavigation().json
* pending.text; // replaces useNavigation().text
*
* @name unstable_useRouterState
* @public
* @category Hooks
* @mode framework
* @mode data
* @returns The current router state with `active` and `pending` variants
*/
declare function useRouterState(): unstable_RouterState;
//#endregion
export { NavigateFunction, unstable_RouterState, unstable_RouterStateActiveVariant, unstable_RouterStatePendingVariant, useActionData, useAsyncError, useAsyncValue, useBlocker, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRoute, useRouteError, useRouteLoaderData, useRouterState, useRoutes };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
import { Equal } from "./types/utils.js";
import { Pages } from "./types/register.js";
//#region lib/href.d.ts
type Args = { [K in keyof Pages]: ToArgs<Pages[K]["params"]> };
type ToArgs<Params extends Record<string, string | undefined>> = Equal<Params, {}> extends true ? [] : Partial<Params> extends Params ? [Params] | [] : [Params];
/**
* Returns a resolved URL path for the specified route.
*
* Param values are percent-encoded for use in a path segment: characters that
* would change the URL structure (`/`, `?`, `#`, `%`, whitespace, non-ASCII)
* are escaped, while characters that RFC 3986 allows literally in a path
* segment (`$ & + , ; = : @`) are kept as-is. Note this differs from query-string
* encoding (`encodeURIComponent`/`URLSearchParams`), where those characters are
* delimiters and must be escaped. Splat (`*`) values are encoded per segment,
* preserving `/` separators.
*
* See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
*
* @example
* const h = href("/:lang?/about", { lang: "en" })
* // -> `/en/about`
*
* <Link to={href("/products/:id", { id: "abc123" })} />
*
* @public
* @category Utils
* @mode framework
* @param path The route path to resolve
* @param args The route params to use when resolving the path
* @returns The resolved URL path
*/
declare function href<Path extends keyof Args>(path: Path, ...args: Args[Path]): string;
//#endregion
export { href };
@@ -0,0 +1,70 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { encodePathParam } from "./router/utils.js";
//#region lib/href.ts
function stringify(p) {
return p == null ? "" : typeof p === "string" ? p : String(p);
}
/**
* Returns a resolved URL path for the specified route.
*
* Param values are percent-encoded for use in a path segment: characters that
* would change the URL structure (`/`, `?`, `#`, `%`, whitespace, non-ASCII)
* are escaped, while characters that RFC 3986 allows literally in a path
* segment (`$ & + , ; = : @`) are kept as-is. Note this differs from query-string
* encoding (`encodeURIComponent`/`URLSearchParams`), where those characters are
* delimiters and must be escaped. Splat (`*`) values are encoded per segment,
* preserving `/` separators.
*
* See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
*
* @example
* const h = href("/:lang?/about", { lang: "en" })
* // -> `/en/about`
*
* <Link to={href("/products/:id", { id: "abc123" })} />
*
* @public
* @category Utils
* @mode framework
* @param path The route path to resolve
* @param args The route params to use when resolving the path
* @returns The resolved URL path
*/
function href(path, ...args) {
let params = args[0];
let result = trimTrailingSplat(path).replace(/\/:([\w-]+)(\?)?/g, (_, param, questionMark) => {
const isRequired = questionMark === void 0;
const value = params?.[param];
if (isRequired && value === void 0) throw new Error(`Path '${path}' requires param '${param}' but it was not provided`);
return value == null ? "" : "/" + encodePathParam(stringify(value));
});
if (path.endsWith("*")) {
const value = params?.["*"];
if (value !== void 0) result += "/" + stringify(value).split("/").map(encodePathParam).join("/");
}
return result || "/";
}
/**
* Removes a trailing splat and any number of slashes from the end of the path.
*
* Benchmarked to be faster than `path.replace(/\/*\*?$/, "")`, which backtracks.
*/
function trimTrailingSplat(path) {
let i = path.length - 1;
let char = path[i];
if (char !== "*" && char !== "/") return path;
i--;
for (; i >= 0; i--) if (path[i] !== "/") break;
return path.slice(0, i + 1);
}
//#endregion
export { href };
@@ -0,0 +1,264 @@
//#region lib/router/history.d.ts
/**
* Actions represent the type of change to a location value.
*/
declare enum Action {
/**
* A POP indicates a change to an arbitrary index in the history stack, such
* as a back or forward navigation. It does not describe the direction of the
* navigation, only that the current index changed.
*
* Note: This is the default action for newly created history objects.
*/
Pop = "POP",
/**
* A PUSH indicates a new entry being added to the history stack, such as when
* a link is clicked and a new page loads. When this happens, all subsequent
* entries in the stack are lost.
*/
Push = "PUSH",
/**
* A REPLACE indicates the entry at the current index in the history stack
* being replaced by a new one.
*/
Replace = "REPLACE"
}
/**
* The pathname, search, and hash values of a URL.
*/
interface Path {
/**
* A URL pathname, beginning with a /.
*/
pathname: string;
/**
* A URL search string, beginning with a ?.
*/
search: string;
/**
* A URL fragment identifier, beginning with a #.
*/
hash: string;
}
/**
* An entry in a history stack. A location contains information about the
* URL path, as well as possibly some arbitrary state and a key.
*/
interface Location<State = any> extends Path {
/**
* A value of arbitrary data associated with this location.
*/
state: State;
/**
* A unique string associated with this location. May be used to safely store
* and retrieve data in some other storage API, like `localStorage`.
*
* Note: This value is always "default" on the initial location.
*/
key: string;
/**
* The masked location displayed in the URL bar, which differs from the URL the
* router is operating on
*/
mask?: Path;
}
/**
* A change to the current location.
*/
interface Update {
/**
* The action that triggered the change.
*/
action: Action;
/**
* The new location.
*/
location: Location;
/**
* The delta between this location and the former location in the history stack
*/
delta: number | null;
}
/**
* A function that receives notifications about location changes.
*/
interface Listener {
(update: Update): void;
}
/**
* Describes a location that is the destination of some navigation used in
* {@link Link}, {@link useNavigate}, etc.
*/
type To = string | Partial<Path>;
/**
* A history is an interface to the navigation stack. The history serves as the
* source of truth for the current location, as well as provides a set of
* methods that may be used to change it.
*
* It is similar to the DOM's `window.history` object, but with a smaller, more
* focused API.
*/
interface History {
/**
* The last action that modified the current location. This will always be
* Action.Pop when a history instance is first created. This value is mutable.
*/
readonly action: Action;
/**
* The current location. This value is mutable.
*/
readonly location: Location;
/**
* Returns a valid href for the given `to` value that may be used as
* the value of an <a href> attribute.
*
* @param to - The destination URL
*/
createHref(to: To): string;
/**
* Returns a URL for the given `to` value
*
* @param to - The destination URL
*/
createURL(to: To): URL;
/**
* Encode a location the same way window.history would do (no-op for memory
* history) so we ensure our PUSH/REPLACE navigations for data routers
* behave the same as POP
*
* @param to Unencoded path
*/
encodeLocation(to: To): Path;
/**
* Pushes a new location onto the history stack, increasing its length by one.
* If there were any entries in the stack after the current one, they are
* lost.
*
* @param to - The new URL
* @param state - Data to associate with the new location
*/
push(to: To, state?: any): void;
/**
* Replaces the current location in the history stack with a new one. The
* location that was replaced will no longer be available.
*
* @param to - The new URL
* @param state - Data to associate with the new location
*/
replace(to: To, state?: any): void;
/**
* Navigates `n` entries backward/forward in the history stack relative to the
* current index. For example, a "back" navigation would use go(-1).
*
* @param delta - The delta in the stack index
*/
go(delta: number): void;
/**
* Sets up a listener that will be called whenever the current location
* changes.
*
* @param listener - A function that will be called when the location changes
* @returns unlisten - A function that may be used to stop listening
*/
listen(listener: Listener): () => void;
}
/**
* A user-supplied object that describes a location. Used when providing
* entries to `createMemoryHistory` via its `initialEntries` option.
*/
type InitialEntry = string | Partial<Location>;
type MemoryHistoryOptions = {
initialEntries?: InitialEntry[];
initialIndex?: number;
v5Compat?: boolean;
};
/**
* A memory history stores locations in memory. This is useful in stateful
* environments where there is no web browser, such as node tests or React
* Native.
*/
interface MemoryHistory extends History {
/**
* The current index in the history stack.
*/
readonly index: number;
}
/**
* Memory history stores the current location in memory. It is designed for use
* in stateful non-browser environments like tests and React Native.
*/
declare function createMemoryHistory(options?: MemoryHistoryOptions): MemoryHistory;
/**
* A browser history stores the current location in regular URLs in a web
* browser environment. This is the standard for most web apps and provides the
* cleanest URLs the browser's address bar.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
*/
interface BrowserHistory extends UrlHistory {}
type BrowserHistoryOptions = UrlHistoryOptions;
/**
* Browser history stores the location in regular URLs. This is the standard for
* most web apps, but it requires some configuration on the server to ensure you
* serve the same app at multiple URLs.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
*/
declare function createBrowserHistory(options?: BrowserHistoryOptions): BrowserHistory;
/**
* A hash history stores the current location in the fragment identifier portion
* of the URL in a web browser environment.
*
* This is ideal for apps that do not control the server for some reason
* (because the fragment identifier is never sent to the server), including some
* shared hosting environments that do not provide fine-grained controls over
* which pages are served at which URLs.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
*/
interface HashHistory extends UrlHistory {}
type HashHistoryOptions = UrlHistoryOptions;
/**
* Hash history stores the location in window.location.hash. This makes it ideal
* for situations where you don't want to send the location to the server for
* some reason, either because you do cannot configure it or the URL space is
* reserved for something else.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
*/
declare function createHashHistory(options?: HashHistoryOptions): HashHistory;
/**
* @private
*/
declare function invariant(value: boolean, message?: string): asserts value;
declare function invariant<T>(value: T | null | undefined, message?: string): asserts value is T;
/**
* Creates a string URL path from the given pathname, search, and hash components.
*
* @public
* @category Utils
* @param path The pathname, search, and hash components to combine.
* @returns The combined URL path.
*/
declare function createPath({
pathname,
search,
hash
}: Partial<Path>): string;
/**
* Parses a string URL path into its separate pathname, search, and hash components.
*
* @public
* @category Utils
* @param path The URL path to parse.
* @returns The parsed pathname, search, and hash components.
*/
declare function parsePath(path: string): Partial<Path>;
interface UrlHistory extends History {}
type UrlHistoryOptions = {
window?: Window;
v5Compat?: boolean;
};
//#endregion
export { Action, History, InitialEntry, Location, Path, To, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, invariant, parsePath };
@@ -0,0 +1,378 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { PROTOCOL_RELATIVE_URL_REGEX } from "./url.js";
//#region lib/router/history.ts
/**
* Actions represent the type of change to a location value.
*/
let Action = /* @__PURE__ */ function(Action) {
/**
* A POP indicates a change to an arbitrary index in the history stack, such
* as a back or forward navigation. It does not describe the direction of the
* navigation, only that the current index changed.
*
* Note: This is the default action for newly created history objects.
*/
Action["Pop"] = "POP";
/**
* A PUSH indicates a new entry being added to the history stack, such as when
* a link is clicked and a new page loads. When this happens, all subsequent
* entries in the stack are lost.
*/
Action["Push"] = "PUSH";
/**
* A REPLACE indicates the entry at the current index in the history stack
* being replaced by a new one.
*/
Action["Replace"] = "REPLACE";
return Action;
}({});
const PopStateEventType = "popstate";
function isLocation(obj) {
return typeof obj === "object" && obj != null && "pathname" in obj && "search" in obj && "hash" in obj && "state" in obj && "key" in obj;
}
/**
* Memory history stores the current location in memory. It is designed for use
* in stateful non-browser environments like tests and React Native.
*/
function createMemoryHistory(options = {}) {
let { initialEntries = ["/"], initialIndex, v5Compat = false } = options;
let entries;
entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : void 0, typeof entry === "string" ? void 0 : entry.mask));
let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
let action = "POP";
let listener = null;
function clampIndex(n) {
return Math.min(Math.max(n, 0), entries.length - 1);
}
function getCurrentLocation() {
return entries[index];
}
function createMemoryLocation(to, state = null, key, mask) {
let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key, mask);
warning(location.pathname.charAt(0) === "/", `relative pathnames are not supported in memory history: ${JSON.stringify(to)}`);
return location;
}
function createHref(to) {
return typeof to === "string" ? to : createPath(to);
}
return {
get index() {
return index;
},
get action() {
return action;
},
get location() {
return getCurrentLocation();
},
createHref,
createURL(to) {
return new URL(createHref(to), "http://localhost");
},
encodeLocation(to) {
let path = typeof to === "string" ? parsePath(to) : to;
return {
pathname: path.pathname || "",
search: path.search || "",
hash: path.hash || ""
};
},
push(to, state) {
action = "PUSH";
let nextLocation = isLocation(to) ? to : createMemoryLocation(to, state);
index += 1;
entries.splice(index, entries.length, nextLocation);
if (v5Compat && listener) listener({
action,
location: nextLocation,
delta: 1
});
},
replace(to, state) {
action = "REPLACE";
let nextLocation = isLocation(to) ? to : createMemoryLocation(to, state);
entries[index] = nextLocation;
if (v5Compat && listener) listener({
action,
location: nextLocation,
delta: 0
});
},
go(delta) {
action = "POP";
let nextIndex = clampIndex(index + delta);
let nextLocation = entries[nextIndex];
index = nextIndex;
if (listener) listener({
action,
location: nextLocation,
delta
});
},
listen(fn) {
listener = fn;
return () => {
listener = null;
};
}
};
}
/**
* Browser history stores the location in regular URLs. This is the standard for
* most web apps, but it requires some configuration on the server to ensure you
* serve the same app at multiple URLs.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
*/
function createBrowserHistory(options = {}) {
function createBrowserLocation(window, globalHistory) {
let maskedLocation = globalHistory.state?.masked;
let { pathname, search, hash } = maskedLocation || window.location;
return createLocation("", {
pathname,
search,
hash
}, globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default", maskedLocation ? {
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash
} : void 0);
}
function createBrowserHref(window, to) {
return typeof to === "string" ? to : createPath(to);
}
return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
}
/**
* Hash history stores the location in window.location.hash. This makes it ideal
* for situations where you don't want to send the location to the server for
* some reason, either because you do cannot configure it or the URL space is
* reserved for something else.
*
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
*/
function createHashHistory(options = {}) {
function createHashLocation(window, globalHistory) {
let { pathname = "/", search = "", hash = "" } = parsePath(window.location.hash.substring(1));
if (!pathname.startsWith("/") && !pathname.startsWith(".")) pathname = "/" + pathname;
return createLocation("", {
pathname,
search,
hash
}, globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
}
function createHashHref(window, to) {
let base = window.document.querySelector("base");
let href = "";
if (base && base.getAttribute("href")) {
let url = window.location.href;
let hashIndex = url.indexOf("#");
href = hashIndex === -1 ? url : url.slice(0, hashIndex);
}
return href + "#" + (typeof to === "string" ? to : createPath(to));
}
function validateHashLocation(location, to) {
warning(location.pathname.charAt(0) === "/", `relative pathnames are not supported in hash history.push(${JSON.stringify(to)})`);
}
return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
}
function invariant(value, message) {
if (value === false || value === null || typeof value === "undefined") throw new Error(message);
}
function warning(cond, message) {
if (!cond) {
if (typeof console !== "undefined") console.warn(message);
try {
throw new Error(message);
} catch (e) {}
}
}
function createKey() {
return Math.random().toString(36).substring(2, 10);
}
/**
* For browser-based histories, we combine the state and key into an object
*/
function getHistoryState(location, index) {
return {
usr: location.state,
key: location.key,
idx: index,
masked: location.mask ? {
pathname: location.pathname,
search: location.search,
hash: location.hash
} : void 0
};
}
/**
* Creates a Location object with a unique key from the given Path
*/
function createLocation(current, to, state = null, key, mask) {
return {
pathname: typeof current === "string" ? current : current.pathname,
search: "",
hash: "",
...typeof to === "string" ? parsePath(to) : to,
state,
key: to && to.key || key || createKey(),
mask
};
}
/**
* Creates a string URL path from the given pathname, search, and hash components.
*
* @public
* @category Utils
* @param path The pathname, search, and hash components to combine.
* @returns The combined URL path.
*/
function createPath({ pathname = "/", search = "", hash = "" }) {
if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
return pathname;
}
/**
* Parses a string URL path into its separate pathname, search, and hash components.
*
* @public
* @category Utils
* @param path The URL path to parse.
* @returns The parsed pathname, search, and hash components.
*/
function parsePath(path) {
let parsedPath = {};
if (path) {
let hashIndex = path.indexOf("#");
if (hashIndex >= 0) {
parsedPath.hash = path.substring(hashIndex);
path = path.substring(0, hashIndex);
}
let searchIndex = path.indexOf("?");
if (searchIndex >= 0) {
parsedPath.search = path.substring(searchIndex);
path = path.substring(0, searchIndex);
}
if (path) parsedPath.pathname = path;
}
return parsedPath;
}
function getUrlBasedHistory(getLocation, createHref, validateLocation, options = {}) {
let { window = document.defaultView, v5Compat = false } = options;
let globalHistory = window.history;
let action = "POP";
let listener = null;
let index = getIndex();
if (index == null) {
index = 0;
globalHistory.replaceState({
...globalHistory.state,
idx: index
}, "");
}
function getIndex() {
return (globalHistory.state || { idx: null }).idx;
}
function handlePop() {
action = "POP";
let nextIndex = getIndex();
let delta = nextIndex == null ? null : nextIndex - index;
index = nextIndex;
if (listener) listener({
action,
location: history.location,
delta
});
}
function push(to, state) {
action = "PUSH";
let location = isLocation(to) ? to : createLocation(history.location, to, state);
if (validateLocation) validateLocation(location, to);
index = getIndex() + 1;
let historyState = getHistoryState(location, index);
let url = history.createHref(location.mask || location);
try {
globalHistory.pushState(historyState, "", url);
} catch (error) {
if (error instanceof DOMException && error.name === "DataCloneError") throw error;
window.location.assign(url);
}
if (v5Compat && listener) listener({
action,
location: history.location,
delta: 1
});
}
function replace(to, state) {
action = "REPLACE";
let location = isLocation(to) ? to : createLocation(history.location, to, state);
if (validateLocation) validateLocation(location, to);
index = getIndex();
let historyState = getHistoryState(location, index);
let url = history.createHref(location.mask || location);
globalHistory.replaceState(historyState, "", url);
if (v5Compat && listener) listener({
action,
location: history.location,
delta: 0
});
}
function createURL(to) {
return createBrowserURLImpl(window, to);
}
let history = {
get action() {
return action;
},
get location() {
return getLocation(window, globalHistory);
},
listen(fn) {
if (listener) throw new Error("A history only accepts one active listener");
window.addEventListener(PopStateEventType, handlePop);
listener = fn;
return () => {
window.removeEventListener(PopStateEventType, handlePop);
listener = null;
};
},
createHref(to) {
return createHref(window, to);
},
createURL,
encodeLocation(to) {
let url = createURL(to);
return {
pathname: url.pathname,
search: url.search,
hash: url.hash
};
},
push,
replace,
go(n) {
return globalHistory.go(n);
}
};
return history;
}
function createBrowserURLImpl(windowImpl, to, isAbsolute = false) {
let base = "http://localhost";
if (windowImpl) base = windowImpl.location.origin !== "null" ? windowImpl.location.origin : windowImpl.location.href;
invariant(base, "No window.location.(origin|href) available to create URL");
let href = typeof to === "string" ? to : createPath(to);
href = href.replace(/ $/, "%20");
if (!isAbsolute && PROTOCOL_RELATIVE_URL_REGEX.test(href)) href = base + href;
return new URL(href, base);
}
//#endregion
export { Action, createBrowserHistory, createBrowserURLImpl, createHashHistory, createLocation, createMemoryHistory, createPath, invariant, parsePath, warning };
@@ -0,0 +1,111 @@
import { FormEncType, HTMLFormMethod, LoaderFunctionArgs, RouterContextProvider } from "./utils.js";
//#region lib/router/instrumentation.d.ts
type ServerInstrumentation = {
handler?: InstrumentRequestHandlerFunction;
route?: InstrumentRouteFunction;
};
type ClientInstrumentation = {
router?: InstrumentRouterFunction;
route?: InstrumentRouteFunction;
};
type InstrumentRequestHandlerFunction = (handler: InstrumentableRequestHandler) => void;
type InstrumentRouterFunction = (router: InstrumentableRouter) => void;
type InstrumentRouteFunction = (route: InstrumentableRoute) => void;
/**
* Route metadata available after React Router has matched an instrumented
* request, navigation, or fetcher call.
*/
type InstrumentationResultMeta = {
url: LoaderFunctionArgs["url"];
pattern: string;
params: LoaderFunctionArgs["params"];
};
/**
* Result returned by route-level instrumented handler calls, such as
* instrumented loaders, actions, middleware, and lazy route functions.
*/
type InstrumentationHandlerResult = {
status: "success";
error: undefined;
} | {
status: "error";
error: Error;
};
/**
* Result returned by client-side router instrumented navigation and fetcher
* calls.
*/
type InstrumentationClientRouterResult = InstrumentationHandlerResult & {
meta: InstrumentationResultMeta | undefined;
};
/**
* Result returned by server request handler instrumentation.
*/
type InstrumentationServerHandlerResult = InstrumentationHandlerResult & {
statusCode: number;
meta: InstrumentationResultMeta | undefined;
};
type InstrumentFunction<T, TInnerResult = InstrumentationHandlerResult> = (handler: () => Promise<TInnerResult>, info: T) => Promise<void>;
type ReadonlyRequest = {
method: string;
url: string;
headers: Pick<Headers, "get">;
};
type ReadonlyContext = Pick<RouterContextProvider, "get">;
type InstrumentableRoute = {
id: string;
index: boolean | undefined;
path: string | undefined;
instrument(instrumentations: RouteInstrumentations): void;
};
type RouteInstrumentations = {
lazy?: InstrumentFunction<RouteLazyInstrumentationInfo>;
"lazy.loader"?: InstrumentFunction<RouteLazyInstrumentationInfo>;
"lazy.action"?: InstrumentFunction<RouteLazyInstrumentationInfo>;
"lazy.middleware"?: InstrumentFunction<RouteLazyInstrumentationInfo>;
middleware?: InstrumentFunction<RouteHandlerInstrumentationInfo>;
loader?: InstrumentFunction<RouteHandlerInstrumentationInfo>;
action?: InstrumentFunction<RouteHandlerInstrumentationInfo>;
};
type RouteLazyInstrumentationInfo = undefined;
type RouteHandlerInstrumentationInfo = Readonly<Omit<LoaderFunctionArgs, "request" | "context"> & {
request: ReadonlyRequest;
context: ReadonlyContext;
}>;
type InstrumentableRouter = {
instrument(instrumentations: RouterInstrumentations): void;
};
type RouterInstrumentations = {
navigate?: InstrumentFunction<RouterNavigationInstrumentationInfo, InstrumentationClientRouterResult>;
fetch?: InstrumentFunction<RouterFetchInstrumentationInfo, InstrumentationClientRouterResult>;
};
type RouterNavigationInstrumentationInfo = Readonly<{
to: string | number;
currentUrl: string;
formMethod?: HTMLFormMethod;
formEncType?: FormEncType;
formData?: FormData;
body?: any;
}>;
type RouterFetchInstrumentationInfo = Readonly<{
href: string;
currentUrl: string;
fetcherKey: string;
formMethod?: HTMLFormMethod;
formEncType?: FormEncType;
formData?: FormData;
body?: any;
}>;
type InstrumentableRequestHandler = {
instrument(instrumentations: RequestHandlerInstrumentations): void;
};
type RequestHandlerInstrumentations = {
request?: InstrumentFunction<RequestHandlerInstrumentationInfo, InstrumentationServerHandlerResult>;
};
type RequestHandlerInstrumentationInfo = Readonly<{
request: ReadonlyRequest;
context: ReadonlyContext | undefined;
}>;
//#endregion
export { ClientInstrumentation, InstrumentRequestHandlerFunction, InstrumentRouteFunction, InstrumentRouterFunction, InstrumentationClientRouterResult, InstrumentationHandlerResult, InstrumentationServerHandlerResult, ServerInstrumentation };
@@ -0,0 +1,290 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createPath, invariant } from "./history.js";
import { RouterContextProvider, createContext } from "./utils.js";
//#region lib/router/instrumentation.ts
const UninstrumentedSymbol = Symbol("Uninstrumented");
const instrumentationResultMetaContext = createContext();
let instrumentationClientResultMetaReceivers = /* @__PURE__ */ new WeakMap();
function getRouteInstrumentationUpdates(fns, route) {
let aggregated = {
lazy: [],
"lazy.loader": [],
"lazy.action": [],
"lazy.middleware": [],
middleware: [],
loader: [],
action: []
};
fns.forEach((fn) => fn({
id: route.id,
index: route.index,
path: route.path,
instrument(i) {
if (i.lazy != null) aggregated.lazy.push(i.lazy);
if (i["lazy.loader"] != null) aggregated["lazy.loader"].push(i["lazy.loader"]);
if (i["lazy.action"] != null) aggregated["lazy.action"].push(i["lazy.action"]);
if (i["lazy.middleware"] != null) aggregated["lazy.middleware"].push(i["lazy.middleware"]);
if (i.middleware != null) aggregated.middleware.push(i.middleware);
if (i.loader != null) aggregated.loader.push(i.loader);
if (i.action != null) aggregated.action.push(i.action);
}
}));
let updates = {};
if (typeof route.lazy === "function" && aggregated.lazy.length > 0) {
let lazy = route.lazy;
updates.lazy = async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated.lazy, void 0, () => lazy(...args), getInstrumentationInnerResult));
};
}
if (typeof route.lazy === "object") {
let lazyObject = route.lazy;
if (typeof lazyObject.middleware === "function" && aggregated["lazy.middleware"].length > 0) {
let middleware = lazyObject.middleware;
updates.lazy = Object.assign(updates.lazy || {}, { middleware: async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated["lazy.middleware"], void 0, () => middleware(...args), getInstrumentationInnerResult));
} });
}
if (typeof lazyObject.loader === "function" && aggregated["lazy.loader"].length > 0) {
let loader = lazyObject.loader;
updates.lazy = Object.assign(updates.lazy || {}, { loader: async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated["lazy.loader"], void 0, () => loader(...args), getInstrumentationInnerResult));
} });
}
if (typeof lazyObject.action === "function" && aggregated["lazy.action"].length > 0) {
let action = lazyObject.action;
updates.lazy = Object.assign(updates.lazy || {}, { action: async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated["lazy.action"], void 0, () => action(...args), getInstrumentationInnerResult));
} });
}
}
if (typeof route.loader === "function" && aggregated.loader.length > 0) {
let original = getUninstrumentedHandler(route.loader);
let instrumented = async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated.loader, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
};
if (original.hydrate === true) instrumented.hydrate = true;
setUninstrumentedHandler(instrumented, original);
updates.loader = instrumented;
}
if (typeof route.action === "function" && aggregated.action.length > 0) {
let original = getUninstrumentedHandler(route.action);
let instrumented = async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated.action, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
};
setUninstrumentedHandler(instrumented, original);
updates.action = instrumented;
}
if (route.middleware && route.middleware.length > 0 && aggregated.middleware.length > 0) updates.middleware = route.middleware.map((middleware) => {
let original = getUninstrumentedHandler(middleware);
let instrumented = async (...args) => {
return throwOrReturnResult(await recurseRight(aggregated.middleware, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
};
setUninstrumentedHandler(instrumented, original);
return instrumented;
});
return updates;
}
function instrumentClientSideRouter(router, fns) {
let aggregated = {
navigate: [],
fetch: []
};
fns.forEach((fn) => fn({ instrument(i) {
if (i.navigate != null) aggregated.navigate.push(i.navigate);
if (i.fetch != null) aggregated.fetch.push(i.fetch);
} }));
if (aggregated.navigate.length > 0) {
let navigate = getUninstrumentedHandler(router.navigate);
let instrumentedNavigate = async (...args) => {
let [to, opts] = args;
let meta;
let info = {
to: typeof to === "number" || typeof to === "string" ? to : to ? createPath(to) : ".",
...getRouterInfo(router, opts ?? {})
};
return throwOrReturnResult(await recurseRight(aggregated.navigate, info, async () => {
if (typeof to === "number") return await navigate(...args);
let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
meta = value;
});
try {
return await navigate(...args);
} finally {
cleanup();
}
}, (result) => ({
...getInstrumentationInnerResult(result),
meta
})));
};
setUninstrumentedHandler(instrumentedNavigate, navigate);
router.navigate = instrumentedNavigate;
}
if (aggregated.fetch.length > 0) {
let fetch = getUninstrumentedHandler(router.fetch);
let instrumentedFetch = async (...args) => {
let [key, _, href, opts] = args;
let meta;
return throwOrReturnResult(await recurseRight(aggregated.fetch, {
href: href ?? ".",
fetcherKey: key,
...getRouterInfo(router, opts ?? {})
}, async () => {
let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
meta = value;
});
try {
return await fetch(...args);
} finally {
cleanup();
}
}, (result) => ({
...getInstrumentationInnerResult(result),
meta
})));
};
setUninstrumentedHandler(instrumentedFetch, fetch);
router.fetch = instrumentedFetch;
}
return router;
}
function instrumentHandler(handler, fns) {
let aggregated = { request: [] };
fns.forEach((fn) => fn({ instrument(i) {
if (i.request != null) aggregated.request.push(i.request);
} }));
let instrumentedHandler = handler;
if (aggregated.request.length > 0) instrumentedHandler = async (...args) => {
let [request, context] = args;
let instrumentationContext = context ?? new RouterContextProvider();
return throwOrReturnResult(await recurseRight(aggregated.request, {
request: getReadonlyRequest(request),
context: getReadonlyContext(instrumentationContext)
}, () => handler(request, instrumentationContext), (result, info) => {
let meta;
try {
meta = info.context?.get(instrumentationResultMetaContext);
} catch {}
invariant(result.value instanceof Response, "Expected a Response from the request handler");
return {
...getInstrumentationInnerResult(result),
statusCode: result.value.status,
meta
};
}));
};
return instrumentedHandler;
}
function getUninstrumentedHandler(handler) {
return handler[UninstrumentedSymbol] ?? handler;
}
function setUninstrumentedHandler(handler, uninstrumentedHandler) {
handler[UninstrumentedSymbol] = uninstrumentedHandler;
}
function setInstrumentationClientResultMetaReceiver(router, receiver) {
instrumentationClientResultMetaReceivers.set(router, receiver);
return () => {
if (instrumentationClientResultMetaReceivers.get(router) === receiver) instrumentationClientResultMetaReceivers.delete(router);
};
}
function consumeInstrumentationClientResultMetaReceiver(router) {
let receiver = instrumentationClientResultMetaReceivers.get(router);
instrumentationClientResultMetaReceivers.delete(router);
return receiver;
}
function throwOrReturnResult(result) {
if (result.type === "error") throw result.value;
return result.value;
}
async function recurseRight(impls, info, handler, getInnerResult, state = {
result: null,
innerResult: null
}, index = impls.length - 1) {
let impl = impls[index];
if (!impl) {
try {
state.result = {
type: "success",
value: await handler()
};
} catch (e) {
state.result = {
type: "error",
value: e
};
}
state.innerResult = getInnerResult(state.result, info);
} else {
let handlerPromise = void 0;
let callHandler = async () => {
if (handlerPromise) console.error("You cannot call instrumented handlers more than once");
else handlerPromise = recurseRight(impls, info, handler, getInnerResult, state, index - 1);
await handlerPromise;
invariant(state.innerResult, "Expected an inner result");
return state.innerResult;
};
try {
await impl(callHandler, info);
} catch (e) {
console.error("An instrumentation function threw an error:", e);
}
if (!handlerPromise) await callHandler();
await handlerPromise;
}
if (state.result) return state.result;
state.result = {
type: "error",
value: /* @__PURE__ */ new Error("No result assigned in instrumentation chain.")
};
state.innerResult = getInnerResult(state.result, info);
return state.result;
}
function getInstrumentationInnerResult(result) {
if (result.type === "error" && result.value instanceof Error) return {
status: "error",
error: result.value
};
return {
status: "success",
error: void 0
};
}
function getHandlerInfo(args) {
let { request, context, params } = args;
return {
...args,
request: getReadonlyRequest(request),
params: { ...params },
context: getReadonlyContext(context)
};
}
function getRouterInfo(router, opts) {
return {
currentUrl: createPath(router.state.location),
..."formMethod" in opts ? { formMethod: opts.formMethod } : {},
..."formEncType" in opts ? { formEncType: opts.formEncType } : {},
..."formData" in opts ? { formData: opts.formData } : {},
..."body" in opts ? { body: opts.body } : {}
};
}
function getReadonlyRequest(request) {
return {
method: request.method,
url: request.url,
headers: { get: (...args) => request.headers.get(...args) }
};
}
function getReadonlyContext(context) {
return { get: (ctx) => context.get(ctx) };
}
//#endregion
export { consumeInstrumentationClientResultMetaReceiver, getRouteInstrumentationUpdates, instrumentClientSideRouter, instrumentHandler, instrumentationResultMetaContext };
@@ -0,0 +1,114 @@
//#region lib/router/links.d.ts
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>);
interface HtmlLinkProps {
/**
* Address of the hyperlink
*/
href?: string;
/**
* How the element handles crossorigin requests
*/
crossOrigin?: "anonymous" | "use-credentials";
/**
* Relationship between the document containing the hyperlink and the destination resource
*/
rel: LiteralUnion<"alternate" | "dns-prefetch" | "icon" | "manifest" | "modulepreload" | "next" | "pingback" | "preconnect" | "prefetch" | "preload" | "prerender" | "search" | "stylesheet", string>;
/**
* Applicable media: "screen", "print", "(max-width: 764px)"
*/
media?: string;
/**
* Integrity metadata used in Subresource Integrity checks
*/
integrity?: string;
/**
* Language of the linked resource
*/
hrefLang?: string;
/**
* Hint for the type of the referenced resource
*/
type?: string;
/**
* Referrer policy for fetches initiated by the element
*/
referrerPolicy?: "" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url";
/**
* Sizes of the icons (for rel="icon")
*/
sizes?: string;
/**
* Potential destination for a preload request (for rel="preload" and rel="modulepreload")
*/
as?: LiteralUnion<"audio" | "audioworklet" | "document" | "embed" | "fetch" | "font" | "frame" | "iframe" | "image" | "manifest" | "object" | "paintworklet" | "report" | "script" | "serviceworker" | "sharedworker" | "style" | "track" | "video" | "worker" | "xslt", string>;
/**
* Color to use when customizing a site's icon (for rel="mask-icon")
*/
color?: string;
/**
* Whether the link is disabled
*/
disabled?: boolean;
/**
* The title attribute has special semantics on this element: Title of the link; CSS style sheet set name.
*/
title?: string;
/**
* Images to use in different situations, e.g., high-resolution displays,
* small monitors, etc. (for rel="preload")
*/
imageSrcSet?: string;
/**
* Image sizes for different page layouts (for rel="preload")
*/
imageSizes?: string;
}
interface HtmlLinkPreloadImage extends HtmlLinkProps {
/**
* Relationship between the document containing the hyperlink and the destination resource
*/
rel: "preload";
/**
* Potential destination for a preload request (for rel="preload" and rel="modulepreload")
*/
as: "image";
/**
* Address of the hyperlink
*/
href?: string;
/**
* Images to use in different situations, e.g., high-resolution displays,
* small monitors, etc. (for rel="preload")
*/
imageSrcSet: string;
/**
* Image sizes for different page layouts (for rel="preload")
*/
imageSizes?: string;
}
/**
* Represents a `<link>` element.
*
* WHATWG Specification: https://html.spec.whatwg.org/multipage/semantics.html#the-link-element
*/
type HtmlLinkDescriptor = (HtmlLinkProps & Pick<Required<HtmlLinkProps>, "href">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "imageSizes">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "href"> & {
imageSizes?: never;
});
interface PageLinkDescriptor extends Omit<HtmlLinkDescriptor, "href" | "rel" | "type" | "sizes" | "imageSrcSet" | "imageSizes" | "as" | "color" | "title"> {
/**
* A [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* attribute to render on the [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
* element. If not provided in Framework Mode, it will default to any
* {@link ServerRouter | `<ServerRouter nonce>`} prop.
*/
nonce?: string | undefined;
/**
* The absolute path of the page to prefetch, e.g. `/absolute/path`.
*/
page: string;
}
type LinkDescriptor = HtmlLinkDescriptor | PageLinkDescriptor;
//#endregion
export { HtmlLinkDescriptor, LinkDescriptor, PageLinkDescriptor };
@@ -0,0 +1,663 @@
import { Action, History, Location, Path, To } from "./history.js";
import { DataRouteMatch, DataRouteObject, DataStrategyFunction, FormEncType, HTMLFormMethod, MapRoutePropertiesFunction, MaybePromise, PatchRoutesOnNavigationFunction, RouteBranch, RouteData, RouteManifest, RouteObject, RouterContextProvider, Submission, UIMatch } from "./utils.js";
import { ClientInstrumentation, ServerInstrumentation } from "./instrumentation.js";
//#region lib/router/router.d.ts
/**
* A Router instance manages all navigation and data loading/mutations
*/
interface Router {
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the basename for the router
*/
get basename(): RouterInit["basename"];
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the future config for the router
*/
get future(): FutureConfig;
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the current state of the router
*/
get state(): RouterState;
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the routes for this router instance
*/
get routes(): DataRouteObject[];
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the route branches for this router instance
*/
get branches(): RouteBranch<DataRouteObject>[] | undefined;
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the manifest for this router instance
*/
get manifest(): RouteManifest;
/**
* @private
* PRIVATE - DO NOT USE
*
* Return the window associated with the router
*/
get window(): RouterInit["window"];
/**
* @private
* PRIVATE - DO NOT USE
*
* Initialize the router, including adding history listeners and kicking off
* initial data fetches. Returns a function to cleanup listeners and abort
* any in-progress loads
*/
initialize(): Router;
/**
* @private
* PRIVATE - DO NOT USE
*
* Subscribe to router.state updates
*
* @param fn function to call with the new state
*/
subscribe(fn: RouterSubscriber): () => void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Enable scroll restoration behavior in the router
*
* @param savedScrollPositions Object that will manage positions, in case
* it's being restored from sessionStorage
* @param getScrollPosition Function to get the active Y scroll position
* @param getKey Function to get the key to use for restoration
*/
enableScrollRestoration(savedScrollPositions: Record<string, number>, getScrollPosition: GetScrollPositionFunction, getKey?: GetScrollRestorationKeyFunction): () => void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Navigate forward/backward in the history stack
* @param to Delta to move in the history stack
*/
navigate(to: number): Promise<void>;
/**
* Navigate to the given path
* @param to Path to navigate to
* @param opts Navigation options (method, submission, etc.)
*/
navigate(to: To | null, opts?: RouterNavigateOptions): Promise<void>;
/**
* @private
* PRIVATE - DO NOT USE
*
* Trigger a fetcher load/submission
*
* @param key Fetcher key
* @param routeId Route that owns the fetcher
* @param href href to fetch
* @param opts Fetcher options, (method, submission, etc.)
*/
fetch(key: string, routeId: string, href: string | null, opts?: RouterFetchOptions): Promise<void>;
/**
* @private
* PRIVATE - DO NOT USE
*
* Trigger a revalidation of all current route loaders and fetcher loads
*/
revalidate(): Promise<void>;
/**
* @private
* PRIVATE - DO NOT USE
*
* Utility function to create an href for the given location
* @param location
*/
createHref(location: Location | URL): string;
/**
* @private
* PRIVATE - DO NOT USE
*
* Utility function to URL encode a destination path according to the internal
* history implementation
* @param to
*/
encodeLocation(to: To): Path;
/**
* @private
* PRIVATE - DO NOT USE
*
* Get/create a fetcher for the given key
* @param key
*/
getFetcher<TData = any>(key: string): Fetcher<TData>;
/**
* @internal
* PRIVATE - DO NOT USE
*
* Reset the fetcher for a given key
* @param key
*/
resetFetcher(key: string, opts?: {
reason?: unknown;
}): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Delete the fetcher for a given key
* @param key
*/
deleteFetcher(key: string): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Cleanup listeners and abort any in-progress loads
*/
dispose(): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Get a navigation blocker
* @param key The identifier for the blocker
* @param fn The blocker function implementation
*/
getBlocker(key: string, fn: BlockerFunction): Blocker;
/**
* @private
* PRIVATE - DO NOT USE
*
* Delete a navigation blocker
* @param key The identifier for the blocker
*/
deleteBlocker(key: string): void;
/**
* @private
* PRIVATE DO NOT USE
*
* Patch additional children routes into an existing parent route
* @param routeId The parent route id or a callback function accepting `patch`
* to perform batch patching
* @param children The additional children routes
* @param unstable_allowElementMutations Allow mutation or route elements on
* existing routes. Intended for RSC-usage
* only.
*/
patchRoutes(routeId: string | null, children: RouteObject[], unstable_allowElementMutations?: boolean): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* HMR needs to pass in-flight route updates to React Router
* TODO: Replace this with granular route update APIs (addRoute, updateRoute, deleteRoute)
*/
_internalSetRoutes(routes: RouteObject[]): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Cause subscribers to re-render. This is used to force a re-render.
*/
_internalSetStateDoNotUseOrYouWillBreakYourApp(state: Partial<RouterState>): void;
/**
* @private
* PRIVATE - DO NOT USE
*
* Internal fetch AbortControllers accessed by unit tests
*/
_internalFetchControllers: Map<string, AbortController>;
}
/**
* State maintained internally by the router. During a navigation, all states
* reflect the "old" location unless otherwise noted.
*/
interface RouterState {
/**
* The action of the most recent navigation
*/
historyAction: Action;
/**
* The current location reflected by the router
*/
location: Location;
/**
* The current set of route matches
*/
matches: DataRouteMatch[];
/**
* Tracks whether we've completed our initial data load
*/
initialized: boolean;
/**
* Tracks whether we should be rendering a HydrateFallback during hydration
*/
renderFallback: boolean;
/**
* Current scroll position we should start at for a new view
* - number -> scroll position to restore to
* - false -> do not restore scroll at all (used during submissions/revalidations)
* - null -> don't have a saved position, scroll to hash or top of page
*/
restoreScrollPosition: number | false | null;
/**
* Indicate whether this navigation should skip resetting the scroll position
* if we are unable to restore the scroll position
*/
preventScrollReset: boolean;
/**
* Tracks the state of the current navigation
*/
navigation: Navigation;
/**
* Tracks any in-progress revalidations
*/
revalidation: RevalidationState;
/**
* Data from the loaders for the current matches
*/
loaderData: RouteData;
/**
* Data from the action for the current matches
*/
actionData: RouteData | null;
/**
* Errors caught from loaders for the current matches
*/
errors: RouteData | null;
/**
* Map of current fetchers
*/
fetchers: Map<string, Fetcher>;
/**
* Map of current blockers
*/
blockers: Map<string, Blocker>;
}
/**
* Data that can be passed into hydrate a Router from SSR
*/
type HydrationState = Partial<Pick<RouterState, "loaderData" | "actionData" | "errors">>;
/**
* Future flags to toggle new feature behavior
*/
interface FutureConfig {}
/**
* Initialization options for createRouter
*/
interface RouterInit {
routes: RouteObject[];
history: History;
basename?: string;
getContext?: () => MaybePromise<RouterContextProvider>;
instrumentations?: ClientInstrumentation[];
mapRouteProperties?: MapRoutePropertiesFunction;
future?: Partial<FutureConfig>;
hydrationRouteProperties?: string[];
hydrationData?: HydrationState;
window?: Window;
dataStrategy?: DataStrategyFunction;
patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
}
/**
* State returned from a server-side query() call
*/
interface StaticHandlerContext {
basename: Router["basename"];
location: RouterState["location"];
matches: RouterState["matches"];
loaderData: RouterState["loaderData"];
actionData: RouterState["actionData"];
errors: RouterState["errors"];
statusCode: number;
loaderHeaders: Record<string, Headers>;
actionHeaders: Record<string, Headers>;
_deepestRenderedBoundaryId?: string | null;
}
/**
* A StaticHandler instance manages a singular SSR navigation/fetch event
*/
interface StaticHandler {
/**
* The set of data routes managed by this handler
*/
dataRoutes: DataRouteObject[];
/**
* @private
* PRIVATE - DO NOT USE
*
* The route branches derived from the data routes, used for internal route
* matching in Framework Mode
*/
_internalRouteBranches: RouteBranch<DataRouteObject>[];
/**
* Perform a query for a given request - executing all matched route
* loaders/actions. Used for document requests.
*
* @param request The request to query
* @param opts Optional query options
* @param opts.dataStrategy Alternate dataStrategy implementation
* @param opts.filterMatchesToLoad Predicate function to filter which matches should be loaded
* @param opts.generateMiddlewareResponse To enable middleware, provide a function
* to generate a response to bubble back up the middleware chain
* @param opts.requestContext Context object to pass to loaders/actions
* @param opts.skipLoaderErrorBubbling Skip loader error bubbling
* @param opts.skipRevalidation Skip revalidation after action submission
* @param opts.normalizePath Normalize the request path
*/
query(request: Request, opts?: {
requestContext?: unknown;
filterMatchesToLoad?: (match: DataRouteMatch) => boolean;
skipLoaderErrorBubbling?: boolean;
skipRevalidation?: boolean;
dataStrategy?: DataStrategyFunction<unknown>;
generateMiddlewareResponse?: (query: (r: Request, args?: {
filterMatchesToLoad?: (match: DataRouteMatch) => boolean;
}) => Promise<StaticHandlerContext | Response>) => MaybePromise<Response>;
normalizePath?: (request: Request) => Path;
}): Promise<StaticHandlerContext | Response>;
/**
* Perform a query for a specific route. Used for resource requests.
*
* @param request The request to query
* @param opts Optional queryRoute options
* @param opts.dataStrategy Alternate dataStrategy implementation
* @param opts.generateMiddlewareResponse To enable middleware, provide a function
* to generate a response to bubble back up the middleware chain
* @param opts.requestContext Context object to pass to loaders/actions
* @param opts.routeId The ID of the route to query
* @param opts.normalizePath Normalize the request path
*/
queryRoute(request: Request, opts?: {
routeId?: string;
requestContext?: unknown;
dataStrategy?: DataStrategyFunction<unknown>;
generateMiddlewareResponse?: (queryRoute: (r: Request) => Promise<Response>) => MaybePromise<Response>;
normalizePath?: (request: Request) => Path;
}): Promise<any>;
}
type ViewTransitionOpts = {
currentLocation: Location;
nextLocation: Location;
};
/**
* Subscriber function signature for changes to router state
*/
interface RouterSubscriber {
(state: RouterState, opts: {
deletedFetchers: string[];
newErrors: RouteData | null;
viewTransitionOpts?: ViewTransitionOpts;
flushSync: boolean;
}): void;
}
/**
* Function signature for determining the key to be used in scroll restoration
* for a given location
*/
interface GetScrollRestorationKeyFunction {
(location: Location, matches: UIMatch[]): string | null;
}
/**
* Function signature for determining the current scroll position
*/
interface GetScrollPositionFunction {
(): number;
}
/**
* - "route": relative to the route hierarchy so `..` means remove all segments
* of the current route even if it has many. For example, a `route("posts/:id")`
* would have both `:id` and `posts` removed from the url.
* - "path": relative to the pathname so `..` means remove one segment of the
* pathname. For example, a `route("posts/:id")` would have only `:id` removed
* from the url.
*/
type RelativeRoutingType = "route" | "path";
type BaseNavigateOrFetchOptions = {
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
flushSync?: boolean;
defaultShouldRevalidate?: boolean;
};
type BaseNavigateOptions = BaseNavigateOrFetchOptions & {
replace?: boolean;
state?: any;
fromRouteId?: string;
viewTransition?: boolean;
mask?: To;
};
type BaseSubmissionOptions = {
formMethod?: HTMLFormMethod;
formEncType?: FormEncType;
} & ({
formData: FormData;
body?: undefined;
} | {
formData?: undefined;
body: any;
});
/**
* Options for a navigate() call for a normal (non-submission) navigation
*/
type LinkNavigateOptions = BaseNavigateOptions;
/**
* Options for a navigate() call for a submission navigation
*/
type SubmissionNavigateOptions = BaseNavigateOptions & BaseSubmissionOptions;
/**
* Options to pass to navigate() for a navigation
*/
type RouterNavigateOptions = LinkNavigateOptions | SubmissionNavigateOptions;
/**
* Options for a fetch() load
*/
type LoadFetchOptions = BaseNavigateOrFetchOptions;
/**
* Options for a fetch() submission
*/
type SubmitFetchOptions = BaseNavigateOrFetchOptions & BaseSubmissionOptions;
/**
* Options to pass to fetch()
*/
type RouterFetchOptions = LoadFetchOptions | SubmitFetchOptions;
/**
* Potential states for state.navigation
*/
type NavigationStates = {
Idle: {
state: "idle";
location: undefined;
matches: undefined;
historyAction: undefined;
formMethod: undefined;
formAction: undefined;
formEncType: undefined;
formData: undefined;
json: undefined;
text: undefined;
};
Loading: {
state: "loading";
location: Location;
matches: DataRouteMatch[];
historyAction: Action;
formMethod: Submission["formMethod"] | undefined;
formAction: Submission["formAction"] | undefined;
formEncType: Submission["formEncType"] | undefined;
formData: Submission["formData"] | undefined;
json: Submission["json"] | undefined;
text: Submission["text"] | undefined;
};
Submitting: {
state: "submitting";
location: Location;
matches: DataRouteMatch[];
historyAction: Action;
formMethod: Submission["formMethod"];
formAction: Submission["formAction"];
formEncType: Submission["formEncType"];
formData: Submission["formData"];
json: Submission["json"];
text: Submission["text"];
};
};
type Navigation = NavigationStates[keyof NavigationStates];
type RevalidationState = "idle" | "loading";
/**
* Potential states for fetchers
*/
type FetcherStates<TData = any> = {
/**
* The fetcher is not calling a loader or action
*
* ```tsx
* fetcher.state === "idle"
* ```
*/
Idle: {
state: "idle";
formMethod: undefined;
formAction: undefined;
formEncType: undefined;
text: undefined;
formData: undefined;
json: undefined;
/**
* If the fetcher has never been called, this will be undefined.
*/
data: TData | undefined;
};
/**
* The fetcher is loading data from a {@link LoaderFunction | loader} from a
* call to {@link FetcherWithComponents.load | `fetcher.load`}.
*
* ```tsx
* // somewhere
* <button onClick={() => fetcher.load("/some/route") }>Load</button>
*
* // the state will update
* fetcher.state === "loading"
* ```
*/
Loading: {
state: "loading";
formMethod: Submission["formMethod"] | undefined;
formAction: Submission["formAction"] | undefined;
formEncType: Submission["formEncType"] | undefined;
text: Submission["text"] | undefined;
formData: Submission["formData"] | undefined;
json: Submission["json"] | undefined;
data: TData | undefined;
};
/**
The fetcher is submitting to a {@link LoaderFunction} (GET) or {@link ActionFunction} (POST) from a {@link FetcherWithComponents.Form | `fetcher.Form`} or {@link FetcherWithComponents.submit | `fetcher.submit`}.
```tsx
// somewhere
<input
onChange={e => {
fetcher.submit(event.currentTarget.form, { method: "post" });
}}
/>
// the state will update
fetcher.state === "submitting"
// and formData will be available
fetcher.formData
```
*/
Submitting: {
state: "submitting";
formMethod: Submission["formMethod"];
formAction: Submission["formAction"];
formEncType: Submission["formEncType"];
text: Submission["text"];
formData: Submission["formData"];
json: Submission["json"];
data: TData | undefined;
};
};
type Fetcher<TData = any> = FetcherStates<TData>[keyof FetcherStates<TData>];
interface BlockerBlocked {
state: "blocked";
reset: () => void;
proceed: () => void;
location: Location;
}
interface BlockerUnblocked {
state: "unblocked";
reset: undefined;
proceed: undefined;
location: undefined;
}
interface BlockerProceeding {
state: "proceeding";
reset: undefined;
proceed: undefined;
location: Location;
}
type Blocker = BlockerUnblocked | BlockerBlocked | BlockerProceeding;
type BlockerFunction = (args: {
currentLocation: Location;
nextLocation: Location;
historyAction: Action;
}) => boolean;
declare const IDLE_NAVIGATION: NavigationStates["Idle"];
declare const IDLE_FETCHER: FetcherStates["Idle"];
declare const IDLE_BLOCKER: BlockerUnblocked;
/**
* Create a router and listen to history POP navigations
*/
declare function createRouter(init: RouterInit): Router;
interface CreateStaticHandlerOptions {
basename?: string;
mapRouteProperties?: MapRoutePropertiesFunction;
instrumentations?: Pick<ServerInstrumentation, "route">[];
future?: Partial<FutureConfig>;
}
/**
* Create a static handler to perform server-side data loading
*
* @example
* export async function handleRequest(request: Request) {
* let { query, dataRoutes } = createStaticHandler(routes);
* let context = await query(request);
*
* if (context instanceof Response) {
* return context;
* }
*
* let router = createStaticRouter(dataRoutes, context);
* return new Response(
* ReactDOMServer.renderToString(<StaticRouterProvider ... />),
* { headers: { "Content-Type": "text/html" } }
* );
* }
*
* @public
* @category Data Routers
* @mode data
* @param routes The {@link RouteObject | route objects} to create a static
* handler for
* @param opts Options
* @param opts.basename The base URL for the static handler (default: `/`)
* @param opts.future Future flags for the static handler
* @returns A static handler that can be used to query data for the provided
* routes
*/
declare function createStaticHandler(routes: RouteObject[], opts?: CreateStaticHandlerOptions): StaticHandler;
//#endregion
export { Blocker, BlockerFunction, Fetcher, FutureConfig, GetScrollPositionFunction, GetScrollRestorationKeyFunction, HydrationState, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, Navigation, NavigationStates, RelativeRoutingType, RevalidationState, Router, RouterFetchOptions, RouterInit, RouterNavigateOptions, RouterState, RouterSubscriber, StaticHandler, StaticHandlerContext, createRouter, createStaticHandler };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/router/url.ts
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i;
const PROTOCOL_RELATIVE_URL_REGEX = /^[\\/]{2}/;
function normalizeProtocolRelativeUrl(url, protocol) {
return protocol + url.replace(/\\/g, "/");
}
//#endregion
export { ABSOLUTE_URL_REGEX, PROTOCOL_RELATIVE_URL_REGEX, normalizeProtocolRelativeUrl };
@@ -0,0 +1,960 @@
import { Location, Path, To } from "./history.js";
import * as React$1 from "react";
//#region lib/router/utils.d.ts
type MaybePromise<T> = T | Promise<T>;
/**
* Map of routeId -> data returned from a loader/action/error
*/
interface RouteData {
[routeId: string]: any;
}
type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>;
/**
* Users can specify either lowercase or uppercase form methods on `<Form>`,
* useSubmit(), `<fetcher.Form>`, etc.
*/
type HTMLFormMethod = LowerCaseFormMethod | UpperCaseFormMethod;
/**
* Active navigation/fetcher form methods are exposed in uppercase on the
* RouterState. This is to align with the normalization done via fetch().
*/
type FormMethod = UpperCaseFormMethod;
type FormEncType = "application/x-www-form-urlencoded" | "multipart/form-data" | "application/json" | "text/plain";
type JsonObject = { [Key in string]: JsonValue } & { [Key in string]?: JsonValue | undefined };
type JsonArray = JsonValue[] | readonly JsonValue[];
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
/**
* @private
* Internal interface to pass around for action submissions, not intended for
* external consumption
*/
type Submission = {
formMethod: FormMethod;
formAction: string;
formEncType: FormEncType;
formData: FormData;
json: undefined;
text: undefined;
} | {
formMethod: FormMethod;
formAction: string;
formEncType: FormEncType;
formData: undefined;
json: JsonValue;
text: undefined;
} | {
formMethod: FormMethod;
formAction: string;
formEncType: FormEncType;
formData: undefined;
json: undefined;
text: string;
};
/**
* A context instance used as the key for the `get`/`set` methods of a
* {@link RouterContextProvider}. Accepts an optional default
* value to be returned if no value has been set.
*/
interface RouterContext<T = unknown> {
defaultValue?: T;
}
/**
* Creates a type-safe {@link RouterContext} object that can be used to
* store and retrieve arbitrary values in [`action`](../../start/framework/route-module#action)s,
* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
* Similar to React's [`createContext`](https://react.dev/reference/react/createContext),
* but specifically designed for React Router's request/response lifecycle.
*
* If a `defaultValue` is provided, it will be returned from `context.get()`
* when no value has been set for the context. Otherwise, reading this context
* when no value has been set will throw an error.
*
* ```tsx filename=app/context.ts
* import { createContext } from "react-router";
*
* // Create a context for user data
* export const userContext =
* createContext<User | null>(null);
* ```
*
* ```tsx filename=app/middleware/auth.ts
* import { getUserFromSession } from "~/auth.server";
* import { userContext } from "~/context";
*
* export const authMiddleware = async ({
* context,
* request,
* }) => {
* const user = await getUserFromSession(request);
* context.set(userContext, user);
* };
* ```
*
* ```tsx filename=app/routes/profile.tsx
* import { userContext } from "~/context";
*
* export async function loader({
* context,
* }: Route.LoaderArgs) {
* const user = context.get(userContext);
*
* if (!user) {
* throw new Response("Unauthorized", { status: 401 });
* }
*
* return { user };
* }
* ```
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param defaultValue An optional default value for the context. This value
* will be returned if no value has been set for this context.
* @returns A {@link RouterContext} object that can be used with
* `context.get()` and `context.set()` in [`action`](../../start/framework/route-module#action)s,
* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
*/
declare function createContext<T>(defaultValue?: T): RouterContext<T>;
/**
* Provides methods for writing/reading values in application context in a
* type-safe way. Primarily for usage with [middleware](../../how-to/middleware).
*
* @example
* import {
* createContext,
* RouterContextProvider
* } from "react-router";
*
* const userContext = createContext<User | null>(null);
* const contextProvider = new RouterContextProvider();
* contextProvider.set(userContext, getUser());
* // ^ Type-safe
* const user = contextProvider.get(userContext);
* // ^ User
*
* @public
* @category Utils
* @mode framework
* @mode data
*/
declare class RouterContextProvider {
#private;
/**
* Create a new `RouterContextProvider` instance
* @param init An optional initial context map to populate the provider with
*/
constructor(init?: Map<RouterContext, unknown>);
/**
* Access a value from the context. If no value has been set for the context,
* it will return the context's `defaultValue` if provided, or throw an error
* if no `defaultValue` was set.
* @param context The context to get the value for
* @returns The value for the context, or the context's `defaultValue` if no
* value was set
*/
get<T>(context: RouterContext<T>): T;
/**
* Set a value for the context. If the context already has a value set, this
* will overwrite it.
*
* @param context The context to set the value for
* @param value The value to set for the context
* @returns {void}
*/
set<C extends RouterContext>(context: C, value: C extends RouterContext<infer T> ? T : never): void;
}
type DefaultContext = Readonly<RouterContextProvider>;
/**
* @private
* Arguments passed to route loader/action functions. Same for now but we keep
* this as a private implementation detail in case they diverge in the future.
*/
interface DataFunctionArgs<Context> {
/** A {@link https://developer.mozilla.org/en-US/docs/Web/API/Request Fetch Request instance} which you can use to read headers (like cookies, and {@link https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams URLSearchParams} from the request. */
request: Request;
/**
* A URL instance representing the application location being navigated to or
* fetched.
*
* In Framework mode, this is a normalized URL with React-Router-specific
* implementation details removed (`.data` suffixes, `index`/`_routes` search
* params). For the raw incoming URL, use `request.url`.
*/
url: URL;
/**
* Matched un-interpolated route pattern for the current path (i.e., /blog/:slug).
* Mostly useful as a identifier to aggregate on for logging/tracing/etc.
*/
pattern: string;
/**
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
* @example
* // app/routes.ts
* route("teams/:teamId", "./team.tsx"),
*
* // app/team.tsx
* export function loader({
* params,
* }: Route.LoaderArgs) {
* params.teamId;
* // ^ string
* }
*/
params: Params;
/**
* This is the context passed in to your server adapter's getLoadContext() function.
* It's a way to bridge the gap between the adapter's request/response API with your React Router app.
* It is only applicable if you are using a custom server adapter.
*/
context: Context;
}
/**
* Route middleware `next` function to call downstream handlers and then complete
* middlewares from the bottom-up
*/
interface MiddlewareNextFunction<Result = unknown> {
(): Promise<Result>;
}
/**
* Route middleware function signature. Receives the same "data" arguments as a
* `loader`/`action` (`request`, `params`, `context`) as the first parameter and
* a `next` function as the second parameter which will call downstream handlers
* and then complete middlewares from the bottom-up
*/
type MiddlewareFunction<Result = unknown> = (args: DataFunctionArgs<Readonly<RouterContextProvider>>, next: MiddlewareNextFunction<Result>) => MaybePromise<Result | void>;
/**
* Arguments passed to loader functions
*/
interface LoaderFunctionArgs<Context = DefaultContext> extends DataFunctionArgs<Context> {}
/**
* Arguments passed to action functions
*/
interface ActionFunctionArgs<Context = DefaultContext> extends DataFunctionArgs<Context> {}
/**
* Loaders and actions can return anything
*/
type DataFunctionValue = unknown;
type DataFunctionReturnValue = MaybePromise<DataFunctionValue>;
/**
* Route loader function signature
*/
type LoaderFunction<Context = DefaultContext> = {
(args: LoaderFunctionArgs<Context>, handlerCtx?: unknown): DataFunctionReturnValue;
} & {
hydrate?: boolean;
};
/**
* Route action function signature
*/
interface ActionFunction<Context = DefaultContext> {
(args: ActionFunctionArgs<Context>, handlerCtx?: unknown): DataFunctionReturnValue;
}
/**
* Arguments passed to shouldRevalidate function
*/
interface ShouldRevalidateFunctionArgs {
/** This is the url the navigation started from. You can compare it with `nextUrl` to decide if you need to revalidate this route's data. */
currentUrl: URL;
/** These are the {@link https://reactrouter.com/start/framework/routing#dynamic-segments dynamic route params} from the URL that can be compared to the `nextParams` to decide if you need to reload or not. Perhaps you're using only a partial piece of the param for data loading, you don't need to revalidate if a superfluous part of the param changed. */
currentParams: DataRouteMatch["params"];
/** In the case of navigation, this the URL the user is requesting. Some revalidations are not navigation, so it will simply be the same as currentUrl. */
nextUrl: URL;
/** In the case of navigation, these are the {@link https://reactrouter.com/start/framework/routing#dynamic-segments dynamic route params} from the next location the user is requesting. Some revalidations are not navigation, so it will simply be the same as currentParams. */
nextParams: DataRouteMatch["params"];
/** The method (probably `"GET"` or `"POST"`) used in the form submission that triggered the revalidation. */
formMethod?: Submission["formMethod"];
/** The form action (`<Form action="/somewhere">`) that triggered the revalidation. */
formAction?: Submission["formAction"];
/** The form encType (`<Form encType="application/x-www-form-urlencoded">) used in the form submission that triggered the revalidation*/
formEncType?: Submission["formEncType"];
/** The form submission data when the form's encType is `text/plain` */
text?: Submission["text"];
/** The form submission data when the form's encType is `application/x-www-form-urlencoded` or `multipart/form-data` */
formData?: Submission["formData"];
/** The form submission data when the form's encType is `application/json` */
json?: Submission["json"];
/** The status code of the action response */
actionStatus?: number;
/**
* When a submission causes the revalidation this will be the result of the action—either action data or an error if the action failed. It's common to include some information in the action result to instruct shouldRevalidate to revalidate or not.
*
* @example
* export async function action() {
* await saveSomeStuff();
* return { ok: true };
* }
*
* export function shouldRevalidate({
* actionResult,
* }) {
* if (actionResult?.ok) {
* return false;
* }
* return true;
* }
*/
actionResult?: any;
/**
* By default, React Router doesn't call every loader all the time. There are reliable optimizations it can make by default. For example, only loaders with changing params are called. Consider navigating from the following URL to the one below it:
*
* /projects/123/tasks/abc
* /projects/123/tasks/def
* React Router will only call the loader for tasks/def because the param for projects/123 didn't change.
*
* It's safest to always return defaultShouldRevalidate after you've done your specific optimizations that return false, otherwise your UI might get out of sync with your data on the server.
*/
defaultShouldRevalidate: boolean;
}
/**
* Route shouldRevalidate function signature. This runs after any submission
* (navigation or fetcher), so we flatten the navigation/fetcher submission
* onto the arguments. It shouldn't matter whether it came from a navigation
* or a fetcher, what really matters is the URLs and the formData since loaders
* have to re-run based on the data models that were potentially mutated.
*/
interface ShouldRevalidateFunction {
(args: ShouldRevalidateFunctionArgs): boolean;
}
interface DataStrategyMatch extends RouteMatch<string, DataRouteObject> {
/**
* @private
*/
_lazyPromises?: {
middleware: Promise<void> | undefined;
handler: Promise<void> | undefined;
route: Promise<void> | undefined;
};
/**
* @deprecated Deprecated in favor of `shouldCallHandler`
*
* A boolean value indicating whether this route handler should be called in
* this pass.
*
* The `matches` array always includes _all_ matched routes even when only
* _some_ route handlers need to be called so that things like middleware can
* be implemented.
*
* `shouldLoad` is usually only interesting if you are skipping the route
* handler entirely and implementing custom handler logic - since it lets you
* determine if that custom logic should run for this route or not.
*
* For example:
* - If you are on `/parent/child/a` and you navigate to `/parent/child/b` -
* you'll get an array of three matches (`[parent, child, b]`), but only `b`
* will have `shouldLoad=true` because the data for `parent` and `child` is
* already loaded
* - If you are on `/parent/child/a` and you submit to `a`'s [`action`](https://reactrouter.com/docs/start/data/route-object#action),
* then only `a` will have `shouldLoad=true` for the action execution of
* `dataStrategy`
* - After the [`action`](https://reactrouter.com/docs/start/data/route-object#action),
* `dataStrategy` will be called again for the [`loader`](https://reactrouter.com/docs/start/data/route-object#loader)
* revalidation, and all matches will have `shouldLoad=true` (assuming no
* custom `shouldRevalidate` implementations)
*/
shouldLoad: boolean;
/**
* Arguments passed to the `shouldRevalidate` function for this `loader` execution.
* Will be `null` if this is not a revalidating loader {@link DataStrategyMatch}.
*/
shouldRevalidateArgs: ShouldRevalidateFunctionArgs | null;
/**
* Determine if this route's handler should be called during this `dataStrategy`
* execution. Calling it with no arguments will leverage the default revalidation
* behavior. You can pass your own `defaultShouldRevalidate` value if you wish
* to change the default revalidation behavior with your `dataStrategy`.
*
* @param defaultShouldRevalidate `defaultShouldRevalidate` override value (optional)
*/
shouldCallHandler(defaultShouldRevalidate?: boolean): boolean;
/**
* An async function that will resolve any `route.lazy` implementations and
* execute the route's handler (if necessary), returning a {@link DataStrategyResult}
*
* - Calling `match.resolve` does not mean you're calling the
* [`action`](https://reactrouter.com/docs/start/data/route-object#action)/[`loader`](https://reactrouter.com/docs/start/data/route-object#loader)
* (the "handler") - `resolve` will only call the `handler` internally if
* needed _and_ if you don't pass your own `handlerOverride` function parameter
* - It is safe to call `match.resolve` for all matches, even if they have
* `shouldLoad=false`, and it will no-op if no loading is required
* - You should generally always call `match.resolve()` for `shouldLoad:true`
* routes to ensure that any `route.lazy` implementations are processed
* - See the examples below for how to implement custom handler execution via
* `match.resolve`
*/
resolve: (handlerOverride?: (handler: (ctx?: unknown) => DataFunctionReturnValue) => DataFunctionReturnValue) => Promise<DataStrategyResult>;
}
interface DataStrategyFunctionArgs<Context = DefaultContext> extends DataFunctionArgs<Context> {
/**
* Matches for this route extended with Data strategy APIs
*/
matches: DataStrategyMatch[];
runClientMiddleware: (cb: DataStrategyFunction<Context>) => Promise<Record<string, DataStrategyResult>>;
/**
* The key of the fetcher we are calling `dataStrategy` for, otherwise `null`
* for navigational executions
*/
fetcherKey: string | null;
}
/**
* Result from a loader or action called via dataStrategy
*/
interface DataStrategyResult {
type: "data" | "error";
result: unknown;
}
interface DataStrategyFunction<Context = DefaultContext> {
(args: DataStrategyFunctionArgs<Context>): Promise<Record<string, DataStrategyResult>>;
}
type PatchRoutesOnNavigationFunctionArgs = {
signal: AbortSignal;
path: string;
matches: RouteMatch[];
fetcherKey: string | undefined;
patch: (routeId: string | null, children: RouteObject[]) => void;
};
type PatchRoutesOnNavigationFunction = (opts: PatchRoutesOnNavigationFunctionArgs) => MaybePromise<void>;
/**
* Function provided to set route-specific properties from route objects
*/
interface MapRoutePropertiesFunction {
(route: DataRouteObject): Partial<DataRouteObject>;
}
/**
* Keys we cannot change from within a lazy object. We spread all other keys
* onto the route. Either they're meaningful to the router, or they'll get
* ignored.
*/
type UnsupportedLazyRouteObjectKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
/**
* Keys we cannot change from within a lazy() function. We spread all other keys
* onto the route. Either they're meaningful to the router, or they'll get
* ignored.
*/
type UnsupportedLazyRouteFunctionKey = UnsupportedLazyRouteObjectKey | "middleware";
/**
* lazy object to load route properties, which can add non-matching
* related properties to a route
*/
type LazyRouteObject<R extends RouteObject> = { [K in keyof R as K extends UnsupportedLazyRouteObjectKey ? never : K]?: () => Promise<R[K] | null | undefined> };
/**
* lazy() function to load a route definition, which can add non-matching
* related properties to a route
*/
interface LazyRouteFunction<R extends RouteObject> {
(): Promise<Omit<R, UnsupportedLazyRouteFunctionKey> & Partial<Record<UnsupportedLazyRouteFunctionKey, never>>>;
}
type LazyRouteDefinition<R extends RouteObject> = LazyRouteObject<R> | LazyRouteFunction<R>;
/**
* Base RouteObject with common props shared by all types of routes
* @internal
*/
type BaseRouteObject = {
/**
* Whether the path should be case-sensitive. Defaults to `false`.
*/
caseSensitive?: boolean;
/**
* The path pattern to match. If unspecified or empty, then this becomes a
* layout route.
*/
path?: string;
/**
* The unique identifier for this route (for use with {@link DataRouter}s)
*/
id?: string;
/**
* The route middleware.
* See [`middleware`](../../start/data/route-object#middleware).
*/
middleware?: MiddlewareFunction[];
/**
* The route loader.
* See [`loader`](../../start/data/route-object#loader).
*/
loader?: LoaderFunction | boolean;
/**
* The route action.
* See [`action`](../../start/data/route-object#action).
*/
action?: ActionFunction | boolean;
/**
* The route shouldRevalidate function.
* See [`shouldRevalidate`](../../start/data/route-object#shouldRevalidate).
*/
shouldRevalidate?: ShouldRevalidateFunction;
/**
* The route handle.
*/
handle?: any;
/**
* A function that returns a promise that resolves to the route object.
* Used for code-splitting routes.
* See [`lazy`](../../start/data/route-object#lazy).
*/
lazy?: LazyRouteDefinition<BaseRouteObject>;
/**
* The React Component to render when this route matches.
* Mutually exclusive with `element`.
*/
Component?: React$1.ComponentType | null;
/**
* The React element to render when this Route matches.
* Mutually exclusive with `Component`.
*/
element?: React$1.ReactNode | null;
/**
* The React Component to render at this route if an error occurs.
* Mutually exclusive with `errorElement`.
*/
ErrorBoundary?: React$1.ComponentType | null;
/**
* The React element to render at this route if an error occurs.
* Mutually exclusive with `ErrorBoundary`.
*/
errorElement?: React$1.ReactNode | null;
/**
* The React Component to render while this router is loading data.
* Mutually exclusive with `hydrateFallbackElement`.
*/
HydrateFallback?: React$1.ComponentType | null;
/**
* The React element to render while this router is loading data.
* Mutually exclusive with `HydrateFallback`.
*/
hydrateFallbackElement?: React$1.ReactNode | null;
};
/**
* Index routes must not have children
*/
type IndexRouteObject = BaseRouteObject & {
/**
* Child Route objects - not valid on index routes.
*/
children?: undefined;
/**
* Whether this is an index route.
*/
index: true;
};
/**
* Non-index routes may have children, but cannot have `index` set to `true`.
*/
type NonIndexRouteObject = BaseRouteObject & {
/**
* Child Route objects.
*/
children?: RouteObject[];
/**
* Whether this is an index route - must be `false` or undefined on non-index routes.
*/
index?: false;
};
/**
* A route object represents a logical route, with (optionally) its child
* routes organized in a tree-like structure.
*/
type RouteObject = IndexRouteObject | NonIndexRouteObject;
type DataIndexRouteObject = IndexRouteObject & {
id: string;
};
type DataNonIndexRouteObject = NonIndexRouteObject & {
children?: DataRouteObject[];
id: string;
};
/**
* A data route object, which is just a RouteObject with a required unique ID
*/
type DataRouteObject = DataIndexRouteObject | DataNonIndexRouteObject;
type RouteManifest<R = DataRouteObject> = Record<string, R | undefined>;
type Regex_az = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
type Regex_AZ = Uppercase<Regex_az>;
type Regex_09 = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type Regex_w = Regex_az | Regex_AZ | Regex_09 | "_";
/** Emulates Regex `+` operator */
type RegexMatchPlus<char extends string, T extends string> = _RegexMatchPlus<char, T> extends infer result extends string ? result extends '' ? never : result : never;
type _RegexMatchPlus<char extends string, T extends string> = T extends `${infer head extends char}${infer rest}` ? `${head}${_RegexMatchPlus<char, rest>}` : '';
type ParamNameChar = Regex_w | "-";
type Simplify<T> = { [K in keyof T]: T[K] } & {};
type GeneratePathParams<path extends string> = Simplify<ParseParams<path> & { [key in string]: string | null | undefined }>;
type ParseParams<path extends string> = path extends '*' ? {
'*': string;
} : path extends `${infer rest}/*` ? {
'*': string;
} & ParseParams<rest> : _ParseParams<path>;
type _ParseParams<path extends string> = path extends `${infer left}/${infer right}` ? _ParseParams<left> & _ParseParams<right> : path extends `:${infer param}?${string}` ? { [key in RegexMatchPlus<ParamNameChar, param>]?: string | null | undefined } : path extends `:${infer param}` ? { [key in RegexMatchPlus<ParamNameChar, param>]: string } : {};
type PathParam<path extends string> = (keyof ParseParams<path>) & string;
type ParamParseKey<Segment extends string> = [PathParam<Segment>] extends [never] ? string : PathParam<Segment>;
/**
* The parameters that were parsed from the URL path.
*/
type Params<Key extends string = string> = { readonly [key in Key]: string | undefined };
/**
* A RouteMatch contains info about how a route matched a URL.
*/
interface RouteMatch<ParamKey extends string = string, RouteObjectType extends RouteObject = RouteObject> {
/**
* The names and values of dynamic parameters in the URL.
*/
params: Params<ParamKey>;
/**
* The portion of the URL pathname that was matched.
*/
pathname: string;
/**
* The portion of the URL pathname that was matched before child routes.
*/
pathnameBase: string;
/**
* The route object that was used to match.
*/
route: RouteObjectType;
}
interface DataRouteMatch extends RouteMatch<string, DataRouteObject> {}
declare function defaultMapRouteProperties(route: DataRouteObject): Partial<DataRouteObject>;
/**
* Matches the given routes to a location and returns the match data.
*
* @example
* import { matchRoutes } from "react-router";
*
* let routes = [{
* path: "/",
* Component: Root,
* children: [{
* path: "dashboard",
* Component: Dashboard,
* }]
* }];
*
* matchRoutes(routes, "/dashboard"); // [rootMatch, dashboardMatch]
*
* @public
* @category Utils
* @param routes The array of route objects to match against.
* @param locationArg The location to match against, either a string path or a
* partial {@link Location} object
* @param basename Optional base path to strip from the location before matching.
* Defaults to `/`.
* @returns An array of matched routes, or `null` if no matches were found.
*/
declare function matchRoutes<RouteObjectType extends RouteObject = RouteObject>(routes: RouteObjectType[], locationArg: Partial<Location> | string, basename?: string): RouteMatch<string, RouteObjectType>[] | null;
interface UIMatch<Data = unknown, Handle = unknown> {
id: string;
pathname: string;
/**
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the matched route.
*/
params: RouteMatch["params"];
/**
* The return value from the matched route's loader or clientLoader. This might
* be `undefined` if this route's `loader` (or a deeper route's `loader`) threw
* an error and we're currently displaying an `ErrorBoundary`.
*/
loaderData: Data | undefined;
/**
* The {@link https://reactrouter.com/start/framework/route-module#handle handle object}
* exported from the matched route module
*/
handle: Handle;
}
interface RouteMeta<RouteObjectType extends RouteObject = RouteObject> {
relativePath: string;
caseSensitive: boolean;
childrenIndex: number;
route: RouteObjectType;
matcher?: RegExp;
compiledParams?: CompiledPathParam[];
}
/**
* @private
* PRIVATE - DO NOT USE
*
* A "branch" of routes that match a given route pattern.
* This is an internal interface not intended for direct external usage.
*/
interface RouteBranch<RouteObjectType extends RouteObject = RouteObject> {
path: string;
score: number;
routesMeta: RouteMeta<RouteObjectType>[];
}
/**
* Returns a path with params interpolated.
*
* Param values are percent-encoded for use in a path segment: characters that
* would change the URL structure (`/`, `?`, `#`, `%`, whitespace, non-ASCII)
* are escaped, while characters that RFC 3986 allows literally in a path
* segment (`$ & + , ; = : @`) are kept as-is. Note this differs from query-string
* encoding (`encodeURIComponent`/`URLSearchParams`), where those characters are
* delimiters and must be escaped. Splat (`*`) values are encoded per segment,
* preserving `/` separators.
*
* See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
*
* @example
* import { generatePath } from "react-router";
*
* generatePath("/users/:id", { id: "123" }); // "/users/123"
* generatePath("/files/:name", { name: "a b" }); // "/files/a%20b"
* generatePath("/releases/:v", { v: "1.0.0+1" }); // "/releases/1.0.0+1"
*
* @public
* @category Utils
* @param originalPath The original path to generate.
* @param params The parameters to interpolate into the path.
* @returns The generated path with parameters interpolated.
*/
declare function generatePath<Path extends string>(originalPath: Path, params?: GeneratePathParams<Path>): string;
/**
* Used to match on some portion of a URL pathname.
*/
interface PathPattern<Path extends string = string> {
/**
* A string to match against a URL pathname. May contain `:id`-style segments
* to indicate placeholders for dynamic parameters. It May also end with `/*`
* to indicate matching the rest of the URL pathname.
*/
path: Path;
/**
* Should be `true` if the static portions of the `path` should be matched in
* the same case.
*/
caseSensitive?: boolean;
/**
* Should be `true` if this pattern should match the entire URL pathname.
*/
end?: boolean;
}
/**
* Contains info about how a {@link PathPattern} matched on a URL pathname.
*/
interface PathMatch<ParamKey extends string = string> {
/**
* The names and values of dynamic parameters in the URL.
*/
params: Params<ParamKey>;
/**
* The portion of the URL pathname that was matched.
*/
pathname: string;
/**
* The portion of the URL pathname that was matched before child routes.
*/
pathnameBase: string;
/**
* The pattern that was used to match.
*/
pattern: PathPattern;
}
/**
* Performs pattern matching on a URL pathname and returns information about
* the match.
*
* @public
* @category Utils
* @param pattern The pattern to match against the URL pathname. This can be a
* string or a {@link PathPattern} object. If a string is provided, it will be
* treated as a pattern with `caseSensitive` set to `false` and `end` set to
* `true`.
* @param pathname The URL pathname to match against the pattern.
* @returns A path match object if the pattern matches the pathname,
* or `null` if it does not match.
*/
declare function matchPath<Path extends string>(pattern: PathPattern<Path> | Path, pathname: string): PathMatch<ParamParseKey<Path>> | null;
type CompiledPathParam = {
paramName: string;
isOptional?: boolean;
};
/**
* Returns a resolved {@link Path} object relative to the given pathname.
*
* @public
* @category Utils
* @param to The path to resolve, either a string or a partial {@link Path}
* object.
* @param fromPathname The pathname to resolve the path from. Defaults to `/`.
* @returns A {@link Path} object with the resolved pathname, search, and hash.
*/
declare function resolvePath(to: To, fromPathname?: string): Path;
declare class DataWithResponseInit<D> {
type: string;
data: D;
init: ResponseInit | null;
constructor(data: D, init?: ResponseInit);
}
/**
* Create "responses" that contain `headers`/`status` without forcing
* serialization into an actual [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
*
* @example
* import { data } from "react-router";
*
* export async function action({ request }: Route.ActionArgs) {
* let formData = await request.formData();
* let item = await createItem(formData);
* return data(item, {
* headers: { "X-Custom-Header": "value" }
* status: 201,
* });
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param data The data to be included in the response.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A {@link DataWithResponseInit} instance containing the data and
* response init.
*/
declare function data<D>(data: D, init?: number | ResponseInit): DataWithResponseInit<D>;
interface TrackedPromise extends Promise<any> {
_tracked?: boolean;
_data?: any;
_error?: any;
}
type RedirectFunction = (url: string, init?: number | ResponseInit) => Response;
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
* Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* @example
* import { redirect } from "react-router";
*
* export async function loader({ request }: Route.LoaderArgs) {
* if (!isLoggedIn(request))
* throw redirect("/login");
* }
*
* // ...
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
declare const redirect: RedirectFunction;
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that will force a document reload to the new location. Sets the status code
* and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* ```tsx filename=routes/logout.tsx
* import { redirectDocument } from "react-router";
*
* import { destroySession } from "../sessions.server";
*
* export async function action({ request }: Route.ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* return redirectDocument("/", {
* headers: { "Set-Cookie": await destroySession(session) }
* });
* }
* ```
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
declare const redirectDocument: RedirectFunction;
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that will perform a [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState)
* instead of a [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
* for client-side navigation redirects. Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* @example
* import { replace } from "react-router";
*
* export async function loader() {
* return replace("/new-location");
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
declare const replace: RedirectFunction;
type ErrorResponse = {
status: number;
statusText: string;
data: any;
};
declare class ErrorResponseImpl implements ErrorResponse {
status: number;
statusText: string;
data: any;
private error?;
private internal;
constructor(status: number, statusText: string | undefined, data: any, internal?: boolean);
}
/**
* Check if the given error is an {@link ErrorResponse} generated from a 4xx/5xx
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* thrown from an [`action`](../../start/framework/route-module#action) or
* [`loader`](../../start/framework/route-module#loader) function.
*
* @example
* import { isRouteErrorResponse } from "react-router";
*
* export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
* if (isRouteErrorResponse(error)) {
* return (
* <>
* <p>Error: `${error.status}: ${error.statusText}`</p>
* <p>{error.data}</p>
* </>
* );
* }
*
* return (
* <p>Error: {error instanceof Error ? error.message : "Unknown Error"}</p>
* );
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param error The error to check.
* @returns `true` if the error is an {@link ErrorResponse}, `false` otherwise.
*/
declare function isRouteErrorResponse(error: any): error is ErrorResponse;
//#endregion
export { ActionFunction, ActionFunctionArgs, BaseRouteObject, DataRouteMatch, DataRouteObject, DataStrategyFunction, DataStrategyFunctionArgs, DataStrategyMatch, DataStrategyResult, DataWithResponseInit, ErrorResponse, ErrorResponseImpl, FormEncType, FormMethod, HTMLFormMethod, IndexRouteObject, LazyRouteFunction, LoaderFunction, LoaderFunctionArgs, MapRoutePropertiesFunction, MaybePromise, MiddlewareFunction, MiddlewareNextFunction, NonIndexRouteObject, ParamParseKey, Params, PatchRoutesOnNavigationFunction, PatchRoutesOnNavigationFunctionArgs, PathMatch, PathParam, PathPattern, RedirectFunction, RouteBranch, RouteData, RouteManifest, RouteMatch, RouteObject, RouterContext, RouterContextProvider, ShouldRevalidateFunction, ShouldRevalidateFunctionArgs, Submission, TrackedPromise, UIMatch, createContext, data, defaultMapRouteProperties, generatePath, isRouteErrorResponse, matchPath, matchRoutes, redirect, redirectDocument, replace, resolvePath };
@@ -0,0 +1,881 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ABSOLUTE_URL_REGEX, PROTOCOL_RELATIVE_URL_REGEX, normalizeProtocolRelativeUrl } from "./url.js";
import { invariant, parsePath, warning } from "./history.js";
import * as React$1 from "react";
//#region lib/router/utils.ts
/**
* Creates a type-safe {@link RouterContext} object that can be used to
* store and retrieve arbitrary values in [`action`](../../start/framework/route-module#action)s,
* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
* Similar to React's [`createContext`](https://react.dev/reference/react/createContext),
* but specifically designed for React Router's request/response lifecycle.
*
* If a `defaultValue` is provided, it will be returned from `context.get()`
* when no value has been set for the context. Otherwise, reading this context
* when no value has been set will throw an error.
*
* ```tsx filename=app/context.ts
* import { createContext } from "react-router";
*
* // Create a context for user data
* export const userContext =
* createContext<User | null>(null);
* ```
*
* ```tsx filename=app/middleware/auth.ts
* import { getUserFromSession } from "~/auth.server";
* import { userContext } from "~/context";
*
* export const authMiddleware = async ({
* context,
* request,
* }) => {
* const user = await getUserFromSession(request);
* context.set(userContext, user);
* };
* ```
*
* ```tsx filename=app/routes/profile.tsx
* import { userContext } from "~/context";
*
* export async function loader({
* context,
* }: Route.LoaderArgs) {
* const user = context.get(userContext);
*
* if (!user) {
* throw new Response("Unauthorized", { status: 401 });
* }
*
* return { user };
* }
* ```
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param defaultValue An optional default value for the context. This value
* will be returned if no value has been set for this context.
* @returns A {@link RouterContext} object that can be used with
* `context.get()` and `context.set()` in [`action`](../../start/framework/route-module#action)s,
* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
*/
function createContext(defaultValue) {
return { defaultValue };
}
/**
* Provides methods for writing/reading values in application context in a
* type-safe way. Primarily for usage with [middleware](../../how-to/middleware).
*
* @example
* import {
* createContext,
* RouterContextProvider
* } from "react-router";
*
* const userContext = createContext<User | null>(null);
* const contextProvider = new RouterContextProvider();
* contextProvider.set(userContext, getUser());
* // ^ Type-safe
* const user = contextProvider.get(userContext);
* // ^ User
*
* @public
* @category Utils
* @mode framework
* @mode data
*/
var RouterContextProvider = class {
#map = /* @__PURE__ */ new Map();
/**
* Create a new `RouterContextProvider` instance
* @param init An optional initial context map to populate the provider with
*/
constructor(init) {
if (init) for (let [context, value] of init) this.set(context, value);
}
/**
* Access a value from the context. If no value has been set for the context,
* it will return the context's `defaultValue` if provided, or throw an error
* if no `defaultValue` was set.
* @param context The context to get the value for
* @returns The value for the context, or the context's `defaultValue` if no
* value was set
*/
get(context) {
if (this.#map.has(context)) return this.#map.get(context);
if (context.defaultValue !== void 0) return context.defaultValue;
throw new Error("No value found for context");
}
/**
* Set a value for the context. If the context already has a value set, this
* will overwrite it.
*
* @param context The context to set the value for
* @param value The value to set for the context
* @returns {void}
*/
set(context, value) {
this.#map.set(context, value);
}
};
const unsupportedLazyRouteObjectKeys = new Set([
"lazy",
"caseSensitive",
"path",
"id",
"index",
"children"
]);
function isUnsupportedLazyRouteObjectKey(key) {
return unsupportedLazyRouteObjectKeys.has(key);
}
const unsupportedLazyRouteFunctionKeys = new Set([
"lazy",
"caseSensitive",
"path",
"id",
"index",
"middleware",
"children"
]);
function isUnsupportedLazyRouteFunctionKey(key) {
return unsupportedLazyRouteFunctionKeys.has(key);
}
function isIndexRoute(route) {
return route.index === true;
}
function defaultMapRouteProperties(route) {
let updates = {};
if (route.Component) {
if (route.element) warning(false, "You should not include both `Component` and `element` on your route - `Component` will be used.");
Object.assign(updates, {
element: React$1.createElement(route.Component),
Component: void 0
});
}
if (route.HydrateFallback) {
if (route.hydrateFallbackElement) warning(false, "You should not include both `HydrateFallback` and `hydrateFallbackElement` on your route - `HydrateFallback` will be used.");
Object.assign(updates, {
hydrateFallbackElement: React$1.createElement(route.HydrateFallback),
HydrateFallback: void 0
});
}
if (route.ErrorBoundary) {
if (route.errorElement) warning(false, "You should not include both `ErrorBoundary` and `errorElement` on your route - `ErrorBoundary` will be used.");
Object.assign(updates, {
errorElement: React$1.createElement(route.ErrorBoundary),
ErrorBoundary: void 0
});
}
return updates;
}
function convertRoutesToDataRoutes(routes, mapRouteProperties = defaultMapRouteProperties, parentPath = [], manifest = {}, allowInPlaceMutations = false) {
return routes.map((route, index) => {
let treePath = [...parentPath, String(index)];
let id = typeof route.id === "string" ? route.id : treePath.join("-");
invariant(route.index !== true || !route.children, `Cannot specify children on an index route`);
invariant(allowInPlaceMutations || !manifest[id], `Found a route id collision on id "${id}". Route id's must be globally unique within Data Router usages`);
if (isIndexRoute(route)) {
let indexRoute = {
...route,
id
};
manifest[id] = mergeRouteUpdates(indexRoute, mapRouteProperties(indexRoute));
return indexRoute;
} else {
let pathOrLayoutRoute = {
...route,
id,
children: void 0
};
manifest[id] = mergeRouteUpdates(pathOrLayoutRoute, mapRouteProperties(pathOrLayoutRoute));
if (route.children) pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest, allowInPlaceMutations);
return pathOrLayoutRoute;
}
});
}
function mergeRouteUpdates(route, updates) {
return Object.assign(route, {
...updates,
...typeof updates.lazy === "object" && updates.lazy != null ? { lazy: {
...route.lazy,
...updates.lazy
} } : {}
});
}
/**
* Matches the given routes to a location and returns the match data.
*
* @example
* import { matchRoutes } from "react-router";
*
* let routes = [{
* path: "/",
* Component: Root,
* children: [{
* path: "dashboard",
* Component: Dashboard,
* }]
* }];
*
* matchRoutes(routes, "/dashboard"); // [rootMatch, dashboardMatch]
*
* @public
* @category Utils
* @param routes The array of route objects to match against.
* @param locationArg The location to match against, either a string path or a
* partial {@link Location} object
* @param basename Optional base path to strip from the location before matching.
* Defaults to `/`.
* @returns An array of matched routes, or `null` if no matches were found.
*/
function matchRoutes(routes, locationArg, basename = "/") {
return matchRoutesImpl(routes, locationArg, basename, false);
}
function matchRoutesImpl(routes, locationArg, basename, allowPartial, precomputedBranches) {
let pathname = stripBasename((typeof locationArg === "string" ? parsePath(locationArg) : locationArg).pathname || "/", basename);
if (pathname == null) return null;
let branches = precomputedBranches ?? flattenAndRankRoutes(routes);
let matches = null;
let decoded = decodePath(pathname);
for (let i = 0; matches == null && i < branches.length; ++i) matches = matchRouteBranch(branches[i], decoded, allowPartial);
return matches;
}
function convertRouteMatchToUiMatch(match, loaderData) {
let { route, pathname, params } = match;
return {
id: route.id,
pathname,
params,
loaderData: loaderData[route.id],
handle: route.handle
};
}
function flattenAndRankRoutes(routes) {
let branches = flattenRoutes(routes);
rankRouteBranches(branches);
return branches;
}
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "", _hasParentOptionalSegments = false) {
let flattenRoute = (route, index, hasParentOptionalSegments = _hasParentOptionalSegments, relativePath) => {
let meta = {
relativePath: relativePath === void 0 ? route.path || "" : relativePath,
caseSensitive: route.caseSensitive === true,
childrenIndex: index,
route
};
if (meta.relativePath.startsWith("/")) {
if (!meta.relativePath.startsWith(parentPath) && hasParentOptionalSegments) return;
invariant(meta.relativePath.startsWith(parentPath), `Absolute route path "${meta.relativePath}" nested under path "${parentPath}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`);
meta.relativePath = meta.relativePath.slice(parentPath.length);
}
let path = joinPaths([parentPath, meta.relativePath]);
let routesMeta = parentsMeta.concat(meta);
if (route.children && route.children.length > 0) {
invariant(route.index !== true, `Index routes must not have child routes. Please remove all child routes from route path "${path}".`);
flattenRoutes(route.children, branches, routesMeta, path, hasParentOptionalSegments);
}
if (route.path == null && !route.index) return;
branches.push({
path,
score: computeScore(path, route.index),
routesMeta: routesMeta.map((meta, i) => {
let [matcher, params] = compilePath(meta.relativePath, meta.caseSensitive, i === routesMeta.length - 1);
return {
...meta,
matcher,
compiledParams: params
};
})
});
};
routes.forEach((route, index) => {
if (route.path === "" || !route.path?.includes("?")) flattenRoute(route, index);
else for (let exploded of explodeOptionalSegments(route.path)) flattenRoute(route, index, true, exploded);
});
return branches;
}
function explodeOptionalSegments(path) {
let segments = path.split("/");
if (segments.length === 0) return [];
let [first, ...rest] = segments;
let isOptional = first.endsWith("?");
let required = first.replace(/\?$/, "");
if (rest.length === 0) return isOptional ? [required, ""] : [required];
let restExploded = explodeOptionalSegments(rest.join("/"));
let result = [];
result.push(...restExploded.map((subpath) => subpath === "" ? required : [required, subpath].join("/")));
if (isOptional) result.push(...restExploded);
return result.map((exploded) => path.startsWith("/") && exploded === "" ? "/" : exploded);
}
function rankRouteBranches(branches) {
branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map((meta) => meta.childrenIndex), b.routesMeta.map((meta) => meta.childrenIndex)));
}
const paramRe = /^:[\w-]+$/;
const partialParamRe = /^:[\w-]+/;
const partialDynamicSegmentValue = 3.5;
const dynamicSegmentValue = 3;
const indexRouteValue = 2;
const emptySegmentValue = 1;
const staticSegmentValue = 10;
const splatPenalty = -2;
const isSplat = (s) => s === "*";
function computeScore(path, index) {
let segments = path.split("/");
let initialScore = segments.length;
if (segments.some(isSplat)) initialScore += splatPenalty;
if (index) initialScore += indexRouteValue;
return segments.filter((s) => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : partialParamRe.test(segment) ? partialDynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
}
function compareIndexes(a, b) {
return a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]) ? a[a.length - 1] - b[b.length - 1] : 0;
}
function matchRouteBranch(branch, pathname, allowPartial = false) {
let { routesMeta } = branch;
let matchedParams = {};
let matchedPathname = "/";
let matches = [];
for (let i = 0; i < routesMeta.length; ++i) {
let meta = routesMeta[i];
let end = i === routesMeta.length - 1;
let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
let pattern = {
path: meta.relativePath,
caseSensitive: meta.caseSensitive,
end
};
let match = meta.matcher && meta.compiledParams ? matchPathImpl(pattern, remainingPathname, meta.matcher, meta.compiledParams) : matchPath(pattern, remainingPathname);
let route = meta.route;
if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) match = matchPath({
path: meta.relativePath,
caseSensitive: meta.caseSensitive,
end: false
}, remainingPathname);
if (!match) return null;
Object.assign(matchedParams, match.params);
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
route
});
if (match.pathnameBase !== "/") matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
return matches;
}
/**
* Characters that `encodeURIComponent` escapes but that are valid literally in
* a URL path segment. Per RFC 3986 §3.3, a path segment is made of `pchar`:
*
* ```
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* ```
*
* `encodeURIComponent` targets query-string values, where `$ & + , ; = : @`
* are delimiters and must be escaped — but in a path segment they carry no
* special meaning, and browsers keep them literal in `location.pathname`.
* (`! ' ( ) *` and the unreserved set are already left alone by
* `encodeURIComponent`, so they need no restoring.)
*/
const PATH_PARAM_OVERESCAPED = {
"%24": "$",
"%26": "&",
"%2B": "+",
"%2C": ",",
"%3A": ":",
"%3B": ";",
"%3D": "=",
"%40": "@"
};
/**
* Encodes a param value for interpolation into a single URL path segment.
*
* Escapes characters that would break the path (`/ ? # %`, whitespace,
* non-ASCII, …) while leaving characters that RFC 3986 permits literally in a
* path segment untouched. Escaping those would needlessly rewrite URLs — e.g.
* a semver build param `1.0.0+1` would become `1.0.0%2B1` even though browsers
* display and match the `+` literally in `location.pathname`.
*
* See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3))
*
* @param value The param value to encode.
* @returns The encoded value, safe for use as a single path segment.
*/
function encodePathParam(value) {
return encodeURIComponent(value).replace(/%(?:24|26|2B|2C|3A|3B|3D|40)/g, (match) => PATH_PARAM_OVERESCAPED[match]);
}
/**
* Returns a path with params interpolated.
*
* Param values are percent-encoded for use in a path segment: characters that
* would change the URL structure (`/`, `?`, `#`, `%`, whitespace, non-ASCII)
* are escaped, while characters that RFC 3986 allows literally in a path
* segment (`$ & + , ; = : @`) are kept as-is. Note this differs from query-string
* encoding (`encodeURIComponent`/`URLSearchParams`), where those characters are
* delimiters and must be escaped. Splat (`*`) values are encoded per segment,
* preserving `/` separators.
*
* See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
*
* @example
* import { generatePath } from "react-router";
*
* generatePath("/users/:id", { id: "123" }); // "/users/123"
* generatePath("/files/:name", { name: "a b" }); // "/files/a%20b"
* generatePath("/releases/:v", { v: "1.0.0+1" }); // "/releases/1.0.0+1"
*
* @public
* @category Utils
* @param originalPath The original path to generate.
* @param params The parameters to interpolate into the path.
* @returns The generated path with parameters interpolated.
*/
function generatePath(originalPath, params = {}) {
let path = originalPath;
if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
warning(false, `Route path "${path}" will be treated as if it were "${path.replace(/\*$/, "/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${path.replace(/\*$/, "/*")}".`);
path = path.replace(/\*$/, "/*");
}
const prefix = path.startsWith("/") ? "/" : "";
const stringify = (p) => p == null ? "" : typeof p === "string" ? p : String(p);
return prefix + path.split(/\/+/).map((segment, index, array) => {
if (index === array.length - 1 && segment === "*") return stringify(params["*"]);
const keyMatch = segment.match(/^:([\w-]+)(\??)(.*)/);
if (keyMatch) {
const [, key, optional, suffix] = keyMatch;
let param = params[key];
invariant(optional === "?" || param != null, `Missing ":${key}" param`);
return encodePathParam(stringify(param)) + suffix;
}
return segment.replace(/\?$/g, "");
}).filter((segment) => !!segment).join("/");
}
/**
* Performs pattern matching on a URL pathname and returns information about
* the match.
*
* @public
* @category Utils
* @param pattern The pattern to match against the URL pathname. This can be a
* string or a {@link PathPattern} object. If a string is provided, it will be
* treated as a pattern with `caseSensitive` set to `false` and `end` set to
* `true`.
* @param pathname The URL pathname to match against the pattern.
* @returns A path match object if the pattern matches the pathname,
* or `null` if it does not match.
*/
function matchPath(pattern, pathname) {
if (typeof pattern === "string") pattern = {
path: pattern,
caseSensitive: false,
end: true
};
let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
return matchPathImpl(pattern, pathname, matcher, compiledParams);
}
function matchPathImpl(pattern, pathname, matcher, compiledParams) {
let match = pathname.match(matcher);
if (!match) return null;
let matchedPathname = match[0];
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
let captureGroups = match.slice(1);
return {
params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
if (paramName === "*") {
let splatValue = captureGroups[index] || "";
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
}
const value = captureGroups[index];
if (isOptional && !value) memo[paramName] = void 0;
else memo[paramName] = (value || "").replace(/%2F/g, "/");
return memo;
}, {}),
pathname: matchedPathname,
pathnameBase,
pattern
};
}
function compilePath(path, caseSensitive = false, end = true) {
warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), `Route path "${path}" will be treated as if it were "${path.replace(/\*$/, "/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${path.replace(/\*$/, "/*")}".`);
let params = [];
let regexpSource = "^" + path.replace(/\/*\*?$/, "").replace(/^\/*/, "/").replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace(/\/:([\w-]+)(\?)?/g, (match, paramName, isOptional, index, str) => {
params.push({
paramName,
isOptional: isOptional != null
});
if (isOptional) {
let nextChar = str.charAt(index + match.length);
if (nextChar && nextChar !== "/") return "/([^\\/]*)";
return "(?:/([^\\/]*))?";
}
return "/([^\\/]+)";
}).replace(/\/([\w-]+)\?(?=\/|$|\()/g, "(?:/$1)?");
if (path.endsWith("*")) {
params.push({ paramName: "*" });
regexpSource += path === "*" || path === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$";
} else if (end) regexpSource += "\\/*$";
else if (path !== "" && path !== "/") regexpSource += "(?:(?=\\/|$))";
return [new RegExp(regexpSource, caseSensitive ? void 0 : "i"), params];
}
function decodePath(value) {
try {
return value.split("/").map((v) => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
} catch (error) {
warning(false, `The URL path "${value}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${error}).`);
return value;
}
}
function stripBasename(pathname, basename) {
if (basename === "/") return pathname;
if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) return null;
let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
let nextChar = pathname.charAt(startIndex);
if (nextChar && nextChar !== "/") return null;
return pathname.slice(startIndex) || "/";
}
function prependBasename({ basename, pathname }) {
return pathname === "/" ? basename : joinPaths([basename, pathname]);
}
const isAbsoluteUrl = (url) => ABSOLUTE_URL_REGEX.test(url);
/**
* Returns a resolved {@link Path} object relative to the given pathname.
*
* @public
* @category Utils
* @param to The path to resolve, either a string or a partial {@link Path}
* object.
* @param fromPathname The pathname to resolve the path from. Defaults to `/`.
* @returns A {@link Path} object with the resolved pathname, search, and hash.
*/
function resolvePath(to, fromPathname = "/") {
let { pathname: toPathname, search = "", hash = "" } = typeof to === "string" ? parsePath(to) : to;
let pathname;
if (toPathname) {
toPathname = removeDoubleSlashes(toPathname);
if (toPathname.startsWith("/")) pathname = resolvePathname(toPathname.substring(1), "/");
else pathname = resolvePathname(toPathname, fromPathname);
} else pathname = fromPathname;
return {
pathname,
search: normalizeSearch(search),
hash: normalizeHash(hash)
};
}
function resolvePathname(relativePath, fromPathname) {
let segments = removeTrailingSlash(fromPathname).split("/");
relativePath.split("/").forEach((segment) => {
if (segment === "..") {
if (segments.length > 1) segments.pop();
} else if (segment !== ".") segments.push(segment);
});
return segments.length > 1 ? segments.join("/") : "/";
}
function getInvalidPathError(char, field, dest, path) {
return `Cannot include a '${char}' character in a manually specified \`to.${field}\` field [${JSON.stringify(path)}]. Please separate it out to the \`to.${dest}\` field. Alternatively you may provide the full path as a string in <Link to="..."> and the router will parse it for you.`;
}
function getPathContributingMatches(matches) {
return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
}
function getResolveToMatches(matches) {
let pathMatches = getPathContributingMatches(matches);
return pathMatches.map((match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
}
function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = false) {
let to;
if (typeof toArg === "string") to = parsePath(toArg);
else {
to = { ...toArg };
invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
}
let isEmptyPath = toArg === "" || to.pathname === "";
let toPathname = isEmptyPath ? "/" : to.pathname;
let from;
if (toPathname == null) from = locationPathname;
else {
let routePathnameIndex = routePathnames.length - 1;
if (!isPathRelative && toPathname.startsWith("..")) {
let toSegments = toPathname.split("/");
while (toSegments[0] === "..") {
toSegments.shift();
routePathnameIndex -= 1;
}
to.pathname = toSegments.join("/");
}
from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
}
let path = resolvePath(to, from);
let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) path.pathname += "/";
return path;
}
const removeDoubleSlashes = (path) => path.replace(/[\\/]{2,}/g, "/");
const joinPaths = (paths) => removeDoubleSlashes(paths.join("/"));
const removeTrailingSlash = (path) => path.replace(/\/+$/, "");
const normalizePathname = (pathname) => removeTrailingSlash(pathname).replace(/^\/*/, "/");
const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
const normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
var DataWithResponseInit = class {
type = "DataWithResponseInit";
data;
init;
constructor(data, init) {
this.data = data;
this.init = init || null;
}
};
/**
* Create "responses" that contain `headers`/`status` without forcing
* serialization into an actual [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
*
* @example
* import { data } from "react-router";
*
* export async function action({ request }: Route.ActionArgs) {
* let formData = await request.formData();
* let item = await createItem(formData);
* return data(item, {
* headers: { "X-Custom-Header": "value" }
* status: 201,
* });
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param data The data to be included in the response.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A {@link DataWithResponseInit} instance containing the data and
* response init.
*/
function data(data, init) {
return new DataWithResponseInit(data, typeof init === "number" ? { status: init } : init);
}
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
* Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* @example
* import { redirect } from "react-router";
*
* export async function loader({ request }: Route.LoaderArgs) {
* if (!isLoggedIn(request))
* throw redirect("/login");
* }
*
* // ...
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
const redirect = (url, init = 302) => {
let responseInit = init;
if (typeof responseInit === "number") responseInit = { status: responseInit };
else if (typeof responseInit.status === "undefined") responseInit.status = 302;
let headers = new Headers(responseInit.headers);
headers.set("Location", url);
return new Response(null, {
...responseInit,
headers
});
};
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that will force a document reload to the new location. Sets the status code
* and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* This utility accepts absolute URLs and can navigate to external domains, so
* the application should validate any user-supplied inputs to redirects.
*
* ```tsx filename=routes/logout.tsx
* import { redirectDocument } from "react-router";
*
* import { destroySession } from "../sessions.server";
*
* export async function action({ request }: Route.ActionArgs) {
* let session = await getSession(request.headers.get("Cookie"));
* return redirectDocument("/", {
* headers: { "Set-Cookie": await destroySession(session) }
* });
* }
* ```
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
const redirectDocument = (url, init) => {
let response = redirect(url, init);
response.headers.set("X-Remix-Reload-Document", "true");
return response;
};
/**
* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that will perform a [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState)
* instead of a [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
* for client-side navigation redirects. Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
*
* @example
* import { replace } from "react-router";
*
* export async function loader() {
* return replace("/new-location");
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param url The URL to redirect to.
* @param init The status code or a `ResponseInit` object to be included in the
* response.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
* header.
*/
const replace = (url, init) => {
let response = redirect(url, init);
response.headers.set("X-Remix-Replace", "true");
return response;
};
const SUPPORTED_ERROR_TYPES = [
"EvalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError"
];
var ErrorResponseImpl = class {
status;
statusText;
data;
error;
internal;
constructor(status, statusText, data, internal = false) {
this.status = status;
this.statusText = statusText || "";
this.internal = internal;
if (data instanceof Error) {
this.data = data.toString();
this.error = data;
} else this.data = data;
}
};
/**
* Check if the given error is an {@link ErrorResponse} generated from a 4xx/5xx
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* thrown from an [`action`](../../start/framework/route-module#action) or
* [`loader`](../../start/framework/route-module#loader) function.
*
* @example
* import { isRouteErrorResponse } from "react-router";
*
* export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
* if (isRouteErrorResponse(error)) {
* return (
* <>
* <p>Error: `${error.status}: ${error.statusText}`</p>
* <p>{error.data}</p>
* </>
* );
* }
*
* return (
* <p>Error: {error instanceof Error ? error.message : "Unknown Error"}</p>
* );
* }
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param error The error to check.
* @returns `true` if the error is an {@link ErrorResponse}, `false` otherwise.
*/
function isRouteErrorResponse(error) {
return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
}
function getRoutePattern(matches) {
return joinPaths(matches.map((m) => m.route.path).filter(Boolean)) || "/";
}
function createDataFunctionUrl(request, path) {
let url = new URL(typeof request === "string" || request instanceof URL ? request : request.url);
let parsed = typeof path === "string" ? parsePath(path) : path;
url.pathname = parsed.pathname || "/";
if (parsed.search) {
let searchParams = new URLSearchParams(parsed.search);
let indexValues = searchParams.getAll("index");
searchParams.delete("index");
for (let value of indexValues.filter(Boolean)) searchParams.append("index", value);
let search = searchParams.toString();
url.search = search ? `?${search}` : "";
} else url.search = "";
url.hash = parsed.hash || "";
return url;
}
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
function parseToInfo(_to, basename) {
let to = _to;
if (typeof to !== "string" || !ABSOLUTE_URL_REGEX.test(to)) return {
absoluteURL: void 0,
isExternal: false,
to
};
let absoluteURL = to;
let isExternal = false;
if (isBrowser) try {
let currentUrl = new URL(window.location.href);
let targetUrl = PROTOCOL_RELATIVE_URL_REGEX.test(to) ? new URL(normalizeProtocolRelativeUrl(to, currentUrl.protocol)) : new URL(to);
let path = stripBasename(targetUrl.pathname, basename);
if (targetUrl.origin === currentUrl.origin && path != null) to = path + targetUrl.search + targetUrl.hash;
else isExternal = true;
} catch (e) {
warning(false, `<Link to="${to}"> contains an invalid URL which will probably break when clicked - please update to a valid URL path.`);
}
return {
absoluteURL,
isExternal,
to
};
}
//#endregion
export { ErrorResponseImpl, RouterContextProvider, SUPPORTED_ERROR_TYPES, compilePath, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createContext, createDataFunctionUrl, data, decodePath, defaultMapRouteProperties, encodePathParam, flattenAndRankRoutes, generatePath, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isBrowser, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, joinPaths, matchPath, matchRoutes, matchRoutesImpl, parseToInfo, prependBasename, redirect, redirectDocument, removeDoubleSlashes, removeTrailingSlash, replace, resolvePath, resolveTo, stripBasename };
@@ -0,0 +1,137 @@
import { RouterInit } from "../router/router.js";
import { RSCPayload } from "./server.rsc.js";
import * as React$1 from "react";
//#region lib/rsc/browser.d.ts
type BrowserCreateFromReadableStreamFunction = (body: ReadableStream<Uint8Array>, {
temporaryReferences
}: {
temporaryReferences: unknown;
}) => Promise<unknown>;
type EncodeReplyFunction = (args: unknown[], options: {
temporaryReferences: unknown;
}) => Promise<BodyInit>;
/**
* Create a React `callServer` implementation for React Router.
*
* @example
* import {
* createFromReadableStream,
* createTemporaryReferenceSet,
* encodeReply,
* setServerCallback,
* } from "@vitejs/plugin-rsc/browser";
* import { unstable_createCallServer as createCallServer } from "react-router";
*
* setServerCallback(
* createCallServer({
* createFromReadableStream,
* createTemporaryReferenceSet,
* encodeReply,
* })
* );
*
* @name unstable_createCallServer
* @public
* @category RSC
* @mode data
* @param opts Options
* @param opts.createFromReadableStream Your `react-server-dom-xyz/client`'s
* `createFromReadableStream`. Used to decode payloads from the server.
* @param opts.createTemporaryReferenceSet A function that creates a temporary
* reference set for the [RSC](https://react.dev/reference/rsc/server-components)
* payload.
* @param opts.encodeReply Your `react-server-dom-xyz/client`'s `encodeReply`.
* Used when sending payloads to the server.
* @param opts.fetch Optional [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
* implementation. Defaults to global [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
* @returns A function that can be used to call server actions.
*/
declare function createCallServer({
createFromReadableStream,
createTemporaryReferenceSet,
encodeReply,
fetch: fetchImplementation
}: {
createFromReadableStream: BrowserCreateFromReadableStreamFunction;
createTemporaryReferenceSet: () => unknown;
encodeReply: EncodeReplyFunction;
fetch?: (request: Request) => Promise<Response>;
}): (id: string, args: unknown[]) => Promise<unknown>;
/**
* Props for the {@link unstable_RSCHydratedRouter} component.
*
* @name unstable_RSCHydratedRouterProps
* @category Types
*/
interface RSCHydratedRouterProps {
/**
* Your `react-server-dom-xyz/client`'s `createFromReadableStream` function,
* used to decode payloads from the server.
*/
createFromReadableStream: BrowserCreateFromReadableStreamFunction;
/**
* Optional fetch implementation. Defaults to global [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
*/
fetch?: (request: Request) => Promise<Response>;
/**
* The decoded {@link unstable_RSCPayload} to hydrate.
*/
payload: RSCPayload;
/**
* A function that returns an {@link RouterContextProvider} instance
* which is provided as the `context` argument to client [`action`](../../start/data/route-object#action)s,
* [`loader`](../../start/data/route-object#loader)s and [middleware](../../how-to/middleware).
* This function is called to generate a fresh `context` instance on each
* navigation or fetcher call.
*/
getContext?: RouterInit["getContext"];
}
/**
* Hydrates a server rendered {@link unstable_RSCPayload} in the browser.
*
* @example
* import { startTransition, StrictMode } from "react";
* import { hydrateRoot } from "react-dom/client";
* import {
* unstable_getRSCStream as getRSCStream,
* unstable_RSCHydratedRouter as RSCHydratedRouter,
* } from "react-router";
* import type { unstable_RSCPayload as RSCPayload } from "react-router";
*
* createFromReadableStream(getRSCStream()).then((payload) =>
* startTransition(async () => {
* hydrateRoot(
* document,
* <StrictMode>
* <RSCHydratedRouter
* createFromReadableStream={createFromReadableStream}
* payload={payload}
* />
* </StrictMode>,
* { formState: await getFormState(payload) },
* );
* }),
* );
*
* @name unstable_RSCHydratedRouter
* @public
* @category RSC
* @mode data
* @param props Props
* @param {unstable_RSCHydratedRouterProps.createFromReadableStream} props.createFromReadableStream n/a
* @param {unstable_RSCHydratedRouterProps.fetch} props.fetch n/a
* @param {unstable_RSCHydratedRouterProps.getContext} props.getContext n/a
* @param {unstable_RSCHydratedRouterProps.payload} props.payload n/a
* @returns A hydrated {@link DataRouter} that can be used to navigate and
* render routes.
*/
declare function RSCHydratedRouter({
createFromReadableStream,
fetch: fetchImplementation,
payload,
getContext
}: RSCHydratedRouterProps): React$1.JSX.Element;
//#endregion
export { BrowserCreateFromReadableStreamFunction, EncodeReplyFunction, RSCHydratedRouter, RSCHydratedRouterProps, createCallServer };
@@ -0,0 +1,612 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { PROTOCOL_RELATIVE_URL_REGEX } from "../router/url.js";
import { createBrowserHistory, createPath, invariant } from "../router/history.js";
import { ErrorResponseImpl, createContext, resolvePath } from "../router/utils.js";
import { createRouter, hasInvalidProtocol, isMutationMethod } from "../router/router.js";
import { RSCRouterContext } from "../context.js";
import { RouterProvider } from "../components.js";
import { createRequestInit } from "../dom/ssr/data.js";
import { getSingleFetchDataStrategyImpl, singleFetchUrl, stripIndexParam } from "../dom/ssr/single-fetch.js";
import { noActionDefinedError, shouldHydrateRouteLoader } from "../dom/ssr/routes.js";
import { getPathsWithAncestors, handleClientVersionMismatch } from "../dom/ssr/fog-of-war.js";
import { FrameworkContext, setIsHydrated } from "../dom/ssr/components.js";
import { RSCRouterGlobalErrorBoundary } from "./errorBoundaries.js";
import { populateRSCRouteModules } from "./route-modules.js";
import { getHydrationData } from "../dom/ssr/hydration.js";
import * as React$1 from "react";
import * as ReactDOM from "react-dom";
//#region lib/rsc/browser.tsx
const defaultManifestPath = "/__manifest";
/**
* Create a React `callServer` implementation for React Router.
*
* @example
* import {
* createFromReadableStream,
* createTemporaryReferenceSet,
* encodeReply,
* setServerCallback,
* } from "@vitejs/plugin-rsc/browser";
* import { unstable_createCallServer as createCallServer } from "react-router";
*
* setServerCallback(
* createCallServer({
* createFromReadableStream,
* createTemporaryReferenceSet,
* encodeReply,
* })
* );
*
* @name unstable_createCallServer
* @public
* @category RSC
* @mode data
* @param opts Options
* @param opts.createFromReadableStream Your `react-server-dom-xyz/client`'s
* `createFromReadableStream`. Used to decode payloads from the server.
* @param opts.createTemporaryReferenceSet A function that creates a temporary
* reference set for the [RSC](https://react.dev/reference/rsc/server-components)
* payload.
* @param opts.encodeReply Your `react-server-dom-xyz/client`'s `encodeReply`.
* Used when sending payloads to the server.
* @param opts.fetch Optional [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
* implementation. Defaults to global [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
* @returns A function that can be used to call server actions.
*/
function createCallServer({ createFromReadableStream, createTemporaryReferenceSet, encodeReply, fetch: fetchImplementation = fetch }) {
const globalVar = window;
let landedActionId = 0;
return async (id, args) => {
let actionId = globalVar.__routerActionID = (globalVar.__routerActionID ??= 0) + 1;
const temporaryReferences = createTemporaryReferenceSet();
const payloadPromise = fetchImplementation(new Request(location.href, {
body: await encodeReply(args, { temporaryReferences }),
method: "POST",
headers: {
Accept: "text/x-component",
"rsc-action-id": id
}
})).then((response) => {
if (!response.body) throw new Error("No response body");
return createFromReadableStream(response.body, { temporaryReferences });
});
React$1.startTransition(() => Promise.resolve(payloadPromise).then(async (payload) => {
if (payload.type === "redirect") {
let location = normalizeRedirectLocation(payload.location);
if (payload.reload || isExternalLocation(location)) {
if (hasInvalidProtocol(location)) throw new Error("Invalid redirect location");
window.location.href = location;
return;
}
React$1.startTransition(() => {
globalVar.__reactRouterDataRouter.navigate(location, { replace: payload.replace });
});
return;
}
if (payload.type !== "action") throw new Error("Unexpected payload type");
const rerender = await payload.rerender;
if (rerender && landedActionId < actionId && globalVar.__routerActionID <= actionId) {
if (rerender.type === "redirect") {
let location = normalizeRedirectLocation(rerender.location);
if (rerender.reload || isExternalLocation(location)) {
if (hasInvalidProtocol(location)) throw new Error("Invalid redirect location");
window.location.href = location;
return;
}
React$1.startTransition(() => {
globalVar.__reactRouterDataRouter.navigate(location, { replace: rerender.replace });
});
return;
}
React$1.startTransition(() => {
let lastMatch;
for (const match of rerender.matches) {
globalVar.__reactRouterDataRouter.patchRoutes(lastMatch?.id ?? null, [createRouteFromServerManifest(match)], true);
lastMatch = match;
}
window.__reactRouterDataRouter._internalSetStateDoNotUseOrYouWillBreakYourApp({
loaderData: Object.assign({}, globalVar.__reactRouterDataRouter.state.loaderData, rerender.loaderData),
errors: rerender.errors ? Object.assign({}, globalVar.__reactRouterDataRouter.state.errors, rerender.errors) : null
});
});
}
}).catch(() => {}));
return payloadPromise.then((payload) => {
if (payload.type !== "action" && payload.type !== "redirect") throw new Error("Unexpected payload type");
return payload.actionResult;
});
};
}
function createRouterFromPayload({ fetchImplementation, createFromReadableStream, getContext, payload }) {
const globalVar = window;
if (globalVar.__reactRouterDataRouter && globalVar.__reactRouterRouteModules) return {
router: globalVar.__reactRouterDataRouter,
routeModules: globalVar.__reactRouterRouteModules
};
if (payload.type !== "render") throw new Error("Invalid payload type");
let { clientVersion } = payload;
globalVar.__reactRouterRouteModules = globalVar.__reactRouterRouteModules ?? {};
populateRSCRouteModules(globalVar.__reactRouterRouteModules, payload.matches);
let routes = payload.matches.reduceRight((previous, match) => {
const route = createRouteFromServerManifest(match, payload);
if (previous.length > 0) route.children = previous;
else if (!route.index) route.children = [];
return [route];
}, []);
let applyPatchesPromise;
globalVar.__reactRouterDataRouter = createRouter({
routes,
getContext,
basename: payload.basename,
history: createBrowserHistory(),
hydrationData: getHydrationData({
state: {
loaderData: payload.loaderData,
actionData: payload.actionData,
errors: payload.errors
},
routes,
getRouteInfo: (routeId) => {
let match = payload.matches.find((m) => m.id === routeId);
invariant(match, "Route not found in payload");
return {
clientLoader: match.clientLoader,
hasLoader: match.hasLoader,
hasHydrateFallback: match.hydrateFallbackElement != null
};
},
location: payload.location,
basename: payload.basename,
isSpaMode: false
}),
async patchRoutesOnNavigation({ path, signal, fetcherKey }) {
if (payload.routeDiscovery.mode === "initial") {
if (!applyPatchesPromise) applyPatchesPromise = (async () => {
if (!payload.patches) return;
let patches = await payload.patches;
React$1.startTransition(() => {
patches.forEach((p) => {
window.__reactRouterDataRouter.patchRoutes(p.parentId ?? null, [createRouteFromServerManifest(p)]);
});
});
})();
await applyPatchesPromise;
return;
}
if (discoveredPaths.has(path)) return;
let { state } = globalVar.__reactRouterDataRouter;
await fetchAndApplyManifestPatches([path], createFromReadableStream, fetchImplementation, clientVersion, fetcherKey ? window.location.href : createPath(state.navigation.location || state.location), signal);
},
dataStrategy: getRSCSingleFetchDataStrategy(() => globalVar.__reactRouterDataRouter, true, createFromReadableStream, fetchImplementation, clientVersion)
});
if (globalVar.__reactRouterDataRouter.state.initialized) {
globalVar.__routerInitialized = true;
globalVar.__reactRouterDataRouter.initialize();
} else globalVar.__routerInitialized = false;
let lastLoaderData = void 0;
globalVar.__reactRouterDataRouter.subscribe(({ loaderData, actionData }) => {
if (lastLoaderData !== loaderData) globalVar.__routerActionID = (globalVar.__routerActionID ??= 0) + 1;
});
globalVar.__reactRouterDataRouter._updateRoutesForHMR = (routeUpdateByRouteId) => {
const oldRoutes = window.__reactRouterDataRouter.routes;
const newRoutes = [];
function walkRoutes(routes, parentId) {
return routes.map((route) => {
const routeUpdate = routeUpdateByRouteId.get(route.id);
if (routeUpdate) {
const { routeModule, hasAction, hasComponent, hasLoader } = routeUpdate;
const newRoute = createRouteFromServerManifest({
clientAction: routeModule.clientAction,
clientLoader: routeModule.clientLoader,
element: route.element,
errorElement: route.errorElement,
handle: route.handle,
hasAction,
hasComponent,
hasLoader,
hydrateFallbackElement: route.hydrateFallbackElement,
id: route.id,
index: route.index,
links: routeModule.links,
meta: routeModule.meta,
parentId,
path: route.path,
shouldRevalidate: routeModule.shouldRevalidate
});
if (route.children) newRoute.children = walkRoutes(route.children, route.id);
return newRoute;
}
const updatedRoute = { ...route };
if (route.children) updatedRoute.children = walkRoutes(route.children, route.id);
return updatedRoute;
});
}
newRoutes.push(...walkRoutes(oldRoutes, void 0));
window.__reactRouterDataRouter._internalSetRoutes(newRoutes);
};
return {
router: globalVar.__reactRouterDataRouter,
routeModules: globalVar.__reactRouterRouteModules
};
}
const renderedRoutesContext = createContext();
function getRSCSingleFetchDataStrategy(getRouter, ssr, createFromReadableStream, fetchImplementation, clientVersion) {
let dataStrategy = getSingleFetchDataStrategyImpl(getRouter, (match) => {
let M = match;
return {
hasLoader: M.route.hasLoader,
hasClientLoader: M.route.hasClientLoader
};
}, getFetchAndDecodeViaRSC(getRouter, createFromReadableStream, fetchImplementation, clientVersion), ssr, (match) => {
let M = match;
return !M.route.hasComponent || M.route.element != null;
});
return async (args) => args.runClientMiddleware(async () => {
args.context.set(renderedRoutesContext, []);
let results = await dataStrategy(args);
const renderedRoutesById = /* @__PURE__ */ new Map();
for (const route of args.context.get(renderedRoutesContext)) {
if (!renderedRoutesById.has(route.id)) renderedRoutesById.set(route.id, []);
renderedRoutesById.get(route.id).push(route);
}
React$1.startTransition(() => {
for (const match of args.matches) {
const renderedRoutes = renderedRoutesById.get(match.route.id);
if (renderedRoutes) for (const rendered of renderedRoutes) window.__reactRouterDataRouter.patchRoutes(rendered.parentId ?? null, [createRouteFromServerManifest(rendered)], true);
}
});
return results;
});
}
function getFetchAndDecodeViaRSC(getRouter, createFromReadableStream, fetchImplementation, clientVersion) {
return async (args, targetRoutes) => {
let { request, context } = args;
let url = singleFetchUrl(request.url, "rsc");
if (request.method === "GET") {
url = stripIndexParam(url);
if (targetRoutes) url.searchParams.set("_routes", targetRoutes.join(","));
}
let res = await fetchImplementation(new Request(url, await createRequestInit(request)));
if (res.status >= 400 && !res.headers.has("X-Remix-Response")) throw new ErrorResponseImpl(res.status, res.statusText, await res.text());
invariant(res.body, "No response body to decode");
try {
const payload = await createFromReadableStream(res.body, { temporaryReferences: void 0 });
if (payload.type === "redirect") return {
status: res.status,
data: { redirect: {
redirect: payload.location,
reload: payload.reload,
replace: payload.replace,
revalidate: false,
status: payload.status
} }
};
if (payload.type !== "render") throw new Error("Unexpected payload type");
if (clientVersion !== void 0 && await handleClientVersionMismatch(payload.clientVersion !== clientVersion, clientVersion, createPath(getRouter().state.navigation.location || getRouter().state.location))) return new Promise(() => {});
context.get(renderedRoutesContext).push(...payload.matches);
let results = { routes: {} };
const dataKey = isMutationMethod(request.method) ? "actionData" : "loaderData";
for (let [routeId, data] of Object.entries(payload[dataKey] || {})) results.routes[routeId] = { data };
if (payload.errors) for (let [routeId, error] of Object.entries(payload.errors)) results.routes[routeId] = { error };
return {
status: res.status,
data: results
};
} catch (cause) {
throw new Error("Unable to decode RSC response", { cause });
}
};
}
/**
* Hydrates a server rendered {@link unstable_RSCPayload} in the browser.
*
* @example
* import { startTransition, StrictMode } from "react";
* import { hydrateRoot } from "react-dom/client";
* import {
* unstable_getRSCStream as getRSCStream,
* unstable_RSCHydratedRouter as RSCHydratedRouter,
* } from "react-router";
* import type { unstable_RSCPayload as RSCPayload } from "react-router";
*
* createFromReadableStream(getRSCStream()).then((payload) =>
* startTransition(async () => {
* hydrateRoot(
* document,
* <StrictMode>
* <RSCHydratedRouter
* createFromReadableStream={createFromReadableStream}
* payload={payload}
* />
* </StrictMode>,
* { formState: await getFormState(payload) },
* );
* }),
* );
*
* @name unstable_RSCHydratedRouter
* @public
* @category RSC
* @mode data
* @param props Props
* @param {unstable_RSCHydratedRouterProps.createFromReadableStream} props.createFromReadableStream n/a
* @param {unstable_RSCHydratedRouterProps.fetch} props.fetch n/a
* @param {unstable_RSCHydratedRouterProps.getContext} props.getContext n/a
* @param {unstable_RSCHydratedRouterProps.payload} props.payload n/a
* @returns A hydrated {@link DataRouter} that can be used to navigate and
* render routes.
*/
function RSCHydratedRouter({ createFromReadableStream, fetch: fetchImplementation = fetch, payload, getContext }) {
if (payload.type !== "render") throw new Error("Invalid payload type");
let { routeDiscovery, clientVersion } = payload;
let { router, routeModules } = React$1.useMemo(() => createRouterFromPayload({
payload,
fetchImplementation,
getContext,
createFromReadableStream
}), [
createFromReadableStream,
payload,
fetchImplementation,
getContext
]);
React$1.useEffect(() => {
setIsHydrated();
}, []);
React$1.useLayoutEffect(() => {
const globalVar = window;
if (!globalVar.__routerInitialized) {
globalVar.__routerInitialized = true;
globalVar.__reactRouterDataRouter.initialize();
}
}, []);
let [{ routes, state }, setState] = React$1.useState(() => ({
routes: cloneRoutes(router.routes),
state: router.state
}));
React$1.useLayoutEffect(() => router.subscribe((newState) => {
if (diffRoutes(router.routes, routes)) React$1.startTransition(() => {
setState({
routes: cloneRoutes(router.routes),
state: newState
});
});
}), [
router.subscribe,
routes,
router
]);
const transitionEnabledRouter = React$1.useMemo(() => ({
...router,
state,
routes
}), [
router,
routes,
state
]);
React$1.useEffect(() => {
if (routeDiscovery.mode === "initial" || window.navigator?.connection?.saveData === true) return;
function registerElement(el) {
let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
if (!path) return;
let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
if (!discoveredPaths.has(pathname)) nextPaths.add(pathname);
}
async function fetchPatches() {
document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
let paths = Array.from(nextPaths.keys()).filter((path) => {
if (discoveredPaths.has(path)) {
nextPaths.delete(path);
return false;
}
return true;
});
if (paths.length === 0) return;
try {
await fetchAndApplyManifestPatches(paths, createFromReadableStream, fetchImplementation, clientVersion, null);
} catch (e) {
console.error("Failed to fetch manifest patches", e);
}
}
let debouncedFetchPatches = debounce(fetchPatches, 100);
fetchPatches();
new MutationObserver(() => debouncedFetchPatches()).observe(document.documentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: [
"data-discover",
"href",
"action"
]
});
}, [
routeDiscovery,
createFromReadableStream,
fetchImplementation,
clientVersion
]);
const frameworkContext = {
future: {},
isSpaMode: false,
ssr: true,
criticalCss: "",
manifest: {
routes: {},
version: "1",
url: "",
entry: {
module: "",
imports: []
}
},
routeDiscovery: payload.routeDiscovery.mode === "initial" ? {
mode: "initial",
manifestPath: defaultManifestPath
} : {
mode: "lazy",
manifestPath: payload.routeDiscovery.manifestPath || defaultManifestPath
},
routeModules
};
return /* @__PURE__ */ React$1.createElement(RSCRouterContext.Provider, { value: true }, /* @__PURE__ */ React$1.createElement(RSCRouterGlobalErrorBoundary, { location: state.location }, /* @__PURE__ */ React$1.createElement(FrameworkContext.Provider, { value: frameworkContext }, /* @__PURE__ */ React$1.createElement(RouterProvider, {
router: transitionEnabledRouter,
flushSync: ReactDOM.flushSync
}))));
}
function createRouteFromServerManifest(match, payload) {
let hasInitialData = payload && match.id in payload.loaderData;
let initialData = payload?.loaderData[match.id];
let hasInitialError = payload?.errors && match.id in payload.errors;
let initialError = payload?.errors?.[match.id];
let isHydrationRequest = match.clientLoader?.hydrate === true || !match.hasLoader || match.hasComponent && !match.element;
invariant(window.__reactRouterRouteModules);
populateRSCRouteModules(window.__reactRouterRouteModules, match);
let dataRoute = {
id: match.id,
element: match.element,
errorElement: match.errorElement,
handle: match.handle,
hydrateFallbackElement: match.hydrateFallbackElement,
index: match.index,
loader: match.clientLoader ? async (args, singleFetch) => {
let _isHydrationRequest = isHydrationRequest;
isHydrationRequest = false;
return await match.clientLoader({
...args,
serverLoader: () => {
preventInvalidServerHandlerCall("loader", match.id, match.hasLoader);
if (_isHydrationRequest) {
if (hasInitialData) return initialData;
if (hasInitialError) throw initialError;
}
return callSingleFetch(singleFetch);
}
});
} : (_, singleFetch) => callSingleFetch(singleFetch),
action: match.clientAction ? (args, singleFetch) => match.clientAction({
...args,
serverAction: async () => {
preventInvalidServerHandlerCall("action", match.id, match.hasLoader);
return await callSingleFetch(singleFetch);
}
}) : match.hasAction ? (_, singleFetch) => callSingleFetch(singleFetch) : () => {
throw noActionDefinedError("action", match.id);
},
path: match.path,
shouldRevalidate: match.shouldRevalidate,
hasLoader: true,
hasClientLoader: match.clientLoader != null,
hasComponent: match.hasComponent,
hasAction: match.hasAction,
hasClientAction: match.clientAction != null
};
if (typeof dataRoute.loader === "function") dataRoute.loader.hydrate = shouldHydrateRouteLoader(match.id, match.clientLoader, match.hasLoader, false);
return dataRoute;
}
function callSingleFetch(singleFetch) {
invariant(typeof singleFetch === "function", "Invalid singleFetch parameter");
return singleFetch();
}
function preventInvalidServerHandlerCall(type, routeId, hasHandler) {
if (!hasHandler) {
let msg = `You are trying to call ${type === "action" ? "serverAction()" : "serverLoader()"} on a route that does not have a server ${type} (routeId: "${routeId}")`;
console.error(msg);
throw new ErrorResponseImpl(400, "Bad Request", new Error(msg), true);
}
}
const nextPaths = /* @__PURE__ */ new Set();
const discoveredPathsMaxSize = 1e3;
const discoveredPaths = /* @__PURE__ */ new Set();
function getManifestUrl(paths, clientVersion) {
if (paths.length === 0) return null;
let url;
if (paths.length === 1) url = new URL(`${paths[0]}.manifest`, window.location.origin);
else {
let basename = (window.__reactRouterDataRouter.basename ?? "").replace(/^\/|\/$/g, "");
url = new URL(`${basename}/.manifest`, window.location.origin);
url.searchParams.set("paths", paths.sort().join(","));
}
if (clientVersion !== void 0) url.searchParams.set("version", clientVersion);
return url;
}
async function fetchAndApplyManifestPatches(paths, createFromReadableStream, fetchImplementation, clientVersion, errorReloadPath, signal) {
paths = getPathsWithAncestors(paths);
let url = getManifestUrl(paths, clientVersion);
if (url == null) return;
if (url.toString().length > 7680) {
nextPaths.clear();
return;
}
let response = await fetchImplementation(new Request(url, { signal }));
if (clientVersion !== void 0 && response.status === 204 && response.headers.has("X-Remix-Reload-Document")) {
await handleClientVersionMismatch(true, clientVersion, errorReloadPath);
return;
}
if (!response.body || response.status < 200 || response.status >= 300) throw new Error("Unable to fetch new route matches from the server");
let payload = await createFromReadableStream(response.body, { temporaryReferences: void 0 });
if (payload.type !== "manifest") throw new Error("Failed to patch routes");
paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
let patches = await payload.patches;
React$1.startTransition(() => {
patches.forEach((p) => {
window.__reactRouterDataRouter.patchRoutes(p.parentId ?? null, [createRouteFromServerManifest(p)]);
});
});
}
function addToFifoQueue(path, queue) {
if (queue.size >= discoveredPathsMaxSize) {
let first = queue.values().next().value;
if (typeof first === "string") queue.delete(first);
}
queue.add(path);
}
function debounce(callback, wait) {
let timeoutId;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(...args), wait);
};
}
function isExternalLocation(location) {
return new URL(location, window.location.href).origin !== window.location.origin;
}
function normalizeRedirectLocation(location) {
if (PROTOCOL_RELATIVE_URL_REGEX.test(location)) {
let path = resolvePath(location);
return path.pathname + path.search + path.hash;
}
return location;
}
function cloneRoutes(routes) {
if (!routes) return void 0;
return routes.map((route) => ({
...route,
children: cloneRoutes(route.children)
}));
}
function diffRoutes(a, b) {
if (a.length !== b.length) return true;
return a.some((route, index) => {
if (route.element !== b[index].element) return true;
if (route.errorElement !== b[index].errorElement) return true;
if (route.hydrateFallbackElement !== b[index].hydrateFallbackElement) return true;
if (route.hasLoader !== b[index].hasLoader) return true;
if (route.hasClientLoader !== b[index].hasClientLoader) return true;
if (route.hasAction !== b[index].hasAction) return true;
if (route.hasClientAction !== b[index].hasClientAction) return true;
return diffRoutes(route.children || [], b[index].children || []);
});
}
//#endregion
export { RSCHydratedRouter, createCallServer };
@@ -0,0 +1,11 @@
import React from "react";
//#region lib/rsc/errorBoundaries.d.ts
declare function RSCDefaultRootErrorBoundary({
hasRootLayout
}: {
hasRootLayout: boolean;
}): React.JSX.Element;
//#endregion
export { RSCDefaultRootErrorBoundary };
@@ -0,0 +1,90 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { isRouteErrorResponse } from "../router/utils.js";
import { useRouteError } from "../hooks.js";
import React from "react";
//#region lib/rsc/errorBoundaries.tsx
var RSCRouterGlobalErrorBoundary = class extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
location: props.location
};
}
static getDerivedStateFromError(error) {
return { error };
}
static getDerivedStateFromProps(props, state) {
if (state.location !== props.location) return {
error: null,
location: props.location
};
return {
error: state.error,
location: state.location
};
}
render() {
if (this.state.error) return /* @__PURE__ */ React.createElement(RSCDefaultRootErrorBoundaryImpl, {
error: this.state.error,
renderAppShell: true
});
else return this.props.children;
}
};
function ErrorWrapper({ renderAppShell, title, children }) {
if (!renderAppShell) return children;
return /* @__PURE__ */ React.createElement("html", { lang: "en" }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", { charSet: "utf-8" }), /* @__PURE__ */ React.createElement("meta", {
name: "viewport",
content: "width=device-width,initial-scale=1,viewport-fit=cover"
}), /* @__PURE__ */ React.createElement("title", null, title)), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("main", { style: {
fontFamily: "system-ui, sans-serif",
padding: "2rem"
} }, children)));
}
function RSCDefaultRootErrorBoundaryImpl({ error, renderAppShell }) {
console.error(error);
let heyDeveloper = /* @__PURE__ */ React.createElement("script", { dangerouslySetInnerHTML: { __html: `
console.log(
"💿 Hey developer 👋. You can provide a way better UX than this when your app throws errors. Check out https://reactrouter.com/how-to/error-boundary for more information."
);
` } });
if (isRouteErrorResponse(error)) return /* @__PURE__ */ React.createElement(ErrorWrapper, {
renderAppShell,
title: "Unhandled Thrown Response!"
}, /* @__PURE__ */ React.createElement("h1", { style: { fontSize: "24px" } }, error.status, " ", error.statusText), heyDeveloper);
let errorInstance;
if (error instanceof Error) errorInstance = error;
else {
let errorString = error == null ? "Unknown Error" : typeof error === "object" && "toString" in error ? error.toString() : JSON.stringify(error);
errorInstance = new Error(errorString);
}
return /* @__PURE__ */ React.createElement(ErrorWrapper, {
renderAppShell,
title: "Application Error!"
}, /* @__PURE__ */ React.createElement("h1", { style: { fontSize: "24px" } }, "Application Error"), /* @__PURE__ */ React.createElement("pre", { style: {
padding: "2rem",
background: "hsla(10, 50%, 50%, 0.1)",
color: "red",
overflow: "auto"
} }, errorInstance.stack), heyDeveloper);
}
function RSCDefaultRootErrorBoundary({ hasRootLayout }) {
let error = useRouteError();
if (hasRootLayout === void 0) throw new Error("Missing 'hasRootLayout' prop");
return /* @__PURE__ */ React.createElement(RSCDefaultRootErrorBoundaryImpl, {
renderAppShell: !hasRootLayout,
error
});
}
//#endregion
export { RSCDefaultRootErrorBoundary, RSCRouterGlobalErrorBoundary };
@@ -0,0 +1,48 @@
//#region lib/rsc/html-stream/browser.d.ts
declare global {
interface Window {
__FLIGHT_DATA: any[];
}
}
/**
* Get the prerendered [RSC](https://react.dev/reference/rsc/server-components)
* stream for hydration. Usually passed directly to your
* `react-server-dom-xyz/client`'s `createFromReadableStream`.
*
* @example
* import { startTransition, StrictMode } from "react";
* import { hydrateRoot } from "react-dom/client";
* import {
* unstable_getRSCStream as getRSCStream,
* unstable_RSCHydratedRouter as RSCHydratedRouter,
* } from "react-router";
* import type { unstable_RSCPayload as RSCPayload } from "react-router";
*
* createFromReadableStream(getRSCStream()).then(
* (payload: RSCServerPayload) => {
* startTransition(async () => {
* hydrateRoot(
* document,
* <StrictMode>
* <RSCHydratedRouter {...props} />
* </StrictMode>,
* {
* // Options
* }
* );
* });
* }
* );
*
* @name unstable_getRSCStream
* @public
* @category RSC
* @mode data
* @returns A [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
* that contains the [RSC](https://react.dev/reference/rsc/server-components)
* data for hydration.
*/
declare function getRSCStream(): ReadableStream;
//#endregion
export { getRSCStream };
@@ -0,0 +1,74 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/rsc/html-stream/browser.ts
/**
* Get the prerendered [RSC](https://react.dev/reference/rsc/server-components)
* stream for hydration. Usually passed directly to your
* `react-server-dom-xyz/client`'s `createFromReadableStream`.
*
* @example
* import { startTransition, StrictMode } from "react";
* import { hydrateRoot } from "react-dom/client";
* import {
* unstable_getRSCStream as getRSCStream,
* unstable_RSCHydratedRouter as RSCHydratedRouter,
* } from "react-router";
* import type { unstable_RSCPayload as RSCPayload } from "react-router";
*
* createFromReadableStream(getRSCStream()).then(
* (payload: RSCServerPayload) => {
* startTransition(async () => {
* hydrateRoot(
* document,
* <StrictMode>
* <RSCHydratedRouter {...props} />
* </StrictMode>,
* {
* // Options
* }
* );
* });
* }
* );
*
* @name unstable_getRSCStream
* @public
* @category RSC
* @mode data
* @returns A [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
* that contains the [RSC](https://react.dev/reference/rsc/server-components)
* data for hydration.
*/
function getRSCStream() {
let encoder = new TextEncoder();
let streamController = null;
let rscStream = new ReadableStream({ start(controller) {
if (typeof window === "undefined") return;
let handleChunk = (chunk) => {
if (typeof chunk === "string") controller.enqueue(encoder.encode(chunk));
else controller.enqueue(chunk);
};
window.__FLIGHT_DATA ||= [];
window.__FLIGHT_DATA.forEach(handleChunk);
window.__FLIGHT_DATA.push = (chunk) => {
handleChunk(chunk);
return 0;
};
streamController = controller;
} });
if (typeof document !== "undefined" && document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => {
streamController?.close();
});
else streamController?.close();
return rscStream;
}
//#endregion
export { getRSCStream };
@@ -0,0 +1,97 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/rsc/html-stream/server.ts
const encoder = new TextEncoder();
const trailer = "</body></html>";
function injectRSCPayload(rscStream, { nonce } = {}) {
let decoder = new TextDecoder();
let resolveFlightDataPromise;
let flightDataPromise = new Promise((resolve) => resolveFlightDataPromise = resolve);
let startedRSC = false;
let cancelled = false;
let rscReader = null;
let buffered = [];
let timeout = null;
function flushBufferedChunks(controller) {
for (let chunk of buffered) {
let buf = decoder.decode(chunk, { stream: true });
if (buf.endsWith(trailer)) buf = buf.slice(0, -14);
controller.enqueue(encoder.encode(buf));
}
buffered.length = 0;
timeout = null;
}
return new TransformStream({
transform(chunk, controller) {
buffered.push(chunk);
if (timeout) return;
timeout = setTimeout(async () => {
if (cancelled) return;
flushBufferedChunks(controller);
if (!startedRSC) {
startedRSC = true;
rscReader = rscStream.getReader();
writeRSCStream(rscReader, controller, () => cancelled, nonce).catch((err) => controller.error(err)).then(resolveFlightDataPromise);
}
}, 0);
},
async flush(controller) {
await flightDataPromise;
if (timeout) {
clearTimeout(timeout);
flushBufferedChunks(controller);
}
controller.enqueue(encoder.encode("</body></html>"));
},
async cancel(reason) {
cancelled = true;
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
buffered.length = 0;
if (rscReader) await rscReader.cancel(reason).catch(() => {});
else await rscStream.cancel(reason).catch(() => {});
resolveFlightDataPromise();
}
});
}
async function writeRSCStream(reader, controller, isCancelled, nonce) {
let decoder = new TextDecoder("utf-8", { fatal: true });
try {
let read;
while ((read = await reader.read()) && !read.done) {
if (isCancelled()) return;
const chunk = read.value;
try {
writeChunk(JSON.stringify(decoder.decode(chunk, { stream: true })), controller, nonce);
} catch (e) {
writeChunk(`Uint8Array.from(atob(${JSON.stringify(btoa(String.fromCodePoint(...chunk)))}), m => m.codePointAt(0))`, controller, nonce);
}
}
} finally {
reader.releaseLock();
}
let remaining = decoder.decode();
if (remaining.length && !isCancelled()) writeChunk(JSON.stringify(remaining), controller, nonce);
}
function writeChunk(chunk, controller, nonce) {
let nonceAttr = nonce == null ? "" : ` nonce="${escapeAttribute(nonce)}"`;
controller.enqueue(encoder.encode(`<script${nonceAttr}>${escapeScript(`(self.__FLIGHT_DATA||=[]).push(${chunk})`)}<\/script>`));
}
function escapeAttribute(value) {
return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function escapeScript(script) {
return script.replace(/<!--/g, "<\\!--").replace(/<\/(script)/gi, "</\\$1");
}
//#endregion
export { injectRSCPayload };
@@ -0,0 +1,27 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/rsc/route-modules.ts
function createRSCRouteModules(payload) {
const routeModules = {};
for (const match of payload.matches) populateRSCRouteModules(routeModules, match);
return routeModules;
}
function populateRSCRouteModules(routeModules, matches) {
matches = Array.isArray(matches) ? matches : [matches];
for (const match of matches) routeModules[match.id] = {
links: match.links,
meta: match.meta,
default: noopComponent
};
}
const noopComponent = () => null;
//#endregion
export { createRSCRouteModules, populateRSCRouteModules };
@@ -0,0 +1,224 @@
import { Location } from "../router/history.js";
import { ActionFunction, LoaderFunction, Params, RouterContextProvider, ShouldRevalidateFunction } from "../router/utils.js";
import { ClientActionFunction, ClientLoaderFunction, HeadersFunction, LinksFunction, MetaFunction } from "../dom/ssr/routeModules.js";
import * as React$1 from "react";
import { ReactFormState } from "react-dom/client";
//#region lib/rsc/server.rsc.d.ts
declare function getRequest(): Request;
type RSCRouteConfigEntryBase = {
action?: ActionFunction;
clientAction?: ClientActionFunction;
clientLoader?: ClientLoaderFunction;
ErrorBoundary?: React$1.ComponentType<any>;
handle?: any;
headers?: HeadersFunction;
HydrateFallback?: React$1.ComponentType<any>;
Layout?: React$1.ComponentType<any>;
links?: LinksFunction;
loader?: LoaderFunction;
meta?: MetaFunction;
shouldRevalidate?: ShouldRevalidateFunction;
};
type RSCRouteConfigEntry = RSCRouteConfigEntryBase & {
id: string;
path?: string;
Component?: React$1.ComponentType<any>;
lazy?: () => Promise<RSCRouteConfigEntryBase & ({
default?: React$1.ComponentType<any>;
Component?: never;
} | {
default?: never;
Component?: React$1.ComponentType<any>;
})>;
} & ({
index: true;
} | {
children?: RSCRouteConfigEntry[];
});
type RSCRouteConfig = Array<RSCRouteConfigEntry>;
type RSCRouteManifest = {
clientAction?: ClientActionFunction;
clientLoader?: ClientLoaderFunction;
element?: React$1.ReactElement | false;
errorElement?: React$1.ReactElement;
handle?: any;
hasAction: boolean;
hasComponent: boolean;
hasLoader: boolean;
hydrateFallbackElement?: React$1.ReactElement;
id: string;
index?: boolean;
links?: LinksFunction;
meta?: MetaFunction;
parentId?: string;
path?: string;
shouldRevalidate?: ShouldRevalidateFunction;
};
type RSCRouteMatch = RSCRouteManifest & {
params: Params;
pathname: string;
pathnameBase: string;
};
type RSCRenderPayload = {
type: "render";
actionData: Record<string, any> | null;
basename: string | undefined;
clientVersion?: string;
errors: Record<string, any> | null;
loaderData: Record<string, any>;
location: Location;
routeDiscovery: RouteDiscovery;
matches: RSCRouteMatch[];
patches?: Promise<RSCRouteManifest[]>;
formState?: ReactFormState;
};
type RSCManifestPayload = {
type: "manifest";
patches: Promise<RSCRouteManifest[]>;
};
type RSCActionPayload = {
type: "action";
actionResult: Promise<unknown>;
rerender?: Promise<RSCRenderPayload | RSCRedirectPayload>;
};
type RSCRedirectPayload = {
type: "redirect";
status: number;
location: string;
replace: boolean;
reload: boolean;
actionResult?: Promise<unknown>;
};
type RSCPayload = RSCRenderPayload | RSCManifestPayload | RSCActionPayload | RSCRedirectPayload;
type RSCMatch = {
statusCode: number;
headers: Headers;
payload: RSCPayload;
};
type DecodeActionFunction = (formData: FormData) => Promise<() => Promise<unknown>>;
type DecodeFormStateFunction = (result: unknown, formData: FormData) => Promise<ReactFormState | undefined>;
type DecodeReplyFunction = (reply: FormData | string, options: {
temporaryReferences: unknown;
}) => Promise<unknown[]>;
type LoadServerActionFunction = (id: string) => Promise<Function>;
type RouteDiscovery = {
mode: "lazy";
manifestPath?: string | undefined;
} | {
mode: "initial";
};
/**
* Matches the given routes to a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* and returns an [RSC](https://react.dev/reference/rsc/server-components)
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* encoding an {@link unstable_RSCPayload} for consumption by an [RSC](https://react.dev/reference/rsc/server-components)
* enabled client router.
*
* @example
* import {
* createTemporaryReferenceSet,
* decodeAction,
* decodeReply,
* loadServerAction,
* renderToReadableStream,
* } from "@vitejs/plugin-rsc/rsc";
* import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router";
*
* matchRSCServerRequest({
* createTemporaryReferenceSet,
* decodeAction,
* decodeFormState,
* decodeReply,
* loadServerAction,
* request,
* routes: routes(),
* generateResponse(match) {
* return new Response(
* renderToReadableStream(match.payload),
* {
* status: match.statusCode,
* headers: match.headers,
* }
* );
* },
* });
*
* @name unstable_matchRSCServerRequest
* @public
* @category RSC
* @mode data
* @param opts Options
* @param opts.allowedActionOrigins Origin patterns that are allowed to execute actions.
* @param opts.basename The basename to use when matching the request.
* @param opts.createTemporaryReferenceSet A function that returns a temporary
* reference set for the request, used to track temporary references in the [RSC](https://react.dev/reference/rsc/server-components)
* stream.
* @param opts.decodeAction Your `react-server-dom-xyz/server`'s `decodeAction`
* function, responsible for loading a server action.
* @param opts.decodeFormState A function responsible for decoding form state for
* progressively enhanceable forms with React's [`useActionState`](https://react.dev/reference/react/useActionState)
* using your `react-server-dom-xyz/server`'s `decodeFormState`.
* @param opts.decodeReply Your `react-server-dom-xyz/server`'s `decodeReply`
* function, used to decode the server function's arguments and bind them to the
* implementation for invocation by the router.
* @param opts.generateResponse A function responsible for using your
* `renderToReadableStream` to generate a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* encoding the {@link unstable_RSCPayload}.
* @param opts.loadServerAction Your `react-server-dom-xyz/server`'s
* `loadServerAction` function, used to load a server action by ID.
* @param opts.clientVersion A version derived from the client build output used
* to detect stale clients during lazy route discovery.
* @param opts.onError An optional error handler that will be called with any
* errors that occur during the request processing.
* @param opts.request The [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* to match against.
* @param opts.requestContext An instance of {@link RouterContextProvider}
* that should be created per request, to be passed to [`action`](../../start/data/route-object#action)s,
* [`loader`](../../start/data/route-object#loader)s and [middleware](../../how-to/middleware).
* @param opts.routeDiscovery The route discovery configuration, used to determine how the router should discover new routes during navigations.
* @param opts.routes Your {@link unstable_RSCRouteConfigEntry | route definitions}.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that contains the [RSC](https://react.dev/reference/rsc/server-components)
* data for hydration.
*/
declare function matchRSCServerRequest({
allowedActionOrigins,
createTemporaryReferenceSet,
basename,
decodeReply,
requestContext,
routeDiscovery,
loadServerAction,
decodeAction,
decodeFormState,
clientVersion,
onError,
request,
routes,
generateResponse
}: {
allowedActionOrigins?: string[];
createTemporaryReferenceSet: () => unknown;
basename?: string;
decodeReply?: DecodeReplyFunction;
decodeAction?: DecodeActionFunction;
decodeFormState?: DecodeFormStateFunction;
requestContext?: RouterContextProvider;
loadServerAction?: LoadServerActionFunction;
clientVersion?: string;
onError?: (error: unknown) => void;
request: Request;
routes: RSCRouteConfigEntry[];
routeDiscovery?: RouteDiscovery;
generateResponse: (match: RSCMatch, {
onError,
temporaryReferences
}: {
onError(error: unknown): string | undefined;
temporaryReferences: unknown;
}) => Response;
}): Promise<Response>;
//#endregion
export { DecodeActionFunction, DecodeFormStateFunction, DecodeReplyFunction, LoadServerActionFunction, RSCManifestPayload, RSCMatch, RSCPayload, RSCRenderPayload, RSCRouteConfig, RSCRouteConfigEntry, RSCRouteManifest, RSCRouteMatch, getRequest, matchRSCServerRequest };
@@ -0,0 +1,152 @@
import { RSCPayload } from "./server.rsc.js";
import * as React$1 from "react";
//#region lib/rsc/server.ssr.d.ts
type DecodedPayload = Promise<RSCPayload> & {
_deepestRenderedBoundaryId?: string | null;
formState: Promise<any>;
};
type SSRCreateFromReadableStreamFunction = (body: ReadableStream<Uint8Array>) => Promise<unknown>;
/**
* Routes the incoming [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* to the [RSC](https://react.dev/reference/rsc/server-components) server and
* appropriately proxies the server response for data / resource requests, or
* renders to HTML for a document request.
*
* @example
* import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
* import * as ReactDomServer from "react-dom/server.edge";
* import {
* unstable_RSCStaticRouter as RSCStaticRouter,
* unstable_routeRSCServerRequest as routeRSCServerRequest,
* } from "react-router";
*
* routeRSCServerRequest({
* request,
* serverResponse,
* createFromReadableStream,
* nonce,
* async renderHTML(getPayload, options) {
* const payload = getPayload();
*
* return await renderHTMLToReadableStream(
* <RSCStaticRouter
* getPayload={getPayload}
* nonce={options.nonce}
* />,
* {
* ...options,
* bootstrapScriptContent,
* formState: await payload.formState,
* }
* );
* },
* });
*
* @name unstable_routeRSCServerRequest
* @public
* @category RSC
* @mode data
* @param opts Options
* @param opts.createFromReadableStream Your `react-server-dom-xyz/client`'s
* `createFromReadableStream` function, used to decode payloads from the server.
* @param opts.serverResponse A Response or partial response generated by the [RSC](https://react.dev/reference/rsc/server-components) handler containing a serialized {@link unstable_RSCPayload}.
* @param opts.hydrate Whether to hydrate the server response with the RSC payload.
* Defaults to `true`.
* @param opts.nonce An optional [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* for inline scripts generated while rendering the HTML document.
* @param opts.renderHTML A function that renders the {@link unstable_RSCPayload} to
* HTML, usually using a {@link unstable_RSCStaticRouter | `<RSCStaticRouter>`}.
* @param opts.request The request to route.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that either contains the [RSC](https://react.dev/reference/rsc/server-components)
* payload for data requests, or renders the HTML for document requests.
*/
declare function routeRSCServerRequest({
request,
serverResponse,
createFromReadableStream,
renderHTML,
hydrate,
nonce
}: {
request: Request;
serverResponse: Response;
createFromReadableStream: SSRCreateFromReadableStreamFunction;
renderHTML: (getPayload: () => DecodedPayload, options: {
nonce?: string;
onError(error: unknown): string | undefined;
onHeaders(headers: Headers): void;
}) => ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>;
hydrate?: boolean;
nonce?: string;
}): Promise<Response>;
/**
* Props for the {@link unstable_RSCStaticRouter} component.
*
* @name unstable_RSCStaticRouterProps
* @category Types
*/
interface RSCStaticRouterProps {
/**
* A function that starts decoding of the {@link unstable_RSCPayload}. Usually passed
* through from {@link unstable_routeRSCServerRequest}'s `renderHTML`.
*/
getPayload: () => DecodedPayload;
/**
* An optional [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* used as the default for nonce-aware components such as `<Links>` and
* `<ScrollRestoration>`.
*/
nonce?: string;
}
/**
* Pre-renders an {@link unstable_RSCPayload} to HTML. Usually used in
* {@link unstable_routeRSCServerRequest}'s `renderHTML` callback.
*
* @example
* import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
* import * as ReactDomServer from "react-dom/server.edge";
* import {
* unstable_RSCStaticRouter as RSCStaticRouter,
* unstable_routeRSCServerRequest as routeRSCServerRequest,
* } from "react-router";
*
* routeRSCServerRequest({
* request,
* serverResponse,
* createFromReadableStream,
* nonce,
* async renderHTML(getPayload, options) {
* const payload = getPayload();
*
* return await renderHTMLToReadableStream(
* <RSCStaticRouter
* getPayload={getPayload}
* nonce={options.nonce}
* />,
* {
* ...options,
* bootstrapScriptContent,
* formState: await payload.formState,
* }
* );
* },
* });
*
* @name unstable_RSCStaticRouter
* @public
* @category RSC
* @mode data
* @param props Props
* @param {unstable_RSCStaticRouterProps.getPayload} props.getPayload n/a
* @param {unstable_RSCStaticRouterProps.nonce} props.nonce n/a
* @returns A React component that renders the {@link unstable_RSCPayload} as HTML.
*/
declare function RSCStaticRouter({
getPayload,
nonce
}: RSCStaticRouterProps): React$1.JSX.Element | null;
//#endregion
export { RSCStaticRouter, RSCStaticRouterProps, SSRCreateFromReadableStreamFunction, routeRSCServerRequest };
@@ -0,0 +1,419 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { isRouteErrorResponse } from "../router/utils.js";
import { hasInvalidProtocol } from "../router/router.js";
import { RSCRouterContext } from "../context.js";
import { decodeRedirectErrorDigest, decodeRouteErrorResponseDigest } from "../errors.js";
import { escapeHtml } from "../dom/ssr/markup.js";
import { shouldHydrateRouteLoader } from "../dom/ssr/routes.js";
import { FrameworkContext } from "../dom/ssr/components.js";
import { StaticRouterProvider, createStaticRouter } from "../dom/server.js";
import { injectRSCPayload } from "./html-stream/server.js";
import { RSCRouterGlobalErrorBoundary } from "./errorBoundaries.js";
import { createRSCRouteModules } from "./route-modules.js";
import * as React$1 from "react";
//#region lib/rsc/server.ssr.tsx
const defaultManifestPath = "/__manifest";
/**
* Routes the incoming [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* to the [RSC](https://react.dev/reference/rsc/server-components) server and
* appropriately proxies the server response for data / resource requests, or
* renders to HTML for a document request.
*
* @example
* import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
* import * as ReactDomServer from "react-dom/server.edge";
* import {
* unstable_RSCStaticRouter as RSCStaticRouter,
* unstable_routeRSCServerRequest as routeRSCServerRequest,
* } from "react-router";
*
* routeRSCServerRequest({
* request,
* serverResponse,
* createFromReadableStream,
* nonce,
* async renderHTML(getPayload, options) {
* const payload = getPayload();
*
* return await renderHTMLToReadableStream(
* <RSCStaticRouter
* getPayload={getPayload}
* nonce={options.nonce}
* />,
* {
* ...options,
* bootstrapScriptContent,
* formState: await payload.formState,
* }
* );
* },
* });
*
* @name unstable_routeRSCServerRequest
* @public
* @category RSC
* @mode data
* @param opts Options
* @param opts.createFromReadableStream Your `react-server-dom-xyz/client`'s
* `createFromReadableStream` function, used to decode payloads from the server.
* @param opts.serverResponse A Response or partial response generated by the [RSC](https://react.dev/reference/rsc/server-components) handler containing a serialized {@link unstable_RSCPayload}.
* @param opts.hydrate Whether to hydrate the server response with the RSC payload.
* Defaults to `true`.
* @param opts.nonce An optional [`nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce)
* for inline scripts generated while rendering the HTML document.
* @param opts.renderHTML A function that renders the {@link unstable_RSCPayload} to
* HTML, usually using a {@link unstable_RSCStaticRouter | `<RSCStaticRouter>`}.
* @param opts.request The request to route.
* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* that either contains the [RSC](https://react.dev/reference/rsc/server-components)
* payload for data requests, or renders the HTML for document requests.
*/
async function routeRSCServerRequest({ request, serverResponse, createFromReadableStream, renderHTML, hydrate = true, nonce }) {
const url = new URL(request.url);
if (isReactServerRequest(url) || isManifestRequest(url) || request.headers.has("rsc-action-id") || serverResponse.headers.get("React-Router-Resource") === "true") return serverResponse;
if (!serverResponse.body) throw new Error("Missing body in server response");
const detectRedirectResponse = serverResponse.clone();
let serverResponseB = null;
if (hydrate) serverResponseB = serverResponse.clone();
const body = serverResponse.body;
let buffer;
let streamControllers = [];
const createStream = () => {
if (!buffer) {
buffer = [];
return body.pipeThrough(new TransformStream({
transform(chunk, controller) {
buffer.push(chunk);
controller.enqueue(chunk);
streamControllers.forEach((c) => c.enqueue(chunk));
},
flush() {
streamControllers.forEach((c) => c.close());
streamControllers = [];
}
}));
}
return new ReadableStream({ start(controller) {
buffer.forEach((chunk) => controller.enqueue(chunk));
streamControllers.push(controller);
} });
};
let deepestRenderedBoundaryId = null;
const getPayload = () => {
const payloadPromise = Promise.resolve(createFromReadableStream(createStream()));
return Object.defineProperties(payloadPromise, {
_deepestRenderedBoundaryId: {
get() {
return deepestRenderedBoundaryId;
},
set(boundaryId) {
deepestRenderedBoundaryId = boundaryId;
}
},
formState: { get() {
return payloadPromise.then((payload) => payload.type === "render" ? payload.formState : void 0);
} }
});
};
let renderRedirect;
let renderError;
try {
if (!detectRedirectResponse.body) throw new Error("Failed to clone server response");
const payload = await createFromReadableStream(detectRedirectResponse.body);
if (serverResponse.status === 202 && payload.type === "redirect") {
if (hasInvalidProtocol(payload.location)) throw new Error("Invalid redirect location");
const headers = new Headers(serverResponse.headers);
headers.delete("Content-Encoding");
headers.delete("Content-Length");
headers.delete("Content-Type");
headers.delete("X-Remix-Response");
headers.set("Location", payload.location);
return new Response(serverResponseB?.body || "", {
headers,
status: payload.status,
statusText: serverResponse.statusText
});
}
let reactHeaders = new Headers();
let status = serverResponse.status;
let statusText = serverResponse.statusText;
let html = await renderHTML(getPayload, {
nonce,
onError(error) {
if (typeof error === "object" && error && "digest" in error && typeof error.digest === "string") {
renderRedirect = decodeRedirectErrorDigest(error.digest);
if (renderRedirect) return error.digest;
let routeErrorResponse = decodeRouteErrorResponseDigest(error.digest);
if (routeErrorResponse) {
renderError = routeErrorResponse;
status = routeErrorResponse.status;
statusText = routeErrorResponse.statusText;
return error.digest;
}
}
},
onHeaders(headers) {
for (const [key, value] of headers) reactHeaders.append(key, value);
}
});
const headers = new Headers(reactHeaders);
for (const [key, value] of serverResponse.headers) headers.append(key, value);
headers.set("Content-Type", "text/html; charset=utf-8");
if (renderRedirect) {
if (hasInvalidProtocol(renderRedirect.location)) throw new Error("Invalid redirect location");
headers.set("Location", renderRedirect.location);
return new Response(html, {
status: renderRedirect.status,
headers
});
}
const redirectTransform = new TransformStream({ flush(controller) {
if (renderRedirect) {
if (hasInvalidProtocol(renderRedirect.location)) return;
controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeHtml(renderRedirect.location)}"/>`));
}
} });
if (!hydrate) return new Response(html.pipeThrough(redirectTransform), {
status,
statusText,
headers
});
if (!serverResponseB?.body) throw new Error("Failed to clone server response");
const body = html.pipeThrough(injectRSCPayload(serverResponseB.body, { nonce })).pipeThrough(redirectTransform);
return new Response(body, {
status,
statusText,
headers
});
} catch (error) {
if (error instanceof Response) return error;
if (renderRedirect) {
if (hasInvalidProtocol(renderRedirect.location)) throw new Error("Invalid redirect location");
return new Response(`Redirect: ${renderRedirect.location}`, {
status: renderRedirect.status,
headers: { Location: renderRedirect.location }
});
}
try {
let normalizedError = renderError ?? error;
let [status, statusText] = isRouteErrorResponse(normalizedError) ? [normalizedError.status, normalizedError.statusText] : [500, ""];
let retryRedirect;
let reactHeaders = new Headers();
const html = await renderHTML(() => {
const payloadPromise = Promise.resolve(createFromReadableStream(createStream())).then((payload) => Object.assign(payload, {
status,
errors: deepestRenderedBoundaryId ? { [deepestRenderedBoundaryId]: normalizedError } : {}
}));
return Object.defineProperties(payloadPromise, {
_deepestRenderedBoundaryId: {
get() {
return deepestRenderedBoundaryId;
},
set(boundaryId) {
deepestRenderedBoundaryId = boundaryId;
}
},
formState: { get() {
return payloadPromise.then((payload) => payload.type === "render" ? payload.formState : void 0);
} }
});
}, {
nonce,
onError(error) {
if (typeof error === "object" && error && "digest" in error && typeof error.digest === "string") {
retryRedirect = decodeRedirectErrorDigest(error.digest);
if (retryRedirect) return error.digest;
let routeErrorResponse = decodeRouteErrorResponseDigest(error.digest);
if (routeErrorResponse) {
status = routeErrorResponse.status;
statusText = routeErrorResponse.statusText;
return error.digest;
}
}
},
onHeaders(headers) {
for (const [key, value] of headers) reactHeaders.append(key, value);
}
});
const headers = new Headers(reactHeaders);
for (const [key, value] of serverResponse.headers) headers.append(key, value);
headers.set("Content-Type", "text/html; charset=utf-8");
if (retryRedirect) {
if (hasInvalidProtocol(retryRedirect.location)) throw new Error("Invalid redirect location");
headers.set("Location", retryRedirect.location);
return new Response(html, {
status: retryRedirect.status,
headers
});
}
const retryRedirectTransform = new TransformStream({ flush(controller) {
if (retryRedirect) {
if (hasInvalidProtocol(retryRedirect.location)) return;
controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeHtml(retryRedirect.location)}"/>`));
}
} });
if (!hydrate) return new Response(html.pipeThrough(retryRedirectTransform), {
status,
statusText,
headers
});
if (!serverResponseB?.body) throw new Error("Failed to clone server response");
const body = html.pipeThrough(injectRSCPayload(serverResponseB.body, { nonce })).pipeThrough(retryRedirectTransform);
return new Response(body, {
status,
statusText,
headers
});
} catch (error2) {}
throw error;
}
}
/**
* Pre-renders an {@link unstable_RSCPayload} to HTML. Usually used in
* {@link unstable_routeRSCServerRequest}'s `renderHTML` callback.
*
* @example
* import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
* import * as ReactDomServer from "react-dom/server.edge";
* import {
* unstable_RSCStaticRouter as RSCStaticRouter,
* unstable_routeRSCServerRequest as routeRSCServerRequest,
* } from "react-router";
*
* routeRSCServerRequest({
* request,
* serverResponse,
* createFromReadableStream,
* nonce,
* async renderHTML(getPayload, options) {
* const payload = getPayload();
*
* return await renderHTMLToReadableStream(
* <RSCStaticRouter
* getPayload={getPayload}
* nonce={options.nonce}
* />,
* {
* ...options,
* bootstrapScriptContent,
* formState: await payload.formState,
* }
* );
* },
* });
*
* @name unstable_RSCStaticRouter
* @public
* @category RSC
* @mode data
* @param props Props
* @param {unstable_RSCStaticRouterProps.getPayload} props.getPayload n/a
* @param {unstable_RSCStaticRouterProps.nonce} props.nonce n/a
* @returns A React component that renders the {@link unstable_RSCPayload} as HTML.
*/
function RSCStaticRouter({ getPayload, nonce }) {
const decoded = getPayload();
const payload = React$1.use(decoded);
if (payload.type === "redirect") {
if (hasInvalidProtocol(payload.location)) throw new Error("Invalid redirect location");
throw new Response(null, {
status: payload.status,
headers: { Location: payload.location }
});
}
if (payload.type !== "render") return null;
let patchedLoaderData = { ...payload.loaderData };
for (const match of payload.matches) if (shouldHydrateRouteLoader(match.id, match.clientLoader, match.hasLoader, false) && (match.hydrateFallbackElement || !match.hasLoader)) delete patchedLoaderData[match.id];
const context = {
get _deepestRenderedBoundaryId() {
return decoded._deepestRenderedBoundaryId ?? null;
},
set _deepestRenderedBoundaryId(boundaryId) {
decoded._deepestRenderedBoundaryId = boundaryId;
},
actionData: payload.actionData,
actionHeaders: {},
basename: payload.basename,
errors: payload.errors,
loaderData: patchedLoaderData,
loaderHeaders: {},
location: payload.location,
statusCode: 200,
matches: payload.matches.map((match) => ({
params: match.params,
pathname: match.pathname,
pathnameBase: match.pathnameBase,
route: {
id: match.id,
action: match.hasAction || !!match.clientAction,
handle: match.handle,
loader: match.hasLoader || !!match.clientLoader,
index: match.index,
path: match.path,
shouldRevalidate: match.shouldRevalidate
}
}))
};
const router = createStaticRouter(payload.matches.reduceRight((previous, match) => {
const route = {
id: match.id,
action: match.hasAction || !!match.clientAction,
element: match.element,
errorElement: match.errorElement,
handle: match.handle,
hydrateFallbackElement: match.hydrateFallbackElement,
index: match.index,
loader: match.hasLoader || !!match.clientLoader,
path: match.path,
shouldRevalidate: match.shouldRevalidate
};
if (previous.length > 0) route.children = previous;
return [route];
}, []), context);
const frameworkContext = {
future: {},
isSpaMode: false,
ssr: true,
criticalCss: "",
manifest: {
routes: {},
version: "1",
url: "",
entry: {
module: "",
imports: []
}
},
routeDiscovery: payload.routeDiscovery.mode === "initial" ? {
mode: "initial",
manifestPath: defaultManifestPath
} : {
mode: "lazy",
manifestPath: payload.routeDiscovery.manifestPath || defaultManifestPath
},
routeModules: createRSCRouteModules(payload),
nonce
};
return /* @__PURE__ */ React$1.createElement(RSCRouterContext.Provider, { value: true }, /* @__PURE__ */ React$1.createElement(RSCRouterGlobalErrorBoundary, { location: payload.location }, /* @__PURE__ */ React$1.createElement(FrameworkContext.Provider, { value: frameworkContext }, /* @__PURE__ */ React$1.createElement(StaticRouterProvider, {
context,
router,
hydrate: false
}))));
}
function isReactServerRequest(url) {
return url.pathname.endsWith(".rsc");
}
function isManifestRequest(url) {
return url.pathname.endsWith(".manifest");
}
//#endregion
export { RSCStaticRouter, routeRSCServerRequest };
@@ -0,0 +1,66 @@
import { ActionFunctionArgs, LoaderFunctionArgs, RouterContextProvider } from "../router/utils.js";
import { AssetsManifest, CriticalCss, EntryContext, FutureConfig } from "../dom/ssr/entry.js";
import { ServerRouteManifest } from "./routes.js";
import { ServerInstrumentation } from "../router/instrumentation.js";
//#region lib/server-runtime/build.d.ts
type OptionalCriticalCss = CriticalCss | undefined;
/**
* The output of the compiler for the server build.
*/
interface ServerBuild {
entry: {
module: ServerEntryModule;
};
routes: ServerRouteManifest;
assets: AssetsManifest;
basename?: string;
publicPath: string;
assetsBuildDirectory: string;
future: FutureConfig;
ssr: boolean;
unstable_getCriticalCss?: (args: {
pathname: string;
}) => OptionalCriticalCss | Promise<OptionalCriticalCss>;
/**
* @deprecated This is now done via a custom header during prerendering
*/
isSpaMode: boolean;
prerender: string[];
routeDiscovery: {
mode: "lazy" | "initial";
manifestPath: string;
};
allowedActionOrigins?: string[] | false;
}
interface HandleDocumentRequestFunction {
(request: Request, responseStatusCode: number, responseHeaders: Headers, context: EntryContext, loadContext: RouterContextProvider): Promise<Response> | Response;
}
interface HandleDataRequestFunction {
(response: Response, args: {
request: LoaderFunctionArgs["request"] | ActionFunctionArgs["request"];
context: LoaderFunctionArgs["context"] | ActionFunctionArgs["context"];
params: LoaderFunctionArgs["params"] | ActionFunctionArgs["params"];
}): Promise<Response> | Response;
}
interface HandleErrorFunction {
(error: unknown, args: {
request: LoaderFunctionArgs["request"] | ActionFunctionArgs["request"];
context: LoaderFunctionArgs["context"] | ActionFunctionArgs["context"];
params: LoaderFunctionArgs["params"] | ActionFunctionArgs["params"];
}): void;
}
/**
* A module that serves as the entry point for a Remix app during server
* rendering.
*/
interface ServerEntryModule {
default: HandleDocumentRequestFunction;
handleDataRequest?: HandleDataRequestFunction;
handleError?: HandleErrorFunction;
instrumentations?: ServerInstrumentation[];
streamTimeout?: number;
}
//#endregion
export { HandleDataRequestFunction, HandleDocumentRequestFunction, HandleErrorFunction, ServerBuild, ServerEntryModule };
@@ -0,0 +1,92 @@
import { CookieParseOptions as CookieParseOptions$1, CookieSerializeOptions as CookieSerializeOptions$1 } from "cookie-es";
//#region lib/server-runtime/cookies.d.ts
interface CookieSignatureOptions {
/**
* An array of secrets that may be used to sign/unsign the value of a cookie.
*
* The array makes it easy to rotate secrets. New secrets should be added to
* the beginning of the array. `cookie.serialize()` will always use the first
* value in the array, but `cookie.parse()` may use any of them so that
* cookies that were signed with older secrets still work.
*/
secrets?: string[];
}
type CookieOptions = CookieParseOptions$1 & CookieSerializeOptions$1 & CookieSignatureOptions;
/**
* A HTTP cookie.
*
* A Cookie is a logical container for metadata about a HTTP cookie; its name
* and options. But it doesn't contain a value. Instead, it has `parse()` and
* `serialize()` methods that allow a single instance to be reused for
* parsing/encoding multiple different values.
*
* @see https://remix.run/utils/cookies#cookie-api
*/
interface Cookie {
/**
* The name of the cookie, used in the `Cookie` and `Set-Cookie` headers.
*/
readonly name: string;
/**
* True if this cookie uses one or more secrets for verification.
*/
readonly isSigned: boolean;
/**
* The Date this cookie expires.
*
* Note: This is calculated at access time using `maxAge` when no `expires`
* option is provided to `createCookie()`.
*/
readonly expires?: Date;
/**
* Parses a raw `Cookie` header and returns the value of this cookie or
* `null` if it's not present.
*/
parse(cookieHeader: string | null, options?: CookieParseOptions$1): Promise<any>;
/**
* Serializes the given value to a string and returns the `Set-Cookie`
* header.
*/
serialize(value: any, options?: CookieSerializeOptions$1): Promise<string>;
}
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param name The name of the cookie.
* @param cookieOptions Options for parsing and serializing the cookie.
* @returns A {@link Cookie} object for parsing and serializing the cookie.
*/
declare const createCookie: (name: string, cookieOptions?: CookieOptions) => Cookie;
/**
* A function that determines whether a value is a React Router {@link Cookie}
* object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
type IsCookieFunction = (object: any) => object is Cookie;
/**
* Returns `true` if a value is a React Router {@link Cookie} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
declare const isCookie: IsCookieFunction;
//#endregion
export { Cookie, CookieOptions, type CookieParseOptions$1 as CookieParseOptions, type CookieSerializeOptions$1 as CookieSerializeOptions, CookieSignatureOptions, IsCookieFunction, createCookie, isCookie };
@@ -0,0 +1,153 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { warnOnce } from "./warnings.js";
import { sign, unsign } from "./crypto.js";
import { parse, serialize } from "cookie-es";
//#region lib/server-runtime/cookies.ts
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param name The name of the cookie.
* @param cookieOptions Options for parsing and serializing the cookie.
* @returns A {@link Cookie} object for parsing and serializing the cookie.
*/
const createCookie = (name, cookieOptions = {}) => {
let { secrets = [], ...options } = {
path: "/",
sameSite: "lax",
...cookieOptions
};
warnOnceAboutExpiresCookie(name, options.expires);
return {
get name() {
return name;
},
get isSigned() {
return secrets.length > 0;
},
get expires() {
return typeof options.maxAge !== "undefined" ? new Date(Date.now() + options.maxAge * 1e3) : options.expires;
},
async parse(cookieHeader, parseOptions) {
if (!cookieHeader) return null;
let cookies = parse(cookieHeader, {
...options,
...parseOptions
});
if (name in cookies) {
let value = cookies[name];
if (typeof value === "string" && value !== "") return await decodeCookieValue(value, secrets);
else return "";
} else return null;
},
async serialize(value, serializeOptions) {
return serialize(name, value === "" ? "" : await encodeCookieValue(value, secrets), {
...options,
...serializeOptions
});
}
};
};
/**
* Returns `true` if a value is a React Router {@link Cookie} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
const isCookie = (object) => {
return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function";
};
async function encodeCookieValue(value, secrets) {
let encoded = encodeData(value);
if (secrets.length > 0) encoded = await sign(encoded, secrets[0]);
return encoded;
}
async function decodeCookieValue(value, secrets) {
if (secrets.length > 0) {
for (let secret of secrets) {
let unsignedValue = await unsign(value, secret);
if (unsignedValue !== false) return decodeData(unsignedValue);
}
return null;
}
return decodeData(value);
}
function encodeData(value) {
return btoa(myUnescape(encodeURIComponent(JSON.stringify(value))));
}
function decodeData(value) {
try {
return JSON.parse(decodeURIComponent(myEscape(atob(value))));
} catch (e) {
return {};
}
}
function myEscape(value) {
let str = value.toString();
let result = "";
let index = 0;
let chr, code;
while (index < str.length) {
chr = str.charAt(index++);
if (/[\w*+\-./@]/.exec(chr)) result += chr;
else {
code = chr.charCodeAt(0);
if (code < 256) result += "%" + hex(code, 2);
else result += "%u" + hex(code, 4).toUpperCase();
}
}
return result;
}
function hex(code, length) {
let result = code.toString(16);
while (result.length < length) result = "0" + result;
return result;
}
function myUnescape(value) {
let str = value.toString();
let result = "";
let index = 0;
let chr, part;
while (index < str.length) {
chr = str.charAt(index++);
if (chr === "%") if (str.charAt(index) === "u") {
part = str.slice(index + 1, index + 5);
if (/^[\da-f]{4}$/i.exec(part)) {
result += String.fromCharCode(parseInt(part, 16));
index += 5;
continue;
}
} else {
part = str.slice(index, index + 2);
if (/^[\da-f]{2}$/i.exec(part)) {
result += String.fromCharCode(parseInt(part, 16));
index += 2;
continue;
}
}
result += chr;
}
return result;
}
function warnOnceAboutExpiresCookie(name, expires) {
warnOnce(!expires, `The "${name}" cookie has an "expires" property set. This will cause the expires value to not be updated when the session is committed. Instead, you should set the expires value when serializing the cookie. You can use \`commitSession(session, { expires })\` if using a session storage object, or \`cookie.serialize("value", { expires })\` if you're using the cookie directly.`);
}
//#endregion
export { createCookie, isCookie };
@@ -0,0 +1,43 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/crypto.ts
const encoder = /* @__PURE__ */ new TextEncoder();
const sign = async (value, secret) => {
let data = encoder.encode(value);
let key = await createKey(secret, ["sign"]);
let signature = await crypto.subtle.sign("HMAC", key, data);
let hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/=+$/, "");
return value + "." + hash;
};
const unsign = async (cookie, secret) => {
let index = cookie.lastIndexOf(".");
let value = cookie.slice(0, index);
let hash = cookie.slice(index + 1);
let data = encoder.encode(value);
let key = await createKey(secret, ["verify"]);
try {
let signature = byteStringToUint8Array(atob(hash));
return await crypto.subtle.verify("HMAC", key, signature, data) ? value : false;
} catch (e) {
return false;
}
};
const createKey = async (secret, usages) => crypto.subtle.importKey("raw", encoder.encode(secret), {
name: "HMAC",
hash: "SHA-256"
}, false, usages);
function byteStringToUint8Array(byteString) {
let array = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) array[i] = byteString.charCodeAt(i);
return array;
}
//#endregion
export { sign, unsign };
@@ -0,0 +1,25 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { isDataWithResponseInit, isRedirectStatusCode } from "../router/router.js";
//#region lib/server-runtime/data.ts
async function callRouteHandler(handler, args) {
let result = await handler({
request: args.request,
url: args.url,
params: args.params,
context: args.context,
pattern: args.pattern
});
if (isDataWithResponseInit(result) && result.init && result.init.status && isRedirectStatusCode(result.init.status)) throw new Response(null, result.init);
return result;
}
//#endregion
export { callRouteHandler };
@@ -0,0 +1,9 @@
//#region lib/server-runtime/dev.d.ts
type DevServerHooks = {
getCriticalCss?: (pathname: string) => Promise<string | undefined>;
processRequestError?: (error: unknown) => void;
};
declare function setDevServerHooks(devServerHooks: DevServerHooks): void;
//#endregion
export { setDevServerHooks };
@@ -0,0 +1,26 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/dev.ts
const globalDevServerHooksKey = "__reactRouterDevServerHooks";
function setDevServerHooks(devServerHooks) {
globalThis[globalDevServerHooksKey] = devServerHooks;
}
function getDevServerHooks() {
return globalThis[globalDevServerHooksKey];
}
function getBuildTimeHeader(request, headerName) {
if (typeof process !== "undefined") try {
if (process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") && process.env.IS_RR_BUILD_REQUEST === "yes") return request.headers.get(headerName);
} catch (e) {}
return null;
}
//#endregion
export { getBuildTimeHeader, getDevServerHooks, setDevServerHooks };
@@ -0,0 +1,20 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/entry.ts
function createEntryRouteModules(manifest) {
return Object.keys(manifest).reduce((memo, routeId) => {
let route = manifest[routeId];
if (route) memo[routeId] = route.module;
return memo;
}, {});
}
//#endregion
export { createEntryRouteModules };
@@ -0,0 +1,75 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import "./mode.js";
//#region lib/server-runtime/errors.ts
/**
* This thing probably warrants some explanation.
*
* The whole point here is to emulate componentDidCatch for server rendering and
* data loading. It can get tricky. React can do this on component boundaries
* but doesn't support it for server rendering or data loading. We know enough
* with nested routes to be able to emulate the behavior (because we know them
* statically before rendering.)
*
* Each route can export an `ErrorBoundary`.
*
* - When rendering throws an error, the nearest error boundary will render
* (normal react componentDidCatch). This will be the route's own boundary, but
* if none is provided, it will bubble up to the parents.
* - When data loading throws an error, the nearest error boundary will render
* - When performing an action, the nearest error boundary for the action's
* route tree will render (no redirect happens)
*
* During normal react rendering, we do nothing special, just normal
* componentDidCatch.
*
* For server rendering, we mutate `renderBoundaryRouteId` to know the last
* layout that has an error boundary that tried to render. This emulates which
* layout would catch a thrown error. If the rendering fails, we catch the error
* on the server, and go again a second time with the emulator holding on to the
* information it needs to render the same error boundary as a dynamically
* thrown render error.
*
* When data loading, server or client side, we use the emulator to likewise
* hang on to the error and re-render at the appropriate layout (where a thrown
* error would have been caught by cDC).
*
* When actions throw, it all works the same. There's an edge case to be aware
* of though. Actions normally are required to redirect, but in the case of
* errors, we render the action's route with the emulator holding on to the
* error. If during this render a parent route/loader throws we ignore that new
* error and render the action's original error as deeply as possible. In other
* words, we simply ignore the new error and use the action's error in place
* because it came first, and that just wouldn't be fair to let errors cut in
* line.
*/
function sanitizeError(error, serverMode) {
if (error instanceof Error && serverMode !== "development") {
let sanitized = /* @__PURE__ */ new Error("Unexpected Server Error");
sanitized.stack = void 0;
return sanitized;
}
return error;
}
function sanitizeErrors(errors, serverMode) {
return Object.entries(errors).reduce((acc, [routeId, error]) => {
return Object.assign(acc, { [routeId]: sanitizeError(error, serverMode) });
}, {});
}
function serializeError(error, serverMode) {
let sanitized = sanitizeError(error, serverMode);
return {
message: sanitized.message,
stack: sanitized.stack
};
}
//#endregion
export { sanitizeError, sanitizeErrors, serializeError };
@@ -0,0 +1,73 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import invariant from "./invariant.js";
import { splitSetCookieString } from "cookie-es";
//#region lib/server-runtime/headers.ts
function getDocumentHeaders(context, build) {
return getDocumentHeadersImpl(context, (m) => {
let route = build.routes[m.route.id];
invariant(route, `Route with id "${m.route.id}" not found in build`);
return route.module.headers;
});
}
function getDocumentHeadersImpl(context, getRouteHeadersFn, _defaultHeaders) {
let boundaryIdx = context.errors ? context.matches.findIndex((m) => context.errors[m.route.id]) : -1;
let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches;
let errorHeaders;
if (boundaryIdx >= 0) {
let { actionHeaders, actionData, loaderHeaders, loaderData } = context;
context.matches.slice(boundaryIdx).some((match) => {
let id = match.route.id;
if (actionHeaders[id] && (!actionData || !actionData.hasOwnProperty(id))) errorHeaders = actionHeaders[id];
else if (loaderHeaders[id] && !loaderData.hasOwnProperty(id)) errorHeaders = loaderHeaders[id];
return errorHeaders != null;
});
}
const defaultHeaders = new Headers(_defaultHeaders);
return matches.reduce((parentHeaders, match, idx) => {
let { id } = match.route;
let loaderHeaders = context.loaderHeaders[id] || new Headers();
let actionHeaders = context.actionHeaders[id] || new Headers();
let includeErrorHeaders = errorHeaders != null && idx === matches.length - 1;
let includeErrorCookies = includeErrorHeaders && errorHeaders !== loaderHeaders && errorHeaders !== actionHeaders;
let headersFn = getRouteHeadersFn(match);
if (headersFn == null) {
let headers = new Headers(parentHeaders);
if (includeErrorCookies) prependCookies(errorHeaders, headers);
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
return headers;
}
let headers = new Headers(typeof headersFn === "function" ? headersFn({
loaderHeaders,
parentHeaders,
actionHeaders,
errorHeaders: includeErrorHeaders ? errorHeaders : void 0
}) : headersFn);
if (includeErrorCookies) prependCookies(errorHeaders, headers);
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
prependCookies(parentHeaders, headers);
return headers;
}, new Headers(defaultHeaders));
}
function prependCookies(parentHeaders, childHeaders) {
let parentSetCookieString = parentHeaders.get("Set-Cookie");
if (parentSetCookieString) {
let cookies = splitSetCookieString(parentSetCookieString);
let childCookies = new Set(childHeaders.getSetCookie());
cookies.forEach((cookie) => {
if (!childCookies.has(cookie)) childHeaders.append("Set-Cookie", cookie);
});
}
}
//#endregion
export { getDocumentHeaders };
@@ -0,0 +1,19 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/invariant.ts
function invariant(value, message) {
if (value === false || value === null || typeof value === "undefined") {
console.error("The following error is a bug in React Router; please open an issue! https://github.com/remix-run/react-router/issues/new/choose");
throw new Error(message);
}
}
//#endregion
export { invariant as default };
@@ -0,0 +1,12 @@
//#region lib/server-runtime/mode.d.ts
/**
* The mode to use when running the server.
*/
declare enum ServerMode {
Development = "development",
Production = "production",
Test = "test"
}
//#endregion
export { ServerMode };
@@ -0,0 +1,25 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/mode.ts
/**
* The mode to use when running the server.
*/
let ServerMode = /* @__PURE__ */ function(ServerMode) {
ServerMode["Development"] = "development";
ServerMode["Production"] = "production";
ServerMode["Test"] = "test";
return ServerMode;
}({});
function isServerMode(value) {
return value === "development" || value === "production" || value === "test";
}
//#endregion
export { ServerMode, isServerMode };
@@ -0,0 +1,28 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { matchRoutesImpl } from "../router/utils.js";
import invariant from "./invariant.js";
//#region lib/server-runtime/routeMatching.ts
function matchServerRoutes(manifest, dataRoutes, branches, pathname, basename) {
let matches = matchRoutesImpl(dataRoutes, pathname, basename ?? "/", false, branches);
if (!matches) return null;
return matches.map((match) => {
let route = manifest[match.route.id];
invariant(route, `Route with id "${match.route.id}" not found in manifest.`);
return {
params: match.params,
pathname: match.pathname,
route
};
});
}
//#endregion
export { matchServerRoutes };
@@ -0,0 +1,13 @@
import { RouteManifest } from "../router/utils.js";
import { ServerRouteModule } from "../dom/ssr/routeModules.js";
import { Route } from "../dom/ssr/routes.js";
//#region lib/server-runtime/routes.d.ts
type ServerRouteManifest = RouteManifest<Omit<ServerRoute, "children">>;
interface ServerRoute extends Route {
children: ServerRoute[];
module: ServerRouteModule;
}
//#endregion
export { ServerRouteManifest };
@@ -0,0 +1,74 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { redirect, redirectDocument, replace } from "../router/utils.js";
import { SingleFetchRedirectSymbol, decodeViaTurboStream } from "../dom/ssr/single-fetch.js";
import invariant from "./invariant.js";
import { callRouteHandler } from "./data.js";
import { getBuildTimeHeader } from "./dev.js";
//#region lib/server-runtime/routes.ts
function groupRoutesByParentId(manifest) {
let routes = {};
Object.values(manifest).forEach((route) => {
if (route) {
let parentId = route.parentId || "";
if (!routes[parentId]) routes[parentId] = [];
routes[parentId].push(route);
}
});
return routes;
}
function createStaticHandlerDataRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map((route) => {
let commonRoute = {
id: route.id,
path: route.path,
middleware: route.module.middleware,
loader: route.module.loader ? async (args) => {
let preRenderedData = getBuildTimeHeader(args.request, "X-React-Router-Prerender-Data");
if (preRenderedData != null) {
let encoded = preRenderedData ? decodeURI(preRenderedData) : preRenderedData;
invariant(encoded, "Missing prerendered data for route");
let uint8array = new TextEncoder().encode(encoded);
let data = (await decodeViaTurboStream(new ReadableStream({ start(controller) {
controller.enqueue(uint8array);
controller.close();
} }), global)).value;
if (data && SingleFetchRedirectSymbol in data) {
let result = data[SingleFetchRedirectSymbol];
let init = { status: result.status };
if (result.reload) throw redirectDocument(result.redirect, init);
else if (result.replace) throw replace(result.redirect, init);
else throw redirect(result.redirect, init);
} else {
invariant(data && route.id in data, "Unable to decode prerendered data");
let result = data[route.id];
invariant("data" in result, "Unable to process prerendered data");
return result.data;
}
}
return await callRouteHandler(route.module.loader, args);
} : void 0,
action: route.module.action ? (args) => callRouteHandler(route.module.action, args) : void 0,
ErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null ? () => null : void 0,
handle: route.module.handle
};
return route.index ? {
index: true,
...commonRoute
} : {
caseSensitive: route.caseSensitive,
children: createStaticHandlerDataRoutes(manifest, route.id, routesByParentId),
...commonRoute
};
});
}
//#endregion
export { createStaticHandlerDataRoutes };
@@ -0,0 +1,22 @@
import { RouterContextProvider } from "../router/utils.js";
import { ServerBuild } from "./build.js";
//#region lib/server-runtime/server.d.ts
type RequestHandler = (request: Request, loadContext?: RouterContextProvider) => Promise<Response>;
type CreateRequestHandlerFunction = (build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>), mode?: string) => RequestHandler;
/**
* Creates a request handler for a React Router server build.
*
* This is a low-level API used by server adapters to translate incoming
* requests into React Router responses.
*
* @category Utils
* @param build The server build, or a function that resolves to the server
* build, used to handle requests.
* @param mode The mode in which the server build is running.
* @returns A request handler that returns a response for each incoming request.
*/
declare const createRequestHandler: CreateRequestHandlerFunction;
//#endregion
export { CreateRequestHandlerFunction, RequestHandler, createRequestHandler };
@@ -0,0 +1,364 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl, RouterContextProvider, createDataFunctionUrl, defaultMapRouteProperties, getRoutePattern, isRouteErrorResponse, removeTrailingSlash, stripBasename } from "../router/utils.js";
import { instrumentHandler, instrumentationResultMetaContext } from "../router/instrumentation.js";
import { createStaticHandler, getStaticContextFromError, isMutationMethod, isRedirectResponse, isResponse } from "../router/router.js";
import { getManifestPath } from "../dom/ssr/fog-of-war.js";
import { createEntryRouteModules } from "./entry.js";
import { isServerMode } from "./mode.js";
import { sanitizeErrors, serializeError } from "./errors.js";
import { matchServerRoutes } from "./routeMatching.js";
import { getBuildTimeHeader, getDevServerHooks } from "./dev.js";
import { createStaticHandlerDataRoutes } from "./routes.js";
import { createServerHandoffString } from "./serverHandoff.js";
import { getDocumentHeaders } from "./headers.js";
import { throwIfPotentialCSRFAttack } from "../actions.js";
import { getNormalizedPath } from "./urls.js";
import { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders } from "./single-fetch.js";
//#region lib/server-runtime/server.ts
function derive(build, mode) {
let dataRoutes = createStaticHandlerDataRoutes(build.routes);
let serverMode = isServerMode(mode) ? mode : "production";
let staticHandler = createStaticHandler(dataRoutes, {
basename: build.basename,
mapRouteProperties: defaultMapRouteProperties,
instrumentations: build.entry.module.instrumentations,
future: build.future
});
let errorHandler = build.entry.module.handleError || ((error, { request }) => {
if (serverMode !== "test" && !request.signal.aborted) console.error(isRouteErrorResponse(error) && error.error ? error.error : error);
});
let requestHandlerInstrumentations = build.entry.module.instrumentations?.map((i) => i.handler).filter(Boolean);
let requestHandler = async (request, initialContext) => {
let params = {};
let loadContext;
let handleError = (error) => {
if (mode === "development") getDevServerHooks()?.processRequestError?.(error);
errorHandler(error, {
context: loadContext,
params,
request
});
};
if (initialContext && !(initialContext instanceof RouterContextProvider)) {
let error = /* @__PURE__ */ new Error("Invalid `context` value provided to `handleRequest`. You must return an instance of `RouterContextProvider` from your `getLoadContext` function.");
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
loadContext = initialContext || new RouterContextProvider();
let requestUrl = new URL(request.url);
let normalizedPath = getNormalizedPath(request);
let normalizedPathname = normalizedPath.pathname;
let isSpaMode = getBuildTimeHeader(request, "X-React-Router-SPA-Mode") === "yes";
if (!build.ssr) {
let decodedPath = decodeURI(normalizedPathname);
if (build.basename && build.basename !== "/") {
let strippedPath = stripBasename(decodedPath, build.basename);
if (strippedPath == null) {
errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to prerender the \`${decodedPath}\` path because it does not start with the basename \`${build.basename}\``), {
context: loadContext,
params,
request
});
return new Response("Not Found", {
status: 404,
statusText: "Not Found"
});
}
decodedPath = strippedPath;
}
if (build.prerender.length === 0) isSpaMode = true;
else if (!build.prerender.some((p) => removeTrailingSlash(p) === removeTrailingSlash(decodedPath))) if (requestUrl.pathname.endsWith(".data")) {
errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to SSR the path \`${decodedPath}\` because \`ssr:false\` is set and the path is not included in the \`prerender\` config, so in production the path will be a 404.`), {
context: loadContext,
params,
request
});
return new Response("Not Found", {
status: 404,
statusText: "Not Found"
});
} else isSpaMode = true;
}
let manifestUrl = getManifestPath(build.routeDiscovery.manifestPath, build.basename);
if (build.routeDiscovery.mode === "lazy" && requestUrl.pathname === manifestUrl) try {
return await handleManifestRequest(build, staticHandler.dataRoutes, staticHandler._internalRouteBranches, requestUrl);
} catch (e) {
handleError(e);
return new Response("Unknown Server Error", { status: 500 });
}
let matches = matchServerRoutes(build.routes, staticHandler.dataRoutes, staticHandler._internalRouteBranches, normalizedPathname, build.basename);
if (matches && matches.length > 0) Object.assign(params, matches[0].params);
if (requestHandlerInstrumentations?.length) loadContext.set(instrumentationResultMetaContext, {
url: createDataFunctionUrl(request, normalizedPath),
pattern: matches ? getRoutePattern(matches) : "",
params: matches?.[0]?.params ? { ...matches[0].params } : {}
});
let response;
if (requestUrl.pathname.endsWith(".data")) {
response = await handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError);
if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
if (build.entry.module.handleDataRequest) {
response = await build.entry.module.handleDataRequest(response, {
context: loadContext,
params: matches ? matches[0].params : {},
request
});
if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
}
} else if (!isSpaMode && matches && matches[matches.length - 1].route.module.default == null && matches[matches.length - 1].route.module.ErrorBoundary == null) response = await handleResourceRequest(serverMode, build, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError);
else {
let { pathname } = requestUrl;
let criticalCss = void 0;
if (build.unstable_getCriticalCss) criticalCss = await build.unstable_getCriticalCss({ pathname });
else if (mode === "development" && getDevServerHooks()?.getCriticalCss) criticalCss = await getDevServerHooks()?.getCriticalCss?.(pathname);
response = await handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss);
}
if (request.method === "HEAD") return new Response(null, {
headers: response.headers,
status: response.status,
statusText: response.statusText
});
return response;
};
if (requestHandlerInstrumentations?.length) requestHandler = instrumentHandler(requestHandler, requestHandlerInstrumentations);
return {
serverMode,
staticHandler,
errorHandler,
requestHandler
};
}
/**
* Creates a request handler for a React Router server build.
*
* This is a low-level API used by server adapters to translate incoming
* requests into React Router responses.
*
* @category Utils
* @param build The server build, or a function that resolves to the server
* build, used to handle requests.
* @param mode The mode in which the server build is running.
* @returns A request handler that returns a response for each incoming request.
*/
const createRequestHandler = (build, mode) => {
let _build;
let serverMode;
let staticHandler;
let errorHandler;
let _requestHandler;
return async function requestHandler(request, initialContext) {
_build = typeof build === "function" ? await build() : build;
if (typeof build === "function") {
let derived = derive(_build, mode);
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
_requestHandler = derived.requestHandler;
} else if (!serverMode || !staticHandler || !errorHandler || !_requestHandler) {
let derived = derive(_build, mode);
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
_requestHandler = derived.requestHandler;
}
return _requestHandler(request, initialContext);
};
};
async function handleManifestRequest(build, dataRoutes, branches, url) {
if (url.toString().length > 7680) return new Response(null, {
statusText: "Bad Request",
status: 400
});
if (build.assets.version !== url.searchParams.get("version")) return new Response(null, {
status: 204,
headers: { "X-Remix-Reload-Document": "true" }
});
let patches = {};
if (url.searchParams.has("paths")) {
let pathParam = url.searchParams.get("paths") || "";
let paths = new Set(pathParam.split(",").filter(Boolean));
for (let path of paths) {
if (!path.startsWith("/")) path = `/${path}`;
let matches = matchServerRoutes(build.routes, dataRoutes, branches, path, build.basename);
if (matches) for (let match of matches) {
let routeId = match.route.id;
let route = build.assets.routes[routeId];
if (route) patches[routeId] = route;
}
}
return Response.json(patches, { headers: { "Cache-Control": "public, max-age=31536000, immutable" } });
}
return new Response("Invalid Request", { status: 400 });
}
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError) {
return isMutationMethod(request.method) ? await singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError);
}
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
try {
if (isMutationMethod(request.method)) try {
throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
} catch (e) {
handleError(e);
return new Response("Bad Request", { status: 400 });
}
let result = await staticHandler.query(request, {
requestContext: loadContext,
generateMiddlewareResponse: async (query) => {
try {
let innerResult = await query(request);
if (!isResponse(innerResult)) innerResult = await renderHtml(innerResult, isSpaMode);
return innerResult;
} catch (error) {
handleError(error);
return new Response(null, { status: 500 });
}
},
normalizePath: (r) => getNormalizedPath(r)
});
if (!isResponse(result)) result = await renderHtml(result, isSpaMode);
return result;
} catch (error) {
handleError(error);
return new Response(null, { status: 500 });
}
async function renderHtml(context, isSpaMode) {
let headers = getDocumentHeaders(context, build);
if (SERVER_NO_BODY_STATUS_CODES.has(context.statusCode)) return new Response(null, {
status: context.statusCode,
headers
});
if (context.errors) {
Object.values(context.errors).forEach((err) => {
if (!isRouteErrorResponse(err) || err.error) handleError(err);
});
context.errors = sanitizeErrors(context.errors, serverMode);
}
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors
};
let baseServerHandoff = {
basename: build.basename,
future: build.future,
routeDiscovery: build.routeDiscovery,
ssr: build.ssr,
isSpaMode
};
let entryContext = {
manifest: build.assets,
branches: staticHandler._internalRouteBranches,
routeModules: createEntryRouteModules(build.routes),
staticHandlerContext: context,
criticalCss,
serverHandoffString: createServerHandoffString({
...baseServerHandoff,
criticalCss
}),
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {},
future: build.future,
ssr: build.ssr,
routeDiscovery: build.routeDiscovery,
isSpaMode,
serializeError: (err) => serializeError(err, serverMode)
};
let handleDocumentRequestFunction = build.entry.module.default;
try {
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
} catch (error) {
handleError(error);
let errorForSecondRender = error;
if (isResponse(error)) try {
let data = await unwrapResponse(error);
errorForSecondRender = new ErrorResponseImpl(error.status, error.statusText, data);
} catch (e) {}
context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
if (context.errors) context.errors = sanitizeErrors(context.errors, serverMode);
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors
};
entryContext = {
...entryContext,
staticHandlerContext: context,
serverHandoffString: createServerHandoffString(baseServerHandoff),
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
};
try {
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
} catch (error) {
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
}
}
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
return handleQueryRouteResult(await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext,
generateMiddlewareResponse: async (queryRoute) => {
try {
return handleQueryRouteResult(await queryRoute(request));
} catch (error) {
return handleQueryRouteError(error);
}
},
normalizePath: (r) => getNormalizedPath(r)
}));
} catch (error) {
return handleQueryRouteError(error);
}
function handleQueryRouteResult(result) {
if (isResponse(result)) return result;
if (typeof result === "string") return new Response(result);
return Response.json(result);
}
function handleQueryRouteError(error) {
if (isResponse(error)) return error;
if (isRouteErrorResponse(error)) {
handleError(error);
return errorResponseToJson(error, serverMode);
}
if (error instanceof Error && error.message === "Expected a response from queryRoute") {
let newError = /* @__PURE__ */ new Error("Expected a Response to be returned from resource route handler");
handleError(newError);
return returnLastResortErrorResponse(newError, serverMode);
}
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
function errorResponseToJson(errorResponse, serverMode) {
return Response.json(serializeError(errorResponse.error || /* @__PURE__ */ new Error("Unexpected Server Error"), serverMode), {
status: errorResponse.status,
statusText: errorResponse.statusText
});
}
function returnLastResortErrorResponse(error, serverMode) {
let message = "Unexpected Server Error";
if (serverMode !== "production") message += `\n\n${String(error)}`;
return new Response(message, {
status: 500,
headers: { "Content-Type": "text/plain" }
});
}
function unwrapResponse(response) {
let contentType = response.headers.get("Content-Type");
return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
}
//#endregion
export { createRequestHandler };
@@ -0,0 +1,17 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { escapeHtml } from "../dom/ssr/markup.js";
//#region lib/server-runtime/serverHandoff.ts
function createServerHandoffString(serverHandoff) {
return escapeHtml(JSON.stringify(serverHandoff));
}
//#endregion
export { createServerHandoffString };
@@ -0,0 +1,174 @@
import { Cookie, CookieOptions } from "./cookies.js";
import { CookieParseOptions, CookieSerializeOptions } from "cookie-es";
//#region lib/server-runtime/sessions.d.ts
/**
* An object of name/value pairs to be used in the session.
*/
interface SessionData {
[name: string]: any;
}
/**
* Session persists data across HTTP requests.
*
* @see https://reactrouter.com/explanation/sessions-and-cookies#sessions
*/
interface Session<Data = SessionData, FlashData = Data> {
/**
* A unique identifier for this session.
*
* Note: This will be the empty string for newly created sessions and
* sessions that are not backed by a database (i.e. cookie-based sessions).
*/
readonly id: string;
/**
* The raw data contained in this session.
*
* This is useful mostly for SessionStorage internally to access the raw
* session data to persist.
*/
readonly data: FlashSessionData<Data, FlashData>;
/**
* Returns `true` if the session has a value for the given `name`, `false`
* otherwise.
*/
has(name: (keyof Data | keyof FlashData) & string): boolean;
/**
* Returns the value for the given `name` in this session.
*/
get<Key extends (keyof Data | keyof FlashData) & string>(name: Key): (Key extends keyof Data ? Data[Key] : undefined) | (Key extends keyof FlashData ? FlashData[Key] : undefined) | undefined;
/**
* Sets a value in the session for the given `name`.
*/
set<Key extends keyof Data & string>(name: Key, value: Data[Key]): void;
/**
* Sets a value in the session that is only valid until the next `get()`.
* This can be useful for temporary values, like error messages.
*/
flash<Key extends keyof FlashData & string>(name: Key, value: FlashData[Key]): void;
/**
* Removes a value from the session.
*/
unset(name: keyof Data & string): void;
}
type FlashSessionData<Data, FlashData> = Partial<Data & { [Key in keyof FlashData as FlashDataKey<Key & string>]: FlashData[Key] }>;
type FlashDataKey<Key extends string> = `__flash_${Key}__`;
type CreateSessionFunction = <Data = SessionData, FlashData = Data>(initialData?: Data, id?: string) => Session<Data, FlashData>;
/**
* Creates a new Session object.
*
* Note: This function is typically not invoked directly by application code.
* Instead, use a `SessionStorage` object's `getSession` method.
*
* @category Utils
* @param initialData The initial data for the session.
* @param id The identifier for the session. Defaults to an empty string for a
* new session.
* @returns A new {@link Session} object.
*/
declare const createSession: CreateSessionFunction;
/**
* A function that determines whether a value is a React Router {@link Session}
* object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
type IsSessionFunction = (object: any) => object is Session;
/**
* Returns `true` if a value is a React Router {@link Session} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
declare const isSession: IsSessionFunction;
/**
* SessionStorage stores session data between HTTP requests and knows how to
* parse and create cookies.
*
* A SessionStorage creates Session objects using a `Cookie` header as input.
* Then, later it generates the `Set-Cookie` header to be used in the response.
*/
interface SessionStorage<Data = SessionData, FlashData = Data> {
/**
* Parses a Cookie header from a HTTP request and returns the associated
* Session. If there is no session associated with the cookie, this will
* return a new Session with no data.
*/
getSession: (cookieHeader?: string | null, options?: CookieParseOptions) => Promise<Session<Data, FlashData>>;
/**
* Stores all data in the Session and returns the Set-Cookie header to be
* used in the HTTP response.
*/
commitSession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
/**
* Deletes all data associated with the Session and returns the Set-Cookie
* header to be used in the HTTP response.
*/
destroySession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
}
/**
* SessionIdStorageStrategy is designed to allow anyone to easily build their
* own SessionStorage using `createSessionStorage(strategy)`.
*
* This strategy describes a common scenario where the session id is stored in
* a cookie but the actual session data is stored elsewhere, usually in a
* database or on disk. A set of create, read, update, and delete operations
* are provided for managing the session data.
*/
interface SessionIdStorageStrategy<Data = SessionData, FlashData = Data> {
/**
* The Cookie used to store the session id, or options used to automatically
* create one.
*/
cookie?: Cookie | (CookieOptions & {
name?: string;
});
/**
* Creates a new record with the given data and returns the session id.
*/
createData: (data: FlashSessionData<Data, FlashData>, expires?: Date) => Promise<string>;
/**
* Returns data for a given session id, or `null` if there isn't any.
*/
readData: (id: string) => Promise<FlashSessionData<Data, FlashData> | null>;
/**
* Updates data for the given session id.
*/
updateData: (id: string, data: FlashSessionData<Data, FlashData>, expires?: Date) => Promise<void>;
/**
* Deletes data for a given session id from the data store.
*/
deleteData: (id: string) => Promise<void>;
}
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.
*
* Note: This is a low-level API that should only be used if none of the
* existing session storage options meet your requirements.
*
* @category Utils
* @param strategy The strategy used to store session identifiers and data.
* @returns A {@link SessionStorage} object that persists session data using the
* provided strategy.
*/
declare function createSessionStorage<Data = SessionData, FlashData = Data>({
cookie: cookieArg,
createData,
readData,
updateData,
deleteData
}: SessionIdStorageStrategy<Data, FlashData>): SessionStorage<Data, FlashData>;
//#endregion
export { FlashSessionData, IsSessionFunction, Session, SessionData, SessionIdStorageStrategy, SessionStorage, createSession, createSessionStorage, isSession };
@@ -0,0 +1,115 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { warnOnce } from "./warnings.js";
import { createCookie, isCookie } from "./cookies.js";
//#region lib/server-runtime/sessions.ts
function flash(name) {
return `__flash_${name}__`;
}
/**
* Creates a new Session object.
*
* Note: This function is typically not invoked directly by application code.
* Instead, use a `SessionStorage` object's `getSession` method.
*
* @category Utils
* @param initialData The initial data for the session.
* @param id The identifier for the session. Defaults to an empty string for a
* new session.
* @returns A new {@link Session} object.
*/
const createSession = (initialData = {}, id = "") => {
let map = new Map(Object.entries(initialData));
return {
get id() {
return id;
},
get data() {
return Object.fromEntries(map);
},
has(name) {
return map.has(name) || map.has(flash(name));
},
get(name) {
if (map.has(name)) return map.get(name);
let flashName = flash(name);
if (map.has(flashName)) {
let value = map.get(flashName);
map.delete(flashName);
return value;
}
},
set(name, value) {
map.set(name, value);
},
flash(name, value) {
map.set(flash(name), value);
},
unset(name) {
map.delete(name);
}
};
};
/**
* Returns `true` if a value is a React Router {@link Session} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
const isSession = (object) => {
return object != null && typeof object.id === "string" && typeof object.data !== "undefined" && typeof object.has === "function" && typeof object.get === "function" && typeof object.set === "function" && typeof object.flash === "function" && typeof object.unset === "function";
};
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.
*
* Note: This is a low-level API that should only be used if none of the
* existing session storage options meet your requirements.
*
* @category Utils
* @param strategy The strategy used to store session identifiers and data.
* @returns A {@link SessionStorage} object that persists session data using the
* provided strategy.
*/
function createSessionStorage({ cookie: cookieArg, createData, readData, updateData, deleteData }) {
let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
warnOnceAboutSigningSessionCookie(cookie);
return {
async getSession(cookieHeader, options) {
let id = cookieHeader && await cookie.parse(cookieHeader, options);
return createSession(id && await readData(id) || {}, id || "");
},
async commitSession(session, options) {
let { id, data } = session;
let expires = options?.maxAge != null ? new Date(Date.now() + options.maxAge * 1e3) : options?.expires != null ? options.expires : cookie.expires;
if (id) await updateData(id, data, expires);
else id = await createData(data, expires);
return cookie.serialize(id, options);
},
async destroySession(session, options) {
await deleteData(session.id);
return cookie.serialize("", {
...options,
maxAge: void 0,
expires: /* @__PURE__ */ new Date(0)
});
}
};
}
function warnOnceAboutSigningSessionCookie(cookie) {
warnOnce(cookie.isSigned, `The "${cookie.name}" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.`);
}
//#endregion
export { createSession, createSessionStorage, isSession, warnOnceAboutSigningSessionCookie };
@@ -0,0 +1,33 @@
import { SessionData, SessionIdStorageStrategy, SessionStorage } from "../sessions.js";
//#region lib/server-runtime/sessions/cookieStorage.d.ts
interface CookieSessionStorageOptions {
/**
* The Cookie used to store the session data on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}
/**
* Creates and returns a SessionStorage object that stores all session data
* directly in the session cookie itself.
*
* This has the advantage that no database or other backend services are
* needed, and can help to simplify some load-balanced scenarios. However, it
* also has the limitation that serialized session data may not exceed the
* browser's maximum cookie size. Trade-offs!
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the cookie-backed session storage.
* @returns A {@link SessionStorage} object that stores all session data in its
* cookie.
*/
declare function createCookieSessionStorage<Data = SessionData, FlashData = Data>({
cookie: cookieArg
}?: CookieSessionStorageOptions): SessionStorage<Data, FlashData>;
//#endregion
export { createCookieSessionStorage };
@@ -0,0 +1,53 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createCookie, isCookie } from "../cookies.js";
import { createSession, warnOnceAboutSigningSessionCookie } from "../sessions.js";
//#region lib/server-runtime/sessions/cookieStorage.ts
/**
* Creates and returns a SessionStorage object that stores all session data
* directly in the session cookie itself.
*
* This has the advantage that no database or other backend services are
* needed, and can help to simplify some load-balanced scenarios. However, it
* also has the limitation that serialized session data may not exceed the
* browser's maximum cookie size. Trade-offs!
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the cookie-backed session storage.
* @returns A {@link SessionStorage} object that stores all session data in its
* cookie.
*/
function createCookieSessionStorage({ cookie: cookieArg } = {}) {
let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
warnOnceAboutSigningSessionCookie(cookie);
return {
async getSession(cookieHeader, options) {
return createSession(cookieHeader && await cookie.parse(cookieHeader, options) || {});
},
async commitSession(session, options) {
let serializedCookie = await cookie.serialize(session.data, options);
if (serializedCookie.length > 4096) throw new Error("Cookie length will exceed browser maximum. Length: " + serializedCookie.length);
return serializedCookie;
},
async destroySession(_session, options) {
return cookie.serialize("", {
...options,
maxAge: void 0,
expires: /* @__PURE__ */ new Date(0)
});
}
};
}
//#endregion
export { createCookieSessionStorage };
@@ -0,0 +1,29 @@
import { SessionData, SessionIdStorageStrategy, SessionStorage } from "../sessions.js";
//#region lib/server-runtime/sessions/memoryStorage.d.ts
interface MemorySessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}
/**
* Creates and returns a simple in-memory SessionStorage object.
*
* Intended for local development and testing. It does not scale beyond a single
* process, and all session data is lost when the server process stops/restarts.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the in-memory session storage.
* @returns A {@link SessionStorage} object that stores session data in memory.
*/
declare function createMemorySessionStorage<Data = SessionData, FlashData = Data>({
cookie
}?: MemorySessionStorageOptions): SessionStorage<Data, FlashData>;
//#endregion
export { createMemorySessionStorage };

Some files were not shown because too many files have changed in this diff Show More