@alepha/react 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.browser.js +202 -84
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +228 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -165
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +261 -164
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +226 -93
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/components/ErrorViewer.tsx +1 -1
- package/src/components/Link.tsx +4 -24
- package/src/components/NestedView.tsx +11 -2
- package/src/components/NotFound.tsx +4 -1
- package/src/contexts/AlephaContext.ts +4 -0
- package/src/contexts/RouterContext.ts +0 -2
- package/src/descriptors/$page.ts +71 -9
- package/src/errors/{RedirectionError.ts → Redirection.ts} +1 -1
- package/src/hooks/RouterHookApi.ts +35 -11
- package/src/hooks/useActive.ts +22 -15
- package/src/hooks/useAlepha.ts +5 -5
- package/src/hooks/useClient.ts +2 -0
- package/src/hooks/useInject.ts +5 -8
- package/src/hooks/useQueryParams.ts +6 -9
- package/src/hooks/useRouter.ts +6 -5
- package/src/hooks/useRouterEvents.ts +12 -12
- package/src/hooks/useRouterState.ts +6 -4
- package/src/hooks/useSchema.ts +93 -0
- package/src/hooks/useStore.ts +47 -0
- package/src/index.shared.ts +5 -2
- package/src/providers/BrowserRouterProvider.ts +9 -0
- package/src/providers/PageDescriptorProvider.ts +123 -39
- package/src/providers/ReactBrowserProvider.ts +17 -11
- package/src/providers/ReactServerProvider.ts +47 -10
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { State } from "@alepha/core";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useAlepha } from "./useAlepha.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to access and mutate the Alepha state.
|
|
7
|
+
*/
|
|
8
|
+
export const useStore = <Key extends keyof State>(
|
|
9
|
+
key: Key,
|
|
10
|
+
defaultValue?: State[Key],
|
|
11
|
+
): [State[Key], (value: State[Key]) => void] => {
|
|
12
|
+
const alepha = useAlepha();
|
|
13
|
+
|
|
14
|
+
useMemo(() => {
|
|
15
|
+
if (defaultValue != null && alepha.state(key) == null) {
|
|
16
|
+
alepha.state(key, defaultValue);
|
|
17
|
+
}
|
|
18
|
+
}, [defaultValue]);
|
|
19
|
+
|
|
20
|
+
const [state, setState] = useState(alepha.state(key));
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!alepha.isBrowser()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return alepha.on("state:mutate", (ev) => {
|
|
28
|
+
if (ev.key === key) {
|
|
29
|
+
setState(ev.value);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
if (!alepha.isBrowser()) {
|
|
35
|
+
const value = alepha.context.get(key) as State[Key];
|
|
36
|
+
if (value !== null) {
|
|
37
|
+
return [value, (_: State[Key]) => {}] as const;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
state,
|
|
43
|
+
(value: State[Key]) => {
|
|
44
|
+
alepha.state(key, value);
|
|
45
|
+
},
|
|
46
|
+
] as const;
|
|
47
|
+
};
|
package/src/index.shared.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { default as ClientOnly } from "./components/ClientOnly.tsx";
|
|
2
2
|
export { default as ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
3
3
|
export * from "./components/ErrorViewer.tsx";
|
|
4
|
-
export { default as Link } from "./components/Link.tsx";
|
|
4
|
+
export { default as Link, type LinkProps } from "./components/Link.tsx";
|
|
5
5
|
export { default as NestedView } from "./components/NestedView.tsx";
|
|
6
6
|
export { default as NotFound } from "./components/NotFound.tsx";
|
|
7
|
+
export * from "./contexts/AlephaContext.ts";
|
|
7
8
|
export * from "./contexts/RouterContext.ts";
|
|
8
9
|
export * from "./contexts/RouterLayerContext.ts";
|
|
9
10
|
export * from "./descriptors/$page.ts";
|
|
10
|
-
export * from "./errors/
|
|
11
|
+
export * from "./errors/Redirection.ts";
|
|
11
12
|
export * from "./hooks/RouterHookApi.ts";
|
|
12
13
|
export * from "./hooks/useActive.ts";
|
|
13
14
|
export * from "./hooks/useAlepha.ts";
|
|
@@ -17,3 +18,5 @@ export * from "./hooks/useQueryParams.ts";
|
|
|
17
18
|
export * from "./hooks/useRouter.ts";
|
|
18
19
|
export * from "./hooks/useRouterEvents.ts";
|
|
19
20
|
export * from "./hooks/useRouterState.ts";
|
|
21
|
+
export * from "./hooks/useSchema.ts";
|
|
22
|
+
export * from "./hooks/useStore.ts";
|
|
@@ -129,6 +129,15 @@ export class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
|
129
129
|
options.state.search = state.search;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
if (options.previous) {
|
|
133
|
+
for (let i = 0; i < options.previous.length; i++) {
|
|
134
|
+
const layer = options.previous[i];
|
|
135
|
+
if (state.layers[i]?.name !== layer.name) {
|
|
136
|
+
this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
await this.alepha.emit("react:transition:end", {
|
|
133
142
|
state: options.state,
|
|
134
143
|
context,
|
|
@@ -13,14 +13,17 @@ import ClientOnly from "../components/ClientOnly.tsx";
|
|
|
13
13
|
import ErrorViewer from "../components/ErrorViewer.tsx";
|
|
14
14
|
import NestedView from "../components/NestedView.tsx";
|
|
15
15
|
import NotFoundPage from "../components/NotFound.tsx";
|
|
16
|
+
import { AlephaContext } from "../contexts/AlephaContext.ts";
|
|
16
17
|
import { RouterContext } from "../contexts/RouterContext.ts";
|
|
17
18
|
import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
|
|
18
19
|
import {
|
|
19
20
|
$page,
|
|
21
|
+
type ErrorHandler,
|
|
20
22
|
type PageDescriptor,
|
|
21
23
|
type PageDescriptorOptions,
|
|
22
24
|
} from "../descriptors/$page.ts";
|
|
23
|
-
import {
|
|
25
|
+
import { Redirection } from "../errors/Redirection.ts";
|
|
26
|
+
import type { HrefLike } from "../hooks/RouterHookApi.ts";
|
|
24
27
|
|
|
25
28
|
const envSchema = t.object({
|
|
26
29
|
REACT_STRICT_MODE: t.boolean({ default: true }),
|
|
@@ -50,10 +53,13 @@ export class PageDescriptorProvider {
|
|
|
50
53
|
throw new Error(`Page ${name} not found`);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
public
|
|
56
|
+
public pathname(
|
|
54
57
|
name: string,
|
|
55
|
-
options: {
|
|
56
|
-
|
|
58
|
+
options: {
|
|
59
|
+
params?: Record<string, string>;
|
|
60
|
+
query?: Record<string, string>;
|
|
61
|
+
} = {},
|
|
62
|
+
) {
|
|
57
63
|
const page = this.page(name);
|
|
58
64
|
if (!page) {
|
|
59
65
|
throw new Error(`Page ${name} not found`);
|
|
@@ -68,23 +74,41 @@ export class PageDescriptorProvider {
|
|
|
68
74
|
|
|
69
75
|
url = this.compile(url, options.params ?? {});
|
|
70
76
|
|
|
77
|
+
if (options.query) {
|
|
78
|
+
const query = new URLSearchParams(options.query);
|
|
79
|
+
if (query.toString()) {
|
|
80
|
+
url += `?${query.toString()}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return url.replace(/\/\/+/g, "/") || "/";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public url(
|
|
88
|
+
name: string,
|
|
89
|
+
options: { params?: Record<string, string>; base?: string } = {},
|
|
90
|
+
): URL {
|
|
71
91
|
return new URL(
|
|
72
|
-
|
|
92
|
+
this.pathname(name, options),
|
|
93
|
+
// use provided base or default to http://localhost
|
|
73
94
|
options.base ?? `http://localhost`,
|
|
74
95
|
);
|
|
75
96
|
}
|
|
76
97
|
|
|
77
98
|
public root(state: RouterState, context: PageReactContext): ReactNode {
|
|
78
99
|
const root = createElement(
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
AlephaContext.Provider,
|
|
101
|
+
{ value: this.alepha },
|
|
102
|
+
createElement(
|
|
103
|
+
RouterContext.Provider,
|
|
104
|
+
{
|
|
105
|
+
value: {
|
|
106
|
+
state,
|
|
107
|
+
context,
|
|
108
|
+
},
|
|
85
109
|
},
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
createElement(NestedView, {}, state.layers[0]?.element),
|
|
111
|
+
),
|
|
88
112
|
);
|
|
89
113
|
|
|
90
114
|
if (this.env.REACT_STRICT_MODE) {
|
|
@@ -196,13 +220,11 @@ export class PageDescriptorProvider {
|
|
|
196
220
|
};
|
|
197
221
|
} catch (e) {
|
|
198
222
|
// check if we need to redirect
|
|
199
|
-
if (e instanceof
|
|
200
|
-
return {
|
|
201
|
-
layers: [],
|
|
202
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
223
|
+
if (e instanceof Redirection) {
|
|
224
|
+
return this.createRedirectionLayer(e.page, {
|
|
203
225
|
pathname,
|
|
204
226
|
search,
|
|
205
|
-
};
|
|
227
|
+
});
|
|
206
228
|
}
|
|
207
229
|
|
|
208
230
|
this.log.error(e);
|
|
@@ -227,28 +249,56 @@ export class PageDescriptorProvider {
|
|
|
227
249
|
const path = acc.replace(/\/+/, "/");
|
|
228
250
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
229
251
|
if (localErrorHandler) {
|
|
230
|
-
request.onError
|
|
252
|
+
const onErrorParent = request.onError;
|
|
253
|
+
request.onError = (error, context) => {
|
|
254
|
+
const result = localErrorHandler(error, context);
|
|
255
|
+
// if nothing happen, call the parent
|
|
256
|
+
if (result === undefined) {
|
|
257
|
+
return onErrorParent(error, context);
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
};
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
// handler has thrown an error, render an error view
|
|
234
264
|
if (it.error) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
265
|
+
try {
|
|
266
|
+
let element: ReactNode | Redirection | undefined =
|
|
267
|
+
await request.onError(it.error, request);
|
|
239
268
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
269
|
+
if (element === undefined) {
|
|
270
|
+
throw it.error;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (element instanceof Redirection) {
|
|
274
|
+
return this.createRedirectionLayer(element.page, {
|
|
275
|
+
pathname,
|
|
276
|
+
search,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (element === null) {
|
|
281
|
+
element = this.renderError(it.error);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
layers.push({
|
|
285
|
+
props,
|
|
286
|
+
error: it.error,
|
|
287
|
+
name: it.route.name,
|
|
288
|
+
part: it.route.path,
|
|
289
|
+
config: it.config,
|
|
290
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
291
|
+
index: i + 1,
|
|
292
|
+
path,
|
|
293
|
+
route: it.route,
|
|
294
|
+
});
|
|
295
|
+
break;
|
|
296
|
+
} catch (e) {
|
|
297
|
+
if (e instanceof Redirection) {
|
|
298
|
+
return this.createRedirectionLayer(e.page, { pathname, search });
|
|
299
|
+
}
|
|
300
|
+
throw e;
|
|
301
|
+
}
|
|
252
302
|
}
|
|
253
303
|
|
|
254
304
|
// normal use case
|
|
@@ -274,7 +324,22 @@ export class PageDescriptorProvider {
|
|
|
274
324
|
return { layers, pathname, search };
|
|
275
325
|
}
|
|
276
326
|
|
|
277
|
-
protected
|
|
327
|
+
protected createRedirectionLayer(
|
|
328
|
+
href: HrefLike,
|
|
329
|
+
context: {
|
|
330
|
+
pathname: string;
|
|
331
|
+
search: string;
|
|
332
|
+
},
|
|
333
|
+
) {
|
|
334
|
+
return {
|
|
335
|
+
layers: [],
|
|
336
|
+
redirect: typeof href === "string" ? href : this.href(href),
|
|
337
|
+
pathname: context.pathname,
|
|
338
|
+
search: context.search,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
protected getErrorHandler(route: PageRoute): ErrorHandler | undefined {
|
|
278
343
|
if (route.errorHandler) return route.errorHandler;
|
|
279
344
|
let parent = route.parent;
|
|
280
345
|
while (parent) {
|
|
@@ -370,6 +435,10 @@ export class PageDescriptorProvider {
|
|
|
370
435
|
const pages = this.alepha.descriptors($page);
|
|
371
436
|
|
|
372
437
|
const hasParent = (it: PageDescriptor) => {
|
|
438
|
+
if (it.options.parent) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
373
442
|
for (const page of pages) {
|
|
374
443
|
const children = page.options.children
|
|
375
444
|
? Array.isArray(page.options.children)
|
|
@@ -402,7 +471,7 @@ export class PageDescriptorProvider {
|
|
|
402
471
|
name: "notFound",
|
|
403
472
|
cache: true,
|
|
404
473
|
component: NotFoundPage,
|
|
405
|
-
|
|
474
|
+
onServerResponse: ({ reply }) => {
|
|
406
475
|
reply.status = 404;
|
|
407
476
|
},
|
|
408
477
|
});
|
|
@@ -420,6 +489,18 @@ export class PageDescriptorProvider {
|
|
|
420
489
|
: target.options.children()
|
|
421
490
|
: [];
|
|
422
491
|
|
|
492
|
+
const getChildrenFromParent = (it: PageDescriptor): PageDescriptor[] => {
|
|
493
|
+
const children = [];
|
|
494
|
+
for (const page of pages) {
|
|
495
|
+
if (page.options.parent === it) {
|
|
496
|
+
children.push(page);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return children;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
children.push(...getChildrenFromParent(target));
|
|
503
|
+
|
|
423
504
|
return {
|
|
424
505
|
...target.options,
|
|
425
506
|
name: target.name,
|
|
@@ -517,7 +598,7 @@ export type PreviousLayerData = Omit<Layer, "element" | "index" | "path">;
|
|
|
517
598
|
|
|
518
599
|
export interface AnchorProps {
|
|
519
600
|
href: string;
|
|
520
|
-
onClick: (ev
|
|
601
|
+
onClick: (ev?: any) => any;
|
|
521
602
|
}
|
|
522
603
|
|
|
523
604
|
export interface RouterState {
|
|
@@ -564,6 +645,9 @@ export interface CreateLayersResult extends RouterState {
|
|
|
564
645
|
*/
|
|
565
646
|
export interface PageReactContext {
|
|
566
647
|
url: URL;
|
|
567
|
-
onError:
|
|
648
|
+
onError: ErrorHandler;
|
|
568
649
|
links?: ApiLinksResponse;
|
|
650
|
+
|
|
651
|
+
params: Record<string, any>;
|
|
652
|
+
query: Record<string, string>;
|
|
569
653
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $hook, $inject, $logger, Alepha } from "@alepha/core";
|
|
1
|
+
import { $hook, $inject, $logger, Alepha, type State } from "@alepha/core";
|
|
2
2
|
import type { ApiLinksResponse } from "@alepha/server";
|
|
3
3
|
import { LinkProvider } from "@alepha/server-links";
|
|
4
4
|
import type { Root } from "react-dom/client";
|
|
@@ -97,18 +97,12 @@ export class ReactBrowserProvider {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
// when redirecting in browser
|
|
100
|
-
if (result.context.url.pathname !== url) {
|
|
101
|
-
|
|
102
|
-
this.pushState(result.context.url.pathname);
|
|
100
|
+
if (result.context.url.pathname + result.context.url.search !== url) {
|
|
101
|
+
this.pushState(result.context.url.pathname + result.context.url.search);
|
|
103
102
|
return;
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
this.pushState(url);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.pushState(url);
|
|
105
|
+
this.pushState(url, options.replace);
|
|
112
106
|
}
|
|
113
107
|
|
|
114
108
|
protected async render(
|
|
@@ -157,9 +151,20 @@ export class ReactBrowserProvider {
|
|
|
157
151
|
const hydration = this.getHydrationState();
|
|
158
152
|
const previous = hydration?.layers ?? [];
|
|
159
153
|
|
|
154
|
+
if (hydration) {
|
|
155
|
+
for (const [key, value] of Object.entries(hydration)) {
|
|
156
|
+
if (key !== "layers" && key !== "links") {
|
|
157
|
+
this.alepha.state(key as keyof State, value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
160
162
|
if (hydration?.links) {
|
|
161
163
|
for (const link of hydration.links.links) {
|
|
162
|
-
this.client.pushLink(
|
|
164
|
+
this.client.pushLink({
|
|
165
|
+
...link,
|
|
166
|
+
prefix: hydration.links.prefix,
|
|
167
|
+
});
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
|
|
@@ -190,6 +195,7 @@ export interface RouterGoOptions {
|
|
|
190
195
|
replace?: boolean;
|
|
191
196
|
match?: TransitionOptions;
|
|
192
197
|
params?: Record<string, string>;
|
|
198
|
+
query?: Record<string, string>;
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
export interface ReactHydrationState {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
$page,
|
|
23
23
|
type PageDescriptorRenderOptions,
|
|
24
24
|
} from "../descriptors/$page.ts";
|
|
25
|
+
import { Redirection } from "../errors/Redirection.ts";
|
|
25
26
|
import {
|
|
26
27
|
PageDescriptorProvider,
|
|
27
28
|
type PageReactContext,
|
|
@@ -231,6 +232,10 @@ export class ReactServerProvider {
|
|
|
231
232
|
options.hydration,
|
|
232
233
|
);
|
|
233
234
|
|
|
235
|
+
if (html instanceof Redirection) {
|
|
236
|
+
throw new Error("Redirection is not supported in this context");
|
|
237
|
+
}
|
|
238
|
+
|
|
234
239
|
const result = {
|
|
235
240
|
context,
|
|
236
241
|
state,
|
|
@@ -254,6 +259,10 @@ export class ReactServerProvider {
|
|
|
254
259
|
throw new Error("Template not found");
|
|
255
260
|
}
|
|
256
261
|
|
|
262
|
+
this.log.trace("Rendering page", {
|
|
263
|
+
name: page.name,
|
|
264
|
+
});
|
|
265
|
+
|
|
257
266
|
const context: PageRequest = {
|
|
258
267
|
url,
|
|
259
268
|
params,
|
|
@@ -300,6 +309,11 @@ export class ReactServerProvider {
|
|
|
300
309
|
// return;
|
|
301
310
|
// }
|
|
302
311
|
|
|
312
|
+
await this.alepha.emit("react:transition:begin", {
|
|
313
|
+
request: serverRequest,
|
|
314
|
+
context,
|
|
315
|
+
});
|
|
316
|
+
|
|
303
317
|
await this.alepha.emit("react:server:render:begin", {
|
|
304
318
|
request: serverRequest,
|
|
305
319
|
context,
|
|
@@ -333,17 +347,31 @@ export class ReactServerProvider {
|
|
|
333
347
|
}
|
|
334
348
|
|
|
335
349
|
const html = this.renderToHtml(template, state, context);
|
|
350
|
+
if (html instanceof Redirection) {
|
|
351
|
+
reply.redirect(
|
|
352
|
+
typeof html.page === "string"
|
|
353
|
+
? html.page
|
|
354
|
+
: this.pageDescriptorProvider.href(html.page),
|
|
355
|
+
);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
336
358
|
|
|
337
|
-
|
|
359
|
+
const event = {
|
|
338
360
|
request: serverRequest,
|
|
339
361
|
context,
|
|
340
362
|
state,
|
|
341
363
|
html,
|
|
342
|
-
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
await this.alepha.emit("react:server:render:end", event);
|
|
343
367
|
|
|
344
|
-
page.
|
|
368
|
+
page.onServerResponse?.(serverRequest);
|
|
369
|
+
|
|
370
|
+
this.log.trace("Page rendered", {
|
|
371
|
+
name: page.name,
|
|
372
|
+
});
|
|
345
373
|
|
|
346
|
-
return html;
|
|
374
|
+
return event.html;
|
|
347
375
|
};
|
|
348
376
|
}
|
|
349
377
|
|
|
@@ -352,7 +380,7 @@ export class ReactServerProvider {
|
|
|
352
380
|
state: RouterState,
|
|
353
381
|
context: PageReactContext,
|
|
354
382
|
hydration = true,
|
|
355
|
-
) {
|
|
383
|
+
): string | Redirection {
|
|
356
384
|
const element = this.pageDescriptorProvider.root(state, context);
|
|
357
385
|
|
|
358
386
|
this.serverTimingProvider.beginTiming("renderToString");
|
|
@@ -362,7 +390,13 @@ export class ReactServerProvider {
|
|
|
362
390
|
app = renderToString(element);
|
|
363
391
|
} catch (error) {
|
|
364
392
|
this.log.error("Error during SSR", error);
|
|
365
|
-
|
|
393
|
+
const element = context.onError(error as Error, context);
|
|
394
|
+
if (element instanceof Redirection) {
|
|
395
|
+
// if the error is a redirection, return the redirection URL
|
|
396
|
+
return element;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
app = renderToString(element);
|
|
366
400
|
}
|
|
367
401
|
|
|
368
402
|
this.serverTimingProvider.endTiming("renderToString");
|
|
@@ -372,8 +406,11 @@ export class ReactServerProvider {
|
|
|
372
406
|
};
|
|
373
407
|
|
|
374
408
|
if (hydration) {
|
|
409
|
+
const { request, context, ...rest } =
|
|
410
|
+
this.alepha.context.als?.getStore() ?? {};
|
|
411
|
+
|
|
375
412
|
const hydrationData: ReactHydrationState = {
|
|
376
|
-
|
|
413
|
+
...rest,
|
|
377
414
|
layers: state.layers.map((it) => ({
|
|
378
415
|
...it,
|
|
379
416
|
error: it.error
|
|
@@ -381,7 +418,7 @@ export class ReactServerProvider {
|
|
|
381
418
|
...it.error,
|
|
382
419
|
name: it.error.name,
|
|
383
420
|
message: it.error.message,
|
|
384
|
-
stack: it.error.stack
|
|
421
|
+
stack: !this.alepha.isProduction() ? it.error.stack : undefined,
|
|
385
422
|
}
|
|
386
423
|
: undefined,
|
|
387
424
|
index: undefined,
|
|
@@ -418,7 +455,7 @@ export class ReactServerProvider {
|
|
|
418
455
|
const bodyOpenTag = /<body([^>]*)>/i;
|
|
419
456
|
if (bodyOpenTag.test(response.html)) {
|
|
420
457
|
response.html = response.html.replace(bodyOpenTag, (match) => {
|
|
421
|
-
return `${match}
|
|
458
|
+
return `${match}<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
|
|
422
459
|
});
|
|
423
460
|
}
|
|
424
461
|
}
|
|
@@ -427,7 +464,7 @@ export class ReactServerProvider {
|
|
|
427
464
|
if (bodyCloseTagRegex.test(response.html)) {
|
|
428
465
|
response.html = response.html.replace(
|
|
429
466
|
bodyCloseTagRegex,
|
|
430
|
-
`${script}
|
|
467
|
+
`${script}</body>`,
|
|
431
468
|
);
|
|
432
469
|
}
|
|
433
470
|
}
|