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 };
|
---|