@alepha/react 0.12.0 → 0.13.0
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/dist/auth/chunk-DhGyd7sr.js +28 -0
- package/dist/auth/index.browser.js +394 -114
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.cjs +80 -1927
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +1130 -420
- package/dist/auth/index.d.ts +1130 -420
- package/dist/auth/index.js +72 -1918
- package/dist/auth/index.js.map +1 -1
- package/dist/core/chunk-DhGyd7sr.js +28 -0
- package/dist/core/index.browser.js +79 -79
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.cjs +89 -85
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +1654 -154
- package/dist/core/index.d.ts +1654 -154
- package/dist/core/index.js +79 -79
- package/dist/core/index.js.map +1 -1
- package/dist/form/chunk-DhGyd7sr.js +28 -0
- package/dist/form/index.cjs +28 -8
- package/dist/form/index.cjs.map +1 -1
- package/dist/form/index.d.cts +215 -7
- package/dist/form/index.d.ts +215 -7
- package/dist/form/index.js +18 -3
- package/dist/form/index.js.map +1 -1
- package/dist/head/chunk-DhGyd7sr.js +28 -0
- package/dist/head/index.browser.js +385 -59
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.cjs +12 -8
- package/dist/head/index.cjs.map +1 -1
- package/dist/head/index.d.cts +1230 -24
- package/dist/head/index.d.ts +1230 -29
- package/dist/head/index.js +2 -2
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/chunk-DhGyd7sr.js +28 -0
- package/dist/i18n/index.cjs +33 -20
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +282 -13
- package/dist/i18n/index.d.ts +282 -13
- package/dist/i18n/index.js +23 -14
- package/dist/i18n/index.js.map +1 -1
- package/dist/websocket/index.cjs +21 -8
- package/dist/websocket/index.cjs.map +1 -1
- package/dist/websocket/index.js +11 -2
- package/dist/websocket/index.js.map +1 -1
- package/package.json +7 -6
- package/src/auth/index.browser.ts +3 -6
- package/src/auth/index.shared.ts +0 -1
- package/src/auth/index.ts +3 -16
- package/src/auth/providers/ReactAuthProvider.ts +1 -614
- package/src/auth/services/ReactAuth.ts +6 -17
- package/src/core/descriptors/$page.ts +1 -1
- package/src/core/index.browser.ts +1 -0
- package/src/core/index.native.ts +21 -0
- package/src/core/index.shared-router.ts +15 -0
- package/src/core/index.shared.ts +0 -14
- package/src/core/index.ts +1 -0
- package/src/core/services/ReactRouter.ts +2 -2
- package/src/form/errors/FormValidationError.ts +20 -0
- package/src/form/hooks/useForm.ts +1 -1
- package/src/form/index.ts +1 -0
- package/src/head/providers/BrowserHeadProvider.ts +1 -1
- package/src/i18n/descriptors/$dictionary.ts +7 -3
- package/src/i18n/providers/I18nProvider.ts +9 -10
- package/src/websocket/hooks/useRoom.tsx +21 -2
- package/dist/auth/index.d.cts.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/core/index.d.cts.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/form/index.d.cts.map +0 -1
- package/dist/form/index.d.ts.map +0 -1
- package/dist/head/index.d.cts.map +0 -1
- package/dist/head/index.d.ts.map +0 -1
- package/dist/i18n/index.d.cts.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/websocket/index.d.cts.map +0 -1
- package/dist/websocket/index.d.ts.map +0 -1
- package/src/auth/descriptors/$auth.ts +0 -436
- package/src/auth/descriptors/$authApple.ts +0 -8
- package/src/auth/descriptors/$authGithub.ts +0 -81
- package/src/auth/descriptors/$authGoogle.ts +0 -38
- package/src/auth/errors/SessionExpiredError.ts +0 -6
- package/src/auth/schemas/tokenResponseSchema.ts +0 -11
- package/src/auth/schemas/tokensSchema.ts +0 -21
- package/src/auth/schemas/userinfoResponseSchema.ts +0 -10
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { };
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Atom, Descriptor, KIND, createDescriptor, t } from "alepha";
|
|
2
2
|
import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
|
|
3
|
-
import { AlephaServer } from "alepha/server";
|
|
4
|
-
import {
|
|
3
|
+
import { AlephaServer, ServerProvider, ServerRouterProvider, ServerTimingProvider } from "alepha/server";
|
|
4
|
+
import { AlephaServerCache } from "alepha/server/cache";
|
|
5
|
+
import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "alepha/server/links";
|
|
5
6
|
import { $logger } from "alepha/logger";
|
|
6
|
-
import { RouterProvider } from "alepha/router";
|
|
7
7
|
import React, { StrictMode, createContext, createElement, memo, use, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ServerStaticProvider } from "alepha/server/static";
|
|
12
|
+
import { renderToString } from "react-dom/server";
|
|
13
|
+
import { RouterProvider } from "alepha/router";
|
|
10
14
|
|
|
11
15
|
//#region src/core/services/ReactPageService.ts
|
|
12
16
|
var ReactPageService = class {
|
|
@@ -66,7 +70,7 @@ var ReactPageService = class {
|
|
|
66
70
|
* const userProfile = $page({
|
|
67
71
|
* path: "/users/:id",
|
|
68
72
|
* schema: {
|
|
69
|
-
* params: t.object({ id: t.
|
|
73
|
+
* params: t.object({ id: t.integer() }),
|
|
70
74
|
* query: t.object({ tab: t.optional(t.text()) })
|
|
71
75
|
* },
|
|
72
76
|
* resolve: async ({ params }) => {
|
|
@@ -143,31 +147,6 @@ var PageDescriptor = class extends Descriptor {
|
|
|
143
147
|
};
|
|
144
148
|
$page[KIND] = PageDescriptor;
|
|
145
149
|
|
|
146
|
-
//#endregion
|
|
147
|
-
//#region src/core/components/NotFound.tsx
|
|
148
|
-
function NotFoundPage(props) {
|
|
149
|
-
return /* @__PURE__ */ jsx("div", {
|
|
150
|
-
style: {
|
|
151
|
-
height: "100vh",
|
|
152
|
-
display: "flex",
|
|
153
|
-
flexDirection: "column",
|
|
154
|
-
justifyContent: "center",
|
|
155
|
-
alignItems: "center",
|
|
156
|
-
textAlign: "center",
|
|
157
|
-
fontFamily: "sans-serif",
|
|
158
|
-
padding: "1rem",
|
|
159
|
-
...props.style
|
|
160
|
-
},
|
|
161
|
-
children: /* @__PURE__ */ jsx("h1", {
|
|
162
|
-
style: {
|
|
163
|
-
fontSize: "1rem",
|
|
164
|
-
marginBottom: "0.5rem"
|
|
165
|
-
},
|
|
166
|
-
children: "404 - This page does not exist"
|
|
167
|
-
})
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
150
|
//#endregion
|
|
172
151
|
//#region src/core/components/ClientOnly.tsx
|
|
173
152
|
/**
|
|
@@ -597,12 +576,37 @@ function parseAnimation(animationLike, state, type = "enter") {
|
|
|
597
576
|
}
|
|
598
577
|
}
|
|
599
578
|
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/core/components/NotFound.tsx
|
|
581
|
+
function NotFoundPage(props) {
|
|
582
|
+
return /* @__PURE__ */ jsx("div", {
|
|
583
|
+
style: {
|
|
584
|
+
height: "100vh",
|
|
585
|
+
display: "flex",
|
|
586
|
+
flexDirection: "column",
|
|
587
|
+
justifyContent: "center",
|
|
588
|
+
alignItems: "center",
|
|
589
|
+
textAlign: "center",
|
|
590
|
+
fontFamily: "sans-serif",
|
|
591
|
+
padding: "1rem",
|
|
592
|
+
...props.style
|
|
593
|
+
},
|
|
594
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
595
|
+
style: {
|
|
596
|
+
fontSize: "1rem",
|
|
597
|
+
marginBottom: "0.5rem"
|
|
598
|
+
},
|
|
599
|
+
children: "404 - This page does not exist"
|
|
600
|
+
})
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
600
604
|
//#endregion
|
|
601
605
|
//#region src/core/providers/ReactPageProvider.ts
|
|
602
|
-
const envSchema$
|
|
606
|
+
const envSchema$2 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
603
607
|
var ReactPageProvider = class {
|
|
604
608
|
log = $logger();
|
|
605
|
-
env = $env(envSchema$
|
|
609
|
+
env = $env(envSchema$2);
|
|
606
610
|
alepha = $inject(Alepha);
|
|
607
611
|
pages = [];
|
|
608
612
|
getPages() {
|
|
@@ -920,6 +924,336 @@ const isPageRoute = (it) => {
|
|
|
920
924
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
921
925
|
};
|
|
922
926
|
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region src/core/providers/ReactServerProvider.ts
|
|
929
|
+
const envSchema$1 = t.object({
|
|
930
|
+
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
931
|
+
REACT_ROOT_ID: t.text({ default: "root" }),
|
|
932
|
+
REACT_SERVER_TEMPLATE: t.optional(t.text({ size: "rich" }))
|
|
933
|
+
});
|
|
934
|
+
/**
|
|
935
|
+
* React server provider configuration atom
|
|
936
|
+
*/
|
|
937
|
+
const reactServerOptions = $atom({
|
|
938
|
+
name: "alepha.react.server.options",
|
|
939
|
+
schema: t.object({
|
|
940
|
+
publicDir: t.string(),
|
|
941
|
+
staticServer: t.object({
|
|
942
|
+
disabled: t.boolean(),
|
|
943
|
+
path: t.string({ description: "URL path where static files will be served." })
|
|
944
|
+
})
|
|
945
|
+
}),
|
|
946
|
+
default: {
|
|
947
|
+
publicDir: "public",
|
|
948
|
+
staticServer: {
|
|
949
|
+
disabled: false,
|
|
950
|
+
path: "/"
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
var ReactServerProvider = class {
|
|
955
|
+
log = $logger();
|
|
956
|
+
alepha = $inject(Alepha);
|
|
957
|
+
env = $env(envSchema$1);
|
|
958
|
+
pageApi = $inject(ReactPageProvider);
|
|
959
|
+
serverProvider = $inject(ServerProvider);
|
|
960
|
+
serverStaticProvider = $inject(ServerStaticProvider);
|
|
961
|
+
serverRouterProvider = $inject(ServerRouterProvider);
|
|
962
|
+
serverTimingProvider = $inject(ServerTimingProvider);
|
|
963
|
+
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
964
|
+
preprocessedTemplate = null;
|
|
965
|
+
options = $use(reactServerOptions);
|
|
966
|
+
/**
|
|
967
|
+
* Configure the React server provider.
|
|
968
|
+
*/
|
|
969
|
+
onConfigure = $hook({
|
|
970
|
+
on: "configure",
|
|
971
|
+
handler: async () => {
|
|
972
|
+
const ssrEnabled = this.alepha.descriptors($page).length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
973
|
+
this.alepha.state.set("alepha.react.server.ssr", ssrEnabled);
|
|
974
|
+
if (this.alepha.isViteDev()) {
|
|
975
|
+
await this.configureVite(ssrEnabled);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
let root = "";
|
|
979
|
+
if (!this.alepha.isServerless()) {
|
|
980
|
+
root = this.getPublicDirectory();
|
|
981
|
+
if (!root) this.log.warn("Missing static files, static file server will be disabled");
|
|
982
|
+
else {
|
|
983
|
+
this.log.debug(`Using static files from: ${root}`);
|
|
984
|
+
await this.configureStaticServer(root);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (ssrEnabled) {
|
|
988
|
+
await this.registerPages(async () => this.template);
|
|
989
|
+
this.log.info("SSR OK");
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
this.log.info("SSR is disabled, use History API fallback");
|
|
993
|
+
this.serverRouterProvider.createRoute({
|
|
994
|
+
path: "*",
|
|
995
|
+
handler: async ({ url, reply }) => {
|
|
996
|
+
if (url.pathname.includes(".")) {
|
|
997
|
+
reply.headers["content-type"] = "text/plain";
|
|
998
|
+
reply.body = "Not Found";
|
|
999
|
+
reply.status = 404;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
reply.headers["content-type"] = "text/html";
|
|
1003
|
+
return this.template;
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
get template() {
|
|
1009
|
+
return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
1010
|
+
}
|
|
1011
|
+
async registerPages(templateLoader) {
|
|
1012
|
+
const template = await templateLoader();
|
|
1013
|
+
if (template) this.preprocessedTemplate = this.preprocessTemplate(template);
|
|
1014
|
+
for (const page of this.pageApi.getPages()) {
|
|
1015
|
+
if (page.children?.length) continue;
|
|
1016
|
+
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
1017
|
+
this.serverRouterProvider.createRoute({
|
|
1018
|
+
...page,
|
|
1019
|
+
schema: void 0,
|
|
1020
|
+
method: "GET",
|
|
1021
|
+
path: page.match,
|
|
1022
|
+
handler: this.createHandler(page, templateLoader)
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Get the public directory path where static files are located.
|
|
1028
|
+
*/
|
|
1029
|
+
getPublicDirectory() {
|
|
1030
|
+
const maybe = [join(process.cwd(), `dist/${this.options.publicDir}`), join(process.cwd(), this.options.publicDir)];
|
|
1031
|
+
for (const it of maybe) if (existsSync(it)) return it;
|
|
1032
|
+
return "";
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Configure the static file server to serve files from the given root directory.
|
|
1036
|
+
*/
|
|
1037
|
+
async configureStaticServer(root) {
|
|
1038
|
+
await this.serverStaticProvider.createStaticServer({
|
|
1039
|
+
root,
|
|
1040
|
+
cacheControl: {
|
|
1041
|
+
maxAge: 3600,
|
|
1042
|
+
immutable: true
|
|
1043
|
+
},
|
|
1044
|
+
...this.options.staticServer
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Configure Vite for SSR.
|
|
1049
|
+
*/
|
|
1050
|
+
async configureVite(ssrEnabled) {
|
|
1051
|
+
if (!ssrEnabled) return;
|
|
1052
|
+
this.log.info("SSR (dev) OK");
|
|
1053
|
+
const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
|
|
1054
|
+
await this.registerPages(() => fetch(`${url}/index.html`).then((it) => it.text()).catch(() => void 0));
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* For testing purposes, creates a render function that can be used.
|
|
1058
|
+
*/
|
|
1059
|
+
async render(name, options = {}) {
|
|
1060
|
+
const page = this.pageApi.page(name);
|
|
1061
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
1062
|
+
const state = {
|
|
1063
|
+
url,
|
|
1064
|
+
params: options.params ?? {},
|
|
1065
|
+
query: options.query ?? {},
|
|
1066
|
+
onError: () => null,
|
|
1067
|
+
layers: [],
|
|
1068
|
+
meta: {}
|
|
1069
|
+
};
|
|
1070
|
+
this.log.trace("Rendering", { url });
|
|
1071
|
+
await this.alepha.events.emit("react:server:render:begin", { state });
|
|
1072
|
+
const { redirect } = await this.pageApi.createLayers(page, state);
|
|
1073
|
+
if (redirect) return {
|
|
1074
|
+
state,
|
|
1075
|
+
html: "",
|
|
1076
|
+
redirect
|
|
1077
|
+
};
|
|
1078
|
+
if (!options.html) {
|
|
1079
|
+
this.alepha.state.set("alepha.react.router.state", state);
|
|
1080
|
+
return {
|
|
1081
|
+
state,
|
|
1082
|
+
html: renderToString(this.pageApi.root(state))
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const template = this.template ?? "";
|
|
1086
|
+
const html = this.renderToHtml(template, state, options.hydration);
|
|
1087
|
+
if (html instanceof Redirection) return {
|
|
1088
|
+
state,
|
|
1089
|
+
html: "",
|
|
1090
|
+
redirect
|
|
1091
|
+
};
|
|
1092
|
+
const result = {
|
|
1093
|
+
state,
|
|
1094
|
+
html
|
|
1095
|
+
};
|
|
1096
|
+
await this.alepha.events.emit("react:server:render:end", result);
|
|
1097
|
+
return result;
|
|
1098
|
+
}
|
|
1099
|
+
createHandler(route, templateLoader) {
|
|
1100
|
+
return async (serverRequest) => {
|
|
1101
|
+
const { url, reply, query, params } = serverRequest;
|
|
1102
|
+
const template = await templateLoader();
|
|
1103
|
+
if (!template) throw new AlephaError("Missing template for SSR rendering");
|
|
1104
|
+
this.log.trace("Rendering page", { name: route.name });
|
|
1105
|
+
const state = {
|
|
1106
|
+
url,
|
|
1107
|
+
params,
|
|
1108
|
+
query,
|
|
1109
|
+
onError: () => null,
|
|
1110
|
+
layers: []
|
|
1111
|
+
};
|
|
1112
|
+
if (this.alepha.has(ServerLinksProvider)) this.alepha.state.set("alepha.server.request.apiLinks", await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
|
|
1113
|
+
user: serverRequest.user,
|
|
1114
|
+
authorization: serverRequest.headers.authorization
|
|
1115
|
+
}));
|
|
1116
|
+
let target = route;
|
|
1117
|
+
while (target) {
|
|
1118
|
+
if (route.can && !route.can()) {
|
|
1119
|
+
reply.status = 403;
|
|
1120
|
+
reply.headers["content-type"] = "text/plain";
|
|
1121
|
+
return "Forbidden";
|
|
1122
|
+
}
|
|
1123
|
+
target = target.parent;
|
|
1124
|
+
}
|
|
1125
|
+
await this.alepha.events.emit("react:server:render:begin", {
|
|
1126
|
+
request: serverRequest,
|
|
1127
|
+
state
|
|
1128
|
+
});
|
|
1129
|
+
this.serverTimingProvider.beginTiming("createLayers");
|
|
1130
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
1131
|
+
this.serverTimingProvider.endTiming("createLayers");
|
|
1132
|
+
if (redirect) return reply.redirect(redirect);
|
|
1133
|
+
reply.headers["content-type"] = "text/html";
|
|
1134
|
+
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
1135
|
+
reply.headers.pragma = "no-cache";
|
|
1136
|
+
reply.headers.expires = "0";
|
|
1137
|
+
const html = this.renderToHtml(template, state);
|
|
1138
|
+
if (html instanceof Redirection) {
|
|
1139
|
+
reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const event = {
|
|
1143
|
+
request: serverRequest,
|
|
1144
|
+
state,
|
|
1145
|
+
html
|
|
1146
|
+
};
|
|
1147
|
+
await this.alepha.events.emit("react:server:render:end", event);
|
|
1148
|
+
route.onServerResponse?.(serverRequest);
|
|
1149
|
+
this.log.trace("Page rendered", { name: route.name });
|
|
1150
|
+
return event.html;
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
renderToHtml(template, state, hydration = true) {
|
|
1154
|
+
const element = this.pageApi.root(state);
|
|
1155
|
+
this.alepha.state.set("alepha.react.router.state", state);
|
|
1156
|
+
this.serverTimingProvider.beginTiming("renderToString");
|
|
1157
|
+
let app = "";
|
|
1158
|
+
try {
|
|
1159
|
+
app = renderToString(element);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
this.log.error("renderToString has failed, fallback to error handler", error);
|
|
1162
|
+
const element$1 = state.onError(error, state);
|
|
1163
|
+
if (element$1 instanceof Redirection) return element$1;
|
|
1164
|
+
app = renderToString(element$1);
|
|
1165
|
+
this.log.debug("Error handled successfully with fallback");
|
|
1166
|
+
}
|
|
1167
|
+
this.serverTimingProvider.endTiming("renderToString");
|
|
1168
|
+
const response = { html: template };
|
|
1169
|
+
if (hydration) {
|
|
1170
|
+
const { request, context, ...store } = this.alepha.context.als?.getStore() ?? {};
|
|
1171
|
+
const hydrationData = {
|
|
1172
|
+
...store,
|
|
1173
|
+
"alepha.react.router.state": void 0,
|
|
1174
|
+
layers: state.layers.map((it) => ({
|
|
1175
|
+
...it,
|
|
1176
|
+
error: it.error ? {
|
|
1177
|
+
...it.error,
|
|
1178
|
+
name: it.error.name,
|
|
1179
|
+
message: it.error.message,
|
|
1180
|
+
stack: !this.alepha.isProduction() ? it.error.stack : void 0
|
|
1181
|
+
} : void 0,
|
|
1182
|
+
index: void 0,
|
|
1183
|
+
path: void 0,
|
|
1184
|
+
element: void 0,
|
|
1185
|
+
route: void 0
|
|
1186
|
+
}))
|
|
1187
|
+
};
|
|
1188
|
+
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
1189
|
+
this.fillTemplate(response, app, script);
|
|
1190
|
+
}
|
|
1191
|
+
return response.html;
|
|
1192
|
+
}
|
|
1193
|
+
preprocessTemplate(template) {
|
|
1194
|
+
const bodyCloseIndex = template.match(/<\/body>/i)?.index ?? template.length;
|
|
1195
|
+
const beforeScript = template.substring(0, bodyCloseIndex);
|
|
1196
|
+
const afterScript = template.substring(bodyCloseIndex);
|
|
1197
|
+
const rootDivMatch = beforeScript.match(this.ROOT_DIV_REGEX);
|
|
1198
|
+
if (rootDivMatch) {
|
|
1199
|
+
const beforeDiv = beforeScript.substring(0, rootDivMatch.index);
|
|
1200
|
+
const afterDivStart = rootDivMatch.index + rootDivMatch[0].length;
|
|
1201
|
+
const afterDiv = beforeScript.substring(afterDivStart);
|
|
1202
|
+
return {
|
|
1203
|
+
beforeApp: `${beforeDiv}<div${rootDivMatch[1]} id="${this.env.REACT_ROOT_ID}"${rootDivMatch[2]}>`,
|
|
1204
|
+
afterApp: `</div>${afterDiv}`,
|
|
1205
|
+
beforeScript: "",
|
|
1206
|
+
afterScript
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
const bodyMatch = beforeScript.match(/<body([^>]*)>/i);
|
|
1210
|
+
if (bodyMatch) {
|
|
1211
|
+
const beforeBody = beforeScript.substring(0, bodyMatch.index + bodyMatch[0].length);
|
|
1212
|
+
const afterBody = beforeScript.substring(bodyMatch.index + bodyMatch[0].length);
|
|
1213
|
+
return {
|
|
1214
|
+
beforeApp: `${beforeBody}<div id="${this.env.REACT_ROOT_ID}">`,
|
|
1215
|
+
afterApp: `</div>${afterBody}`,
|
|
1216
|
+
beforeScript: "",
|
|
1217
|
+
afterScript
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
beforeApp: `<div id="${this.env.REACT_ROOT_ID}">`,
|
|
1222
|
+
afterApp: `</div>`,
|
|
1223
|
+
beforeScript,
|
|
1224
|
+
afterScript
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
fillTemplate(response, app, script) {
|
|
1228
|
+
if (!this.preprocessedTemplate) this.preprocessedTemplate = this.preprocessTemplate(response.html);
|
|
1229
|
+
response.html = this.preprocessedTemplate.beforeApp + app + this.preprocessedTemplate.afterApp + script + this.preprocessedTemplate.afterScript;
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/core/services/ReactPageServerService.ts
|
|
1235
|
+
var ReactPageServerService = class extends ReactPageService {
|
|
1236
|
+
reactServerProvider = $inject(ReactServerProvider);
|
|
1237
|
+
serverProvider = $inject(ServerProvider);
|
|
1238
|
+
async render(name, options = {}) {
|
|
1239
|
+
return this.reactServerProvider.render(name, options);
|
|
1240
|
+
}
|
|
1241
|
+
async fetch(pathname, options = {}) {
|
|
1242
|
+
const response = await fetch(`${this.serverProvider.hostname}/${pathname}`);
|
|
1243
|
+
const html = await response.text();
|
|
1244
|
+
if (options?.html) return {
|
|
1245
|
+
html,
|
|
1246
|
+
response
|
|
1247
|
+
};
|
|
1248
|
+
const match = html.match(this.reactServerProvider.ROOT_DIV_REGEX);
|
|
1249
|
+
if (match) return {
|
|
1250
|
+
html: match[3],
|
|
1251
|
+
response
|
|
1252
|
+
};
|
|
1253
|
+
throw new AlephaError("Invalid HTML response");
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
|
|
923
1257
|
//#endregion
|
|
924
1258
|
//#region src/core/providers/ReactBrowserRouterProvider.ts
|
|
925
1259
|
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
@@ -1166,26 +1500,6 @@ var ReactBrowserProvider = class {
|
|
|
1166
1500
|
});
|
|
1167
1501
|
};
|
|
1168
1502
|
|
|
1169
|
-
//#endregion
|
|
1170
|
-
//#region src/core/providers/ReactBrowserRendererProvider.ts
|
|
1171
|
-
var ReactBrowserRendererProvider = class {
|
|
1172
|
-
log = $logger();
|
|
1173
|
-
root;
|
|
1174
|
-
onBrowserRender = $hook({
|
|
1175
|
-
on: "react:browser:render",
|
|
1176
|
-
handler: async ({ hydration, root, element }) => {
|
|
1177
|
-
if (hydration?.layers) {
|
|
1178
|
-
this.root = hydrateRoot(root, element);
|
|
1179
|
-
this.log.info("Hydrated root element");
|
|
1180
|
-
} else {
|
|
1181
|
-
this.root ??= createRoot(root);
|
|
1182
|
-
this.root.render(element);
|
|
1183
|
-
this.log.info("Created root element");
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
1503
|
//#endregion
|
|
1190
1504
|
//#region src/core/services/ReactRouter.ts
|
|
1191
1505
|
var ReactRouter = class {
|
|
@@ -1212,7 +1526,7 @@ var ReactRouter = class {
|
|
|
1212
1526
|
path(name, config = {}) {
|
|
1213
1527
|
return this.pageApi.pathname(name, {
|
|
1214
1528
|
params: {
|
|
1215
|
-
...this.state
|
|
1529
|
+
...this.state?.params,
|
|
1216
1530
|
...config.params
|
|
1217
1531
|
},
|
|
1218
1532
|
query: config.query
|
|
@@ -1311,19 +1625,31 @@ const useInject = (service) => {
|
|
|
1311
1625
|
};
|
|
1312
1626
|
|
|
1313
1627
|
//#endregion
|
|
1314
|
-
//#region src/core/index.
|
|
1628
|
+
//#region src/core/index.ts
|
|
1629
|
+
/**
|
|
1630
|
+
* Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
1631
|
+
*
|
|
1632
|
+
* The React module enables building modern React applications using the `$page` descriptor on class properties.
|
|
1633
|
+
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1634
|
+
* type safety and schema validation for route parameters and data.
|
|
1635
|
+
*
|
|
1636
|
+
* @see {@link $page}
|
|
1637
|
+
* @module alepha.react
|
|
1638
|
+
*/
|
|
1315
1639
|
const AlephaReact = $module({
|
|
1316
1640
|
name: "alepha.react",
|
|
1317
1641
|
descriptors: [$page],
|
|
1318
1642
|
services: [
|
|
1643
|
+
ReactServerProvider,
|
|
1319
1644
|
ReactPageProvider,
|
|
1320
|
-
ReactBrowserRouterProvider,
|
|
1321
|
-
ReactBrowserProvider,
|
|
1322
1645
|
ReactRouter,
|
|
1323
|
-
|
|
1324
|
-
|
|
1646
|
+
ReactPageService,
|
|
1647
|
+
ReactPageServerService
|
|
1325
1648
|
],
|
|
1326
|
-
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(
|
|
1649
|
+
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with({
|
|
1650
|
+
provide: ReactPageService,
|
|
1651
|
+
use: ReactPageServerService
|
|
1652
|
+
}).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
|
|
1327
1653
|
});
|
|
1328
1654
|
|
|
1329
1655
|
//#endregion
|