290 lines
9.3 KiB
JavaScript
290 lines
9.3 KiB
JavaScript
|
import * as React from 'react';
|
||
|
import { Action, UNSAFE_invariant, isRouteErrorResponse, createStaticHandler as createStaticHandler$1, UNSAFE_convertRoutesToDataRoutes, IDLE_NAVIGATION, IDLE_FETCHER, IDLE_BLOCKER } from '@remix-run/router';
|
||
|
import { UNSAFE_useRoutesImpl, UNSAFE_mapRouteProperties } from 'react-router';
|
||
|
import { parsePath, Router, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, UNSAFE_FetchersContext, UNSAFE_ViewTransitionContext, createPath } from 'react-router-dom';
|
||
|
|
||
|
/**
|
||
|
* A `<Router>` that may not navigate to any other location. This is useful
|
||
|
* on the server where there is no stateful UI.
|
||
|
*/
|
||
|
function StaticRouter({
|
||
|
basename,
|
||
|
children,
|
||
|
location: locationProp = "/",
|
||
|
future
|
||
|
}) {
|
||
|
if (typeof locationProp === "string") {
|
||
|
locationProp = parsePath(locationProp);
|
||
|
}
|
||
|
let action = Action.Pop;
|
||
|
let location = {
|
||
|
pathname: locationProp.pathname || "/",
|
||
|
search: locationProp.search || "",
|
||
|
hash: locationProp.hash || "",
|
||
|
state: locationProp.state || null,
|
||
|
key: locationProp.key || "default"
|
||
|
};
|
||
|
let staticNavigator = getStatelessNavigator();
|
||
|
return /*#__PURE__*/React.createElement(Router, {
|
||
|
basename: basename,
|
||
|
children: children,
|
||
|
location: location,
|
||
|
navigationType: action,
|
||
|
navigator: staticNavigator,
|
||
|
future: future,
|
||
|
static: true
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* A Data Router that may not navigate to any other location. This is useful
|
||
|
* on the server where there is no stateful UI.
|
||
|
*/
|
||
|
function StaticRouterProvider({
|
||
|
context,
|
||
|
router,
|
||
|
hydrate = true,
|
||
|
nonce
|
||
|
}) {
|
||
|
!(router && context) ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "You must provide `router` and `context` to <StaticRouterProvider>") : UNSAFE_invariant(false) : void 0;
|
||
|
let dataRouterContext = {
|
||
|
router,
|
||
|
navigator: getStatelessNavigator(),
|
||
|
static: true,
|
||
|
staticContext: context,
|
||
|
basename: context.basename || "/"
|
||
|
};
|
||
|
let fetchersContext = new Map();
|
||
|
let hydrateScript = "";
|
||
|
if (hydrate !== false) {
|
||
|
let data = {
|
||
|
loaderData: context.loaderData,
|
||
|
actionData: context.actionData,
|
||
|
errors: serializeErrors(context.errors)
|
||
|
};
|
||
|
// Use JSON.parse here instead of embedding a raw JS object here to speed
|
||
|
// up parsing on the client. Dual-stringify is needed to ensure all quotes
|
||
|
// are properly escaped in the resulting string. See:
|
||
|
// https://v8.dev/blog/cost-of-javascript-2019#json
|
||
|
let json = htmlEscape(JSON.stringify(JSON.stringify(data)));
|
||
|
hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
|
||
|
}
|
||
|
let {
|
||
|
state
|
||
|
} = dataRouterContext.router;
|
||
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UNSAFE_DataRouterContext.Provider, {
|
||
|
value: dataRouterContext
|
||
|
}, /*#__PURE__*/React.createElement(UNSAFE_DataRouterStateContext.Provider, {
|
||
|
value: state
|
||
|
}, /*#__PURE__*/React.createElement(UNSAFE_FetchersContext.Provider, {
|
||
|
value: fetchersContext
|
||
|
}, /*#__PURE__*/React.createElement(UNSAFE_ViewTransitionContext.Provider, {
|
||
|
value: {
|
||
|
isTransitioning: false
|
||
|
}
|
||
|
}, /*#__PURE__*/React.createElement(Router, {
|
||
|
basename: dataRouterContext.basename,
|
||
|
location: state.location,
|
||
|
navigationType: state.historyAction,
|
||
|
navigator: dataRouterContext.navigator,
|
||
|
static: dataRouterContext.static,
|
||
|
future: {
|
||
|
v7_relativeSplatPath: router.future.v7_relativeSplatPath
|
||
|
}
|
||
|
}, /*#__PURE__*/React.createElement(DataRoutes, {
|
||
|
routes: router.routes,
|
||
|
future: router.future,
|
||
|
state: state
|
||
|
})))))), hydrateScript ? /*#__PURE__*/React.createElement("script", {
|
||
|
suppressHydrationWarning: true,
|
||
|
nonce: nonce,
|
||
|
dangerouslySetInnerHTML: {
|
||
|
__html: hydrateScript
|
||
|
}
|
||
|
}) : null);
|
||
|
}
|
||
|
function DataRoutes({
|
||
|
routes,
|
||
|
future,
|
||
|
state
|
||
|
}) {
|
||
|
return UNSAFE_useRoutesImpl(routes, undefined, state, future);
|
||
|
}
|
||
|
function serializeErrors(errors) {
|
||
|
if (!errors) return null;
|
||
|
let entries = Object.entries(errors);
|
||
|
let serialized = {};
|
||
|
for (let [key, val] of entries) {
|
||
|
// Hey you! If you change this, please change the corresponding logic in
|
||
|
// deserializeErrors in react-router-dom/index.tsx :)
|
||
|
if (isRouteErrorResponse(val)) {
|
||
|
serialized[key] = {
|
||
|
...val,
|
||
|
__type: "RouteErrorResponse"
|
||
|
};
|
||
|
} else if (val instanceof Error) {
|
||
|
// Do not serialize stack traces from SSR for security reasons
|
||
|
serialized[key] = {
|
||
|
message: val.message,
|
||
|
__type: "Error",
|
||
|
// If this is a subclass (i.e., ReferenceError), send up the type so we
|
||
|
// can re-create the same type during hydration.
|
||
|
...(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.`);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function createStaticHandler(routes, opts) {
|
||
|
return createStaticHandler$1(routes, {
|
||
|
...opts,
|
||
|
mapRouteProperties: UNSAFE_mapRouteProperties
|
||
|
});
|
||
|
}
|
||
|
function createStaticRouter(routes, context, opts = {}) {
|
||
|
let manifest = {};
|
||
|
let dataRoutes = UNSAFE_convertRoutesToDataRoutes(routes, UNSAFE_mapRouteProperties, undefined, manifest);
|
||
|
|
||
|
// Because our context matches may be from a framework-agnostic set of
|
||
|
// routes passed to createStaticHandler(), we update them here with our
|
||
|
// newly created/enhanced data routes
|
||
|
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 {
|
||
|
v7_fetcherPersist: false,
|
||
|
v7_normalizeFormMethod: false,
|
||
|
v7_partialHydration: opts.future?.v7_partialHydration === true,
|
||
|
v7_prependBasename: false,
|
||
|
v7_relativeSplatPath: opts.future?.v7_relativeSplatPath === true
|
||
|
};
|
||
|
},
|
||
|
get state() {
|
||
|
return {
|
||
|
historyAction: Action.Pop,
|
||
|
location: context.location,
|
||
|
matches,
|
||
|
loaderData: context.loaderData,
|
||
|
actionData: context.actionData,
|
||
|
errors: context.errors,
|
||
|
initialized: true,
|
||
|
navigation: IDLE_NAVIGATION,
|
||
|
restoreScrollPosition: null,
|
||
|
preventScrollReset: false,
|
||
|
revalidation: "idle",
|
||
|
fetchers: new Map(),
|
||
|
blockers: new Map()
|
||
|
};
|
||
|
},
|
||
|
get routes() {
|
||
|
return dataRoutes;
|
||
|
},
|
||
|
get window() {
|
||
|
return undefined;
|
||
|
},
|
||
|
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");
|
||
|
},
|
||
|
dispose() {
|
||
|
throw msg("dispose");
|
||
|
},
|
||
|
getBlocker() {
|
||
|
return IDLE_BLOCKER;
|
||
|
},
|
||
|
deleteBlocker() {
|
||
|
throw msg("deleteBlocker");
|
||
|
},
|
||
|
_internalFetchControllers: new Map(),
|
||
|
_internalActiveDeferreds: new Map(),
|
||
|
_internalSetRoutes() {
|
||
|
throw msg("_internalSetRoutes");
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function createHref(to) {
|
||
|
return typeof to === "string" ? to : createPath(to);
|
||
|
}
|
||
|
function encodeLocation(to) {
|
||
|
let href = typeof to === "string" ? to : createPath(to);
|
||
|
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
|
||
|
};
|
||
|
}
|
||
|
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
||
|
|
||
|
// This utility is based on https://github.com/zertosh/htmlescape
|
||
|
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
|
||
|
const ESCAPE_LOOKUP = {
|
||
|
"&": "\\u0026",
|
||
|
">": "\\u003e",
|
||
|
"<": "\\u003c",
|
||
|
"\u2028": "\\u2028",
|
||
|
"\u2029": "\\u2029"
|
||
|
};
|
||
|
const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
||
|
function htmlEscape(str) {
|
||
|
return str.replace(ESCAPE_REGEX, match => ESCAPE_LOOKUP[match]);
|
||
|
}
|
||
|
|
||
|
export { StaticRouter, StaticRouterProvider, createStaticHandler, createStaticRouter };
|