123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- 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 ? 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,
- v7_skipActionErrorRevalidation: false
- };
- },
- 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");
- },
- patchRoutes() {
- throw msg("patchRoutes");
- },
- _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);
- // Treating this as a full URL will strip any trailing spaces so we need to
- // pre-encode them since they might be part of a matching splat param from
- // an ancestor route
- 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
- };
- }
- 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 };
|