server.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import * as React from 'react';
  2. import { Action, invariant, isRouteErrorResponse, UNSAFE_convertRoutesToDataRoutes, IDLE_NAVIGATION, IDLE_FETCHER } from '@remix-run/router';
  3. import { parsePath, Router, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, Routes, UNSAFE_enhanceManualRouteObjects, createPath } from 'react-router-dom';
  4. /**
  5. * A <Router> that may not navigate to any other location. This is useful
  6. * on the server where there is no stateful UI.
  7. */
  8. function StaticRouter({
  9. basename,
  10. children,
  11. location: locationProp = "/"
  12. }) {
  13. if (typeof locationProp === "string") {
  14. locationProp = parsePath(locationProp);
  15. }
  16. let action = Action.Pop;
  17. let location = {
  18. pathname: locationProp.pathname || "/",
  19. search: locationProp.search || "",
  20. hash: locationProp.hash || "",
  21. state: locationProp.state || null,
  22. key: locationProp.key || "default"
  23. };
  24. let staticNavigator = getStatelessNavigator();
  25. return /*#__PURE__*/React.createElement(Router, {
  26. basename: basename,
  27. children: children,
  28. location: location,
  29. navigationType: action,
  30. navigator: staticNavigator,
  31. static: true
  32. });
  33. }
  34. /**
  35. * A Data Router that may not navigate to any other location. This is useful
  36. * on the server where there is no stateful UI.
  37. */
  38. function StaticRouterProvider({
  39. context,
  40. router,
  41. hydrate = true,
  42. nonce
  43. }) {
  44. !(router && context) ? process.env.NODE_ENV !== "production" ? invariant(false, "You must provide `router` and `context` to <StaticRouterProvider>") : invariant(false) : void 0;
  45. let dataRouterContext = {
  46. router,
  47. navigator: getStatelessNavigator(),
  48. static: true,
  49. staticContext: context,
  50. basename: context.basename || "/"
  51. };
  52. let hydrateScript = "";
  53. if (hydrate !== false) {
  54. let data = {
  55. loaderData: context.loaderData,
  56. actionData: context.actionData,
  57. errors: serializeErrors(context.errors)
  58. }; // Use JSON.parse here instead of embedding a raw JS object here to speed
  59. // up parsing on the client. Dual-stringify is needed to ensure all quotes
  60. // are properly escaped in the resulting string. See:
  61. // https://v8.dev/blog/cost-of-javascript-2019#json
  62. let json = JSON.stringify(JSON.stringify(data));
  63. hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
  64. }
  65. return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UNSAFE_DataRouterContext.Provider, {
  66. value: dataRouterContext
  67. }, /*#__PURE__*/React.createElement(UNSAFE_DataRouterStateContext.Provider, {
  68. value: dataRouterContext.router.state
  69. }, /*#__PURE__*/React.createElement(Router, {
  70. basename: dataRouterContext.basename,
  71. location: dataRouterContext.router.state.location,
  72. navigationType: dataRouterContext.router.state.historyAction,
  73. navigator: dataRouterContext.navigator
  74. }, /*#__PURE__*/React.createElement(Routes, null)))), hydrateScript ? /*#__PURE__*/React.createElement("script", {
  75. suppressHydrationWarning: true,
  76. nonce: nonce,
  77. dangerouslySetInnerHTML: {
  78. __html: hydrateScript
  79. }
  80. }) : null);
  81. }
  82. function serializeErrors(errors) {
  83. if (!errors) return null;
  84. let entries = Object.entries(errors);
  85. let serialized = {};
  86. for (let [key, val] of entries) {
  87. // Hey you! If you change this, please change the corresponding logic in
  88. // deserializeErrors in react-router-dom/index.tsx :)
  89. if (isRouteErrorResponse(val)) {
  90. serialized[key] = { ...val,
  91. __type: "RouteErrorResponse"
  92. };
  93. } else if (val instanceof Error) {
  94. // Do not serialize stack traces from SSR for security reasons
  95. serialized[key] = {
  96. message: val.message,
  97. __type: "Error"
  98. };
  99. } else {
  100. serialized[key] = val;
  101. }
  102. }
  103. return serialized;
  104. }
  105. function getStatelessNavigator() {
  106. return {
  107. createHref,
  108. encodeLocation,
  109. push(to) {
  110. 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.`);
  111. },
  112. replace(to) {
  113. 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.`);
  114. },
  115. go(delta) {
  116. 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.`);
  117. },
  118. back() {
  119. throw new Error(`You cannot use navigator.back() on the server because it is a stateless ` + `environment.`);
  120. },
  121. forward() {
  122. throw new Error(`You cannot use navigator.forward() on the server because it is a stateless ` + `environment.`);
  123. }
  124. };
  125. } // Temporary manifest generation - we should optimize this by combining the
  126. // tree-walks between convertRoutesToDataRoutes, enhanceManualRouteObjects,
  127. // and generateManifest.
  128. // Also look into getting rid of `route as AgnosticDataRouteObject` down below?
  129. function generateManifest(routes, manifest = new Map()) {
  130. routes.forEach(route => {
  131. manifest.set(route.id, route);
  132. if (route.children) {
  133. generateManifest(route.children, manifest);
  134. }
  135. });
  136. return manifest;
  137. }
  138. function createStaticRouter(routes, context) {
  139. let dataRoutes = UNSAFE_convertRoutesToDataRoutes(UNSAFE_enhanceManualRouteObjects(routes));
  140. let manifest = generateManifest(dataRoutes); // Because our context matches may be from a framework-agnostic set of
  141. // routes passed to createStaticHandler(), we update them here with our
  142. // newly created/enhanced data routes
  143. let matches = context.matches.map(match => {
  144. let route = manifest.get(match.route.id) || match.route;
  145. return { ...match,
  146. route: route
  147. };
  148. });
  149. let msg = method => `You cannot use router.${method}() on the server because it is a stateless environment`;
  150. return {
  151. get basename() {
  152. return context.basename;
  153. },
  154. get state() {
  155. return {
  156. historyAction: Action.Pop,
  157. location: context.location,
  158. matches,
  159. loaderData: context.loaderData,
  160. actionData: context.actionData,
  161. errors: context.errors,
  162. initialized: true,
  163. navigation: IDLE_NAVIGATION,
  164. restoreScrollPosition: null,
  165. preventScrollReset: false,
  166. revalidation: "idle",
  167. fetchers: new Map(),
  168. blockers: new Map()
  169. };
  170. },
  171. get routes() {
  172. return dataRoutes;
  173. },
  174. initialize() {
  175. throw msg("initialize");
  176. },
  177. subscribe() {
  178. throw msg("subscribe");
  179. },
  180. enableScrollRestoration() {
  181. throw msg("enableScrollRestoration");
  182. },
  183. navigate() {
  184. throw msg("navigate");
  185. },
  186. fetch() {
  187. throw msg("fetch");
  188. },
  189. revalidate() {
  190. throw msg("revalidate");
  191. },
  192. createHref,
  193. encodeLocation,
  194. getFetcher() {
  195. return IDLE_FETCHER;
  196. },
  197. deleteFetcher() {
  198. throw msg("deleteFetcher");
  199. },
  200. dispose() {
  201. throw msg("dispose");
  202. },
  203. getBlocker() {
  204. throw msg("getBlocker");
  205. },
  206. deleteBlocker() {
  207. throw msg("deleteBlocker");
  208. },
  209. _internalFetchControllers: new Map(),
  210. _internalActiveDeferreds: new Map()
  211. };
  212. }
  213. function createHref(to) {
  214. return typeof to === "string" ? to : createPath(to);
  215. }
  216. function encodeLocation(to) {
  217. // Locations should already be encoded on the server, so just return as-is
  218. let path = typeof to === "string" ? parsePath(to) : to;
  219. return {
  220. pathname: path.pathname || "",
  221. search: path.search || "",
  222. hash: path.hash || ""
  223. };
  224. }
  225. export { StaticRouter, StaticRouterProvider, createStaticRouter };