[d565449] | 1 | import * as React from 'react';
|
---|
| 2 | import { Action, UNSAFE_invariant, isRouteErrorResponse, createStaticHandler as createStaticHandler$1, UNSAFE_convertRoutesToDataRoutes, IDLE_NAVIGATION, IDLE_FETCHER, IDLE_BLOCKER } from '@remix-run/router';
|
---|
| 3 | import { UNSAFE_useRoutesImpl, UNSAFE_mapRouteProperties } from 'react-router';
|
---|
| 4 | import { parsePath, Router, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, UNSAFE_FetchersContext, UNSAFE_ViewTransitionContext, createPath } from 'react-router-dom';
|
---|
| 5 |
|
---|
| 6 | /**
|
---|
| 7 | * A `<Router>` that may not navigate to any other location. This is useful
|
---|
| 8 | * on the server where there is no stateful UI.
|
---|
| 9 | */
|
---|
| 10 | function StaticRouter({
|
---|
| 11 | basename,
|
---|
| 12 | children,
|
---|
| 13 | location: locationProp = "/",
|
---|
| 14 | future
|
---|
| 15 | }) {
|
---|
| 16 | if (typeof locationProp === "string") {
|
---|
| 17 | locationProp = parsePath(locationProp);
|
---|
| 18 | }
|
---|
| 19 | let action = Action.Pop;
|
---|
| 20 | let location = {
|
---|
| 21 | pathname: locationProp.pathname || "/",
|
---|
| 22 | search: locationProp.search || "",
|
---|
| 23 | hash: locationProp.hash || "",
|
---|
| 24 | state: locationProp.state != null ? locationProp.state : null,
|
---|
| 25 | key: locationProp.key || "default"
|
---|
| 26 | };
|
---|
| 27 | let staticNavigator = getStatelessNavigator();
|
---|
| 28 | return /*#__PURE__*/React.createElement(Router, {
|
---|
| 29 | basename: basename,
|
---|
| 30 | children: children,
|
---|
| 31 | location: location,
|
---|
| 32 | navigationType: action,
|
---|
| 33 | navigator: staticNavigator,
|
---|
| 34 | future: future,
|
---|
| 35 | static: true
|
---|
| 36 | });
|
---|
| 37 | }
|
---|
| 38 | /**
|
---|
| 39 | * A Data Router that may not navigate to any other location. This is useful
|
---|
| 40 | * on the server where there is no stateful UI.
|
---|
| 41 | */
|
---|
| 42 | function StaticRouterProvider({
|
---|
| 43 | context,
|
---|
| 44 | router,
|
---|
| 45 | hydrate = true,
|
---|
| 46 | nonce
|
---|
| 47 | }) {
|
---|
| 48 | !(router && context) ? process.env.NODE_ENV !== "production" ? UNSAFE_invariant(false, "You must provide `router` and `context` to <StaticRouterProvider>") : UNSAFE_invariant(false) : void 0;
|
---|
| 49 | let dataRouterContext = {
|
---|
| 50 | router,
|
---|
| 51 | navigator: getStatelessNavigator(),
|
---|
| 52 | static: true,
|
---|
| 53 | staticContext: context,
|
---|
| 54 | basename: context.basename || "/"
|
---|
| 55 | };
|
---|
| 56 | let fetchersContext = new Map();
|
---|
| 57 | let hydrateScript = "";
|
---|
| 58 | if (hydrate !== false) {
|
---|
| 59 | let data = {
|
---|
| 60 | loaderData: context.loaderData,
|
---|
| 61 | actionData: context.actionData,
|
---|
| 62 | errors: serializeErrors(context.errors)
|
---|
| 63 | };
|
---|
| 64 | // Use JSON.parse here instead of embedding a raw JS object here to speed
|
---|
| 65 | // up parsing on the client. Dual-stringify is needed to ensure all quotes
|
---|
| 66 | // are properly escaped in the resulting string. See:
|
---|
| 67 | // https://v8.dev/blog/cost-of-javascript-2019#json
|
---|
| 68 | let json = htmlEscape(JSON.stringify(JSON.stringify(data)));
|
---|
| 69 | hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
|
---|
| 70 | }
|
---|
| 71 | let {
|
---|
| 72 | state
|
---|
| 73 | } = dataRouterContext.router;
|
---|
| 74 | return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UNSAFE_DataRouterContext.Provider, {
|
---|
| 75 | value: dataRouterContext
|
---|
| 76 | }, /*#__PURE__*/React.createElement(UNSAFE_DataRouterStateContext.Provider, {
|
---|
| 77 | value: state
|
---|
| 78 | }, /*#__PURE__*/React.createElement(UNSAFE_FetchersContext.Provider, {
|
---|
| 79 | value: fetchersContext
|
---|
| 80 | }, /*#__PURE__*/React.createElement(UNSAFE_ViewTransitionContext.Provider, {
|
---|
| 81 | value: {
|
---|
| 82 | isTransitioning: false
|
---|
| 83 | }
|
---|
| 84 | }, /*#__PURE__*/React.createElement(Router, {
|
---|
| 85 | basename: dataRouterContext.basename,
|
---|
| 86 | location: state.location,
|
---|
| 87 | navigationType: state.historyAction,
|
---|
| 88 | navigator: dataRouterContext.navigator,
|
---|
| 89 | static: dataRouterContext.static,
|
---|
| 90 | future: {
|
---|
| 91 | v7_relativeSplatPath: router.future.v7_relativeSplatPath
|
---|
| 92 | }
|
---|
| 93 | }, /*#__PURE__*/React.createElement(DataRoutes, {
|
---|
| 94 | routes: router.routes,
|
---|
| 95 | future: router.future,
|
---|
| 96 | state: state
|
---|
| 97 | })))))), hydrateScript ? /*#__PURE__*/React.createElement("script", {
|
---|
| 98 | suppressHydrationWarning: true,
|
---|
| 99 | nonce: nonce,
|
---|
| 100 | dangerouslySetInnerHTML: {
|
---|
| 101 | __html: hydrateScript
|
---|
| 102 | }
|
---|
| 103 | }) : null);
|
---|
| 104 | }
|
---|
| 105 | function DataRoutes({
|
---|
| 106 | routes,
|
---|
| 107 | future,
|
---|
| 108 | state
|
---|
| 109 | }) {
|
---|
| 110 | return UNSAFE_useRoutesImpl(routes, undefined, state, future);
|
---|
| 111 | }
|
---|
| 112 | function serializeErrors(errors) {
|
---|
| 113 | if (!errors) return null;
|
---|
| 114 | let entries = Object.entries(errors);
|
---|
| 115 | let serialized = {};
|
---|
| 116 | for (let [key, val] of entries) {
|
---|
| 117 | // Hey you! If you change this, please change the corresponding logic in
|
---|
| 118 | // deserializeErrors in react-router-dom/index.tsx :)
|
---|
| 119 | if (isRouteErrorResponse(val)) {
|
---|
| 120 | serialized[key] = {
|
---|
| 121 | ...val,
|
---|
| 122 | __type: "RouteErrorResponse"
|
---|
| 123 | };
|
---|
| 124 | } else if (val instanceof Error) {
|
---|
| 125 | // Do not serialize stack traces from SSR for security reasons
|
---|
| 126 | serialized[key] = {
|
---|
| 127 | message: val.message,
|
---|
| 128 | __type: "Error",
|
---|
| 129 | // If this is a subclass (i.e., ReferenceError), send up the type so we
|
---|
| 130 | // can re-create the same type during hydration.
|
---|
| 131 | ...(val.name !== "Error" ? {
|
---|
| 132 | __subType: val.name
|
---|
| 133 | } : {})
|
---|
| 134 | };
|
---|
| 135 | } else {
|
---|
| 136 | serialized[key] = val;
|
---|
| 137 | }
|
---|
| 138 | }
|
---|
| 139 | return serialized;
|
---|
| 140 | }
|
---|
| 141 | function getStatelessNavigator() {
|
---|
| 142 | return {
|
---|
| 143 | createHref,
|
---|
| 144 | encodeLocation,
|
---|
| 145 | push(to) {
|
---|
| 146 | 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.`);
|
---|
| 147 | },
|
---|
| 148 | replace(to) {
|
---|
| 149 | 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.`);
|
---|
| 150 | },
|
---|
| 151 | go(delta) {
|
---|
| 152 | 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.`);
|
---|
| 153 | },
|
---|
| 154 | back() {
|
---|
| 155 | throw new Error(`You cannot use navigator.back() on the server because it is a stateless ` + `environment.`);
|
---|
| 156 | },
|
---|
| 157 | forward() {
|
---|
| 158 | throw new Error(`You cannot use navigator.forward() on the server because it is a stateless ` + `environment.`);
|
---|
| 159 | }
|
---|
| 160 | };
|
---|
| 161 | }
|
---|
| 162 | function createStaticHandler(routes, opts) {
|
---|
| 163 | return createStaticHandler$1(routes, {
|
---|
| 164 | ...opts,
|
---|
| 165 | mapRouteProperties: UNSAFE_mapRouteProperties
|
---|
| 166 | });
|
---|
| 167 | }
|
---|
| 168 | function createStaticRouter(routes, context, opts = {}) {
|
---|
| 169 | let manifest = {};
|
---|
| 170 | let dataRoutes = UNSAFE_convertRoutesToDataRoutes(routes, UNSAFE_mapRouteProperties, undefined, manifest);
|
---|
| 171 |
|
---|
| 172 | // Because our context matches may be from a framework-agnostic set of
|
---|
| 173 | // routes passed to createStaticHandler(), we update them here with our
|
---|
| 174 | // newly created/enhanced data routes
|
---|
| 175 | let matches = context.matches.map(match => {
|
---|
| 176 | let route = manifest[match.route.id] || match.route;
|
---|
| 177 | return {
|
---|
| 178 | ...match,
|
---|
| 179 | route
|
---|
| 180 | };
|
---|
| 181 | });
|
---|
| 182 | let msg = method => `You cannot use router.${method}() on the server because it is a stateless environment`;
|
---|
| 183 | return {
|
---|
| 184 | get basename() {
|
---|
| 185 | return context.basename;
|
---|
| 186 | },
|
---|
| 187 | get future() {
|
---|
| 188 | return {
|
---|
| 189 | v7_fetcherPersist: false,
|
---|
| 190 | v7_normalizeFormMethod: false,
|
---|
| 191 | v7_partialHydration: opts.future?.v7_partialHydration === true,
|
---|
| 192 | v7_prependBasename: false,
|
---|
| 193 | v7_relativeSplatPath: opts.future?.v7_relativeSplatPath === true,
|
---|
| 194 | v7_skipActionErrorRevalidation: false
|
---|
| 195 | };
|
---|
| 196 | },
|
---|
| 197 | get state() {
|
---|
| 198 | return {
|
---|
| 199 | historyAction: Action.Pop,
|
---|
| 200 | location: context.location,
|
---|
| 201 | matches,
|
---|
| 202 | loaderData: context.loaderData,
|
---|
| 203 | actionData: context.actionData,
|
---|
| 204 | errors: context.errors,
|
---|
| 205 | initialized: true,
|
---|
| 206 | navigation: IDLE_NAVIGATION,
|
---|
| 207 | restoreScrollPosition: null,
|
---|
| 208 | preventScrollReset: false,
|
---|
| 209 | revalidation: "idle",
|
---|
| 210 | fetchers: new Map(),
|
---|
| 211 | blockers: new Map()
|
---|
| 212 | };
|
---|
| 213 | },
|
---|
| 214 | get routes() {
|
---|
| 215 | return dataRoutes;
|
---|
| 216 | },
|
---|
| 217 | get window() {
|
---|
| 218 | return undefined;
|
---|
| 219 | },
|
---|
| 220 | initialize() {
|
---|
| 221 | throw msg("initialize");
|
---|
| 222 | },
|
---|
| 223 | subscribe() {
|
---|
| 224 | throw msg("subscribe");
|
---|
| 225 | },
|
---|
| 226 | enableScrollRestoration() {
|
---|
| 227 | throw msg("enableScrollRestoration");
|
---|
| 228 | },
|
---|
| 229 | navigate() {
|
---|
| 230 | throw msg("navigate");
|
---|
| 231 | },
|
---|
| 232 | fetch() {
|
---|
| 233 | throw msg("fetch");
|
---|
| 234 | },
|
---|
| 235 | revalidate() {
|
---|
| 236 | throw msg("revalidate");
|
---|
| 237 | },
|
---|
| 238 | createHref,
|
---|
| 239 | encodeLocation,
|
---|
| 240 | getFetcher() {
|
---|
| 241 | return IDLE_FETCHER;
|
---|
| 242 | },
|
---|
| 243 | deleteFetcher() {
|
---|
| 244 | throw msg("deleteFetcher");
|
---|
| 245 | },
|
---|
| 246 | dispose() {
|
---|
| 247 | throw msg("dispose");
|
---|
| 248 | },
|
---|
| 249 | getBlocker() {
|
---|
| 250 | return IDLE_BLOCKER;
|
---|
| 251 | },
|
---|
| 252 | deleteBlocker() {
|
---|
| 253 | throw msg("deleteBlocker");
|
---|
| 254 | },
|
---|
| 255 | patchRoutes() {
|
---|
| 256 | throw msg("patchRoutes");
|
---|
| 257 | },
|
---|
| 258 | _internalFetchControllers: new Map(),
|
---|
| 259 | _internalActiveDeferreds: new Map(),
|
---|
| 260 | _internalSetRoutes() {
|
---|
| 261 | throw msg("_internalSetRoutes");
|
---|
| 262 | }
|
---|
| 263 | };
|
---|
| 264 | }
|
---|
| 265 | function createHref(to) {
|
---|
| 266 | return typeof to === "string" ? to : createPath(to);
|
---|
| 267 | }
|
---|
| 268 | function encodeLocation(to) {
|
---|
| 269 | let href = typeof to === "string" ? to : createPath(to);
|
---|
| 270 | // Treating this as a full URL will strip any trailing spaces so we need to
|
---|
| 271 | // pre-encode them since they might be part of a matching splat param from
|
---|
| 272 | // an ancestor route
|
---|
| 273 | href = href.replace(/ $/, "%20");
|
---|
| 274 | let encoded = ABSOLUTE_URL_REGEX.test(href) ? new URL(href) : new URL(href, "http://localhost");
|
---|
| 275 | return {
|
---|
| 276 | pathname: encoded.pathname,
|
---|
| 277 | search: encoded.search,
|
---|
| 278 | hash: encoded.hash
|
---|
| 279 | };
|
---|
| 280 | }
|
---|
| 281 | const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
---|
| 282 |
|
---|
| 283 | // This utility is based on https://github.com/zertosh/htmlescape
|
---|
| 284 | // License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
|
---|
| 285 | const ESCAPE_LOOKUP = {
|
---|
| 286 | "&": "\\u0026",
|
---|
| 287 | ">": "\\u003e",
|
---|
| 288 | "<": "\\u003c",
|
---|
| 289 | "\u2028": "\\u2028",
|
---|
| 290 | "\u2029": "\\u2029"
|
---|
| 291 | };
|
---|
| 292 | const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
---|
| 293 | function htmlEscape(str) {
|
---|
| 294 | return str.replace(ESCAPE_REGEX, match => ESCAPE_LOOKUP[match]);
|
---|
| 295 | }
|
---|
| 296 |
|
---|
| 297 | export { StaticRouter, StaticRouterProvider, createStaticHandler, createStaticRouter };
|
---|