@alepha/react 0.7.7 → 0.8.1
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 +153 -1
- package/dist/index.browser.js +61 -26
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +175 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -41
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +197 -40
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +180 -31
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/components/NestedView.tsx +3 -1
- package/src/descriptors/$page.ts +50 -42
- package/src/hooks/RouterHookApi.ts +17 -0
- package/src/hooks/useRouter.ts +1 -0
- package/src/index.browser.ts +4 -0
- package/src/index.shared.ts +1 -0
- package/src/index.ts +127 -3
- package/src/providers/BrowserRouterProvider.ts +1 -1
- package/src/providers/PageDescriptorProvider.ts +39 -23
- package/src/providers/ReactBrowserProvider.ts +38 -5
- package/src/providers/ReactBrowserRenderer.ts +21 -1
- package/src/providers/ReactServerProvider.ts +5 -5
- package/dist/index.browser.d.ts +0 -523
package/dist/index.cjs
CHANGED
|
@@ -24,11 +24,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
const __alepha_core = __toESM(require("@alepha/core"));
|
|
25
25
|
const __alepha_server = __toESM(require("@alepha/server"));
|
|
26
26
|
const __alepha_server_cache = __toESM(require("@alepha/server-cache"));
|
|
27
|
+
const __alepha_server_links = __toESM(require("@alepha/server-links"));
|
|
27
28
|
const react = __toESM(require("react"));
|
|
28
29
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
29
30
|
const node_fs = __toESM(require("node:fs"));
|
|
30
31
|
const node_path = __toESM(require("node:path"));
|
|
31
|
-
const __alepha_server_links = __toESM(require("@alepha/server-links"));
|
|
32
32
|
const __alepha_server_static = __toESM(require("@alepha/server-static"));
|
|
33
33
|
const react_dom_server = __toESM(require("react-dom/server"));
|
|
34
34
|
const __alepha_router = __toESM(require("@alepha/router"));
|
|
@@ -40,11 +40,6 @@ const KEY = "PAGE";
|
|
|
40
40
|
*/
|
|
41
41
|
const $page = (options) => {
|
|
42
42
|
(0, __alepha_core.__descriptor)(KEY);
|
|
43
|
-
if (options.children) for (const child of options.children) child[__alepha_core.OPTIONS].parent = { [__alepha_core.OPTIONS]: options };
|
|
44
|
-
if (options.parent) {
|
|
45
|
-
options.parent[__alepha_core.OPTIONS].children ??= [];
|
|
46
|
-
options.parent[__alepha_core.OPTIONS].children.push({ [__alepha_core.OPTIONS]: options });
|
|
47
|
-
}
|
|
48
43
|
return {
|
|
49
44
|
[__alepha_core.KIND]: KEY,
|
|
50
45
|
[__alepha_core.OPTIONS]: options,
|
|
@@ -319,7 +314,7 @@ const NestedView = (props) => {
|
|
|
319
314
|
const index = layer?.index ?? 0;
|
|
320
315
|
const [view, setView] = (0, react.useState)(app?.state.layers[index]?.element);
|
|
321
316
|
useRouterEvents({ onEnd: ({ state }) => {
|
|
322
|
-
setView(state.layers[index]?.element);
|
|
317
|
+
if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
|
|
323
318
|
} }, [app]);
|
|
324
319
|
if (!app) throw new Error("NestedView must be used within a RouterContext.");
|
|
325
320
|
const element = view ?? props.children ?? null;
|
|
@@ -417,19 +412,18 @@ var PageDescriptorProvider = class {
|
|
|
417
412
|
const route$1 = it.route;
|
|
418
413
|
const config = {};
|
|
419
414
|
try {
|
|
420
|
-
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) :
|
|
415
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
|
|
421
416
|
} catch (e) {
|
|
422
417
|
it.error = e;
|
|
423
418
|
break;
|
|
424
419
|
}
|
|
425
420
|
try {
|
|
426
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) :
|
|
421
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
|
|
427
422
|
} catch (e) {
|
|
428
423
|
it.error = e;
|
|
429
424
|
break;
|
|
430
425
|
}
|
|
431
426
|
it.config = { ...config };
|
|
432
|
-
if (!route$1.resolve) continue;
|
|
433
427
|
const previous = request.previous;
|
|
434
428
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
435
429
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
@@ -444,6 +438,7 @@ var PageDescriptorProvider = class {
|
|
|
444
438
|
if (prev === curr) {
|
|
445
439
|
it.props = previous[i].props;
|
|
446
440
|
it.error = previous[i].error;
|
|
441
|
+
it.cache = true;
|
|
447
442
|
context = {
|
|
448
443
|
...context,
|
|
449
444
|
...it.props
|
|
@@ -452,6 +447,7 @@ var PageDescriptorProvider = class {
|
|
|
452
447
|
}
|
|
453
448
|
forceRefresh = true;
|
|
454
449
|
}
|
|
450
|
+
if (!route$1.resolve) continue;
|
|
455
451
|
try {
|
|
456
452
|
const props = await route$1.resolve?.({
|
|
457
453
|
...request,
|
|
@@ -498,7 +494,7 @@ var PageDescriptorProvider = class {
|
|
|
498
494
|
element: this.renderView(i + 1, path, element$1, it.route),
|
|
499
495
|
index: i + 1,
|
|
500
496
|
path,
|
|
501
|
-
route
|
|
497
|
+
route: it.route
|
|
502
498
|
});
|
|
503
499
|
break;
|
|
504
500
|
}
|
|
@@ -514,7 +510,8 @@ var PageDescriptorProvider = class {
|
|
|
514
510
|
element: this.renderView(i + 1, path, element, it.route),
|
|
515
511
|
index: i + 1,
|
|
516
512
|
path,
|
|
517
|
-
route
|
|
513
|
+
route: it.route,
|
|
514
|
+
cache: it.cache
|
|
518
515
|
});
|
|
519
516
|
}
|
|
520
517
|
return {
|
|
@@ -570,14 +567,20 @@ var PageDescriptorProvider = class {
|
|
|
570
567
|
} }, element);
|
|
571
568
|
}
|
|
572
569
|
configure = (0, __alepha_core.$hook)({
|
|
573
|
-
|
|
570
|
+
on: "configure",
|
|
574
571
|
handler: () => {
|
|
575
572
|
let hasNotFoundHandler = false;
|
|
576
573
|
const pages = this.alepha.getDescriptorValues($page);
|
|
574
|
+
const hasParent = (it) => {
|
|
575
|
+
for (const page of pages) {
|
|
576
|
+
const children = page.value[__alepha_core.OPTIONS].children ? Array.isArray(page.value[__alepha_core.OPTIONS].children) ? page.value[__alepha_core.OPTIONS].children : page.value[__alepha_core.OPTIONS].children() : [];
|
|
577
|
+
if (children.includes(it)) return true;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
577
580
|
for (const { value, key } of pages) value[__alepha_core.OPTIONS].name ??= key;
|
|
578
581
|
for (const { value } of pages) {
|
|
579
|
-
if (value[__alepha_core.OPTIONS].parent) continue;
|
|
580
582
|
if (value[__alepha_core.OPTIONS].path === "/*") hasNotFoundHandler = true;
|
|
583
|
+
if (hasParent(value)) continue;
|
|
581
584
|
this.add(this.map(pages, value));
|
|
582
585
|
}
|
|
583
586
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
@@ -592,7 +595,7 @@ var PageDescriptorProvider = class {
|
|
|
592
595
|
}
|
|
593
596
|
});
|
|
594
597
|
map(pages, target) {
|
|
595
|
-
const children = target[__alepha_core.OPTIONS].children
|
|
598
|
+
const children = target[__alepha_core.OPTIONS].children ? Array.isArray(target[__alepha_core.OPTIONS].children) ? target[__alepha_core.OPTIONS].children : target[__alepha_core.OPTIONS].children() : [];
|
|
596
599
|
return {
|
|
597
600
|
...target[__alepha_core.OPTIONS],
|
|
598
601
|
parent: void 0,
|
|
@@ -649,11 +652,11 @@ var ReactServerProvider = class {
|
|
|
649
652
|
env = (0, __alepha_core.$inject)(envSchema);
|
|
650
653
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
651
654
|
onConfigure = (0, __alepha_core.$hook)({
|
|
652
|
-
|
|
655
|
+
on: "configure",
|
|
653
656
|
handler: async () => {
|
|
654
657
|
const pages = this.alepha.getDescriptorValues($page);
|
|
655
658
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
656
|
-
this.alepha.state("
|
|
659
|
+
this.alepha.state("react.server.ssr", ssrEnabled);
|
|
657
660
|
for (const { key, instance, value } of pages) {
|
|
658
661
|
const name = value[__alepha_core.OPTIONS].name ?? key;
|
|
659
662
|
instance[key].render = this.createRenderFunction(name);
|
|
@@ -693,7 +696,7 @@ var ReactServerProvider = class {
|
|
|
693
696
|
}
|
|
694
697
|
});
|
|
695
698
|
get template() {
|
|
696
|
-
return this.alepha.state("
|
|
699
|
+
return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
697
700
|
}
|
|
698
701
|
async registerPages(templateLoader) {
|
|
699
702
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
@@ -868,7 +871,7 @@ var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
|
868
871
|
this.pageDescriptorProvider.add(entry);
|
|
869
872
|
}
|
|
870
873
|
configure = (0, __alepha_core.$hook)({
|
|
871
|
-
|
|
874
|
+
on: "configure",
|
|
872
875
|
handler: async () => {
|
|
873
876
|
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
874
877
|
path: page.match,
|
|
@@ -974,8 +977,17 @@ var ReactBrowserProvider = class {
|
|
|
974
977
|
get history() {
|
|
975
978
|
return window.history;
|
|
976
979
|
}
|
|
980
|
+
get location() {
|
|
981
|
+
return window.location;
|
|
982
|
+
}
|
|
977
983
|
get url() {
|
|
978
|
-
|
|
984
|
+
let url = this.location.pathname + this.location.search;
|
|
985
|
+
return url;
|
|
986
|
+
}
|
|
987
|
+
pushState(url, replace) {
|
|
988
|
+
let path = url;
|
|
989
|
+
if (replace) this.history.replaceState({}, "", path);
|
|
990
|
+
else this.history.pushState({}, "", path);
|
|
979
991
|
}
|
|
980
992
|
async invalidate(props) {
|
|
981
993
|
const previous = [];
|
|
@@ -1001,14 +1013,14 @@ var ReactBrowserProvider = class {
|
|
|
1001
1013
|
async go(url, options = {}) {
|
|
1002
1014
|
const result = await this.render({ url });
|
|
1003
1015
|
if (result.context.url.pathname !== url) {
|
|
1004
|
-
this.
|
|
1016
|
+
this.pushState(result.context.url.pathname);
|
|
1005
1017
|
return;
|
|
1006
1018
|
}
|
|
1007
1019
|
if (options.replace) {
|
|
1008
|
-
this.
|
|
1020
|
+
this.pushState(url);
|
|
1009
1021
|
return;
|
|
1010
1022
|
}
|
|
1011
|
-
this.
|
|
1023
|
+
this.pushState(url);
|
|
1012
1024
|
}
|
|
1013
1025
|
async render(options = {}) {
|
|
1014
1026
|
const previous = options.previous ?? this.state.layers;
|
|
@@ -1033,7 +1045,7 @@ var ReactBrowserProvider = class {
|
|
|
1033
1045
|
}
|
|
1034
1046
|
}
|
|
1035
1047
|
ready = (0, __alepha_core.$hook)({
|
|
1036
|
-
|
|
1048
|
+
on: "ready",
|
|
1037
1049
|
handler: async () => {
|
|
1038
1050
|
const hydration = this.getHydrationState();
|
|
1039
1051
|
const previous = hydration?.layers ?? [];
|
|
@@ -1045,6 +1057,7 @@ var ReactBrowserProvider = class {
|
|
|
1045
1057
|
hydration
|
|
1046
1058
|
});
|
|
1047
1059
|
window.addEventListener("popstate", () => {
|
|
1060
|
+
if (this.state.pathname === location.pathname) return;
|
|
1048
1061
|
this.render();
|
|
1049
1062
|
});
|
|
1050
1063
|
}
|
|
@@ -1054,12 +1067,21 @@ var ReactBrowserProvider = class {
|
|
|
1054
1067
|
//#endregion
|
|
1055
1068
|
//#region src/hooks/RouterHookApi.ts
|
|
1056
1069
|
var RouterHookApi = class {
|
|
1057
|
-
constructor(pages, state, layer, browser) {
|
|
1070
|
+
constructor(pages, context, state, layer, browser) {
|
|
1058
1071
|
this.pages = pages;
|
|
1072
|
+
this.context = context;
|
|
1059
1073
|
this.state = state;
|
|
1060
1074
|
this.layer = layer;
|
|
1061
1075
|
this.browser = browser;
|
|
1062
1076
|
}
|
|
1077
|
+
getURL() {
|
|
1078
|
+
if (!this.browser) return this.context.url;
|
|
1079
|
+
return new URL(this.location.href);
|
|
1080
|
+
}
|
|
1081
|
+
get location() {
|
|
1082
|
+
if (!this.browser) throw new Error("Browser is required");
|
|
1083
|
+
return this.browser.location;
|
|
1084
|
+
}
|
|
1063
1085
|
get current() {
|
|
1064
1086
|
return this.state;
|
|
1065
1087
|
}
|
|
@@ -1137,7 +1159,7 @@ const useRouter = () => {
|
|
|
1137
1159
|
const pages = (0, react.useMemo)(() => {
|
|
1138
1160
|
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
1139
1161
|
}, []);
|
|
1140
|
-
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
|
|
1162
|
+
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
|
|
1141
1163
|
};
|
|
1142
1164
|
|
|
1143
1165
|
//#endregion
|
|
@@ -1257,17 +1279,139 @@ const useRouterState = () => {
|
|
|
1257
1279
|
//#endregion
|
|
1258
1280
|
//#region src/index.ts
|
|
1259
1281
|
/**
|
|
1260
|
-
*
|
|
1282
|
+
* Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
1283
|
+
*
|
|
1284
|
+
* The React module enables building modern React applications using the `$page` descriptor on class properties.
|
|
1285
|
+
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1286
|
+
* type safety and schema validation for route parameters and data.
|
|
1287
|
+
*
|
|
1288
|
+
* **Key Features:**
|
|
1289
|
+
* - Declarative page definition with `$page` descriptor
|
|
1290
|
+
* - Server-side rendering (SSR) with automatic hydration
|
|
1291
|
+
* - Type-safe routing with parameter validation
|
|
1292
|
+
* - Schema-based data resolution and validation
|
|
1293
|
+
* - SEO-friendly meta tag management
|
|
1294
|
+
* - Automatic code splitting and lazy loading
|
|
1295
|
+
* - Client-side navigation with browser history
|
|
1261
1296
|
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1297
|
+
* **Basic Usage:**
|
|
1298
|
+
* ```ts
|
|
1299
|
+
* import { Alepha, run, t } from "alepha";
|
|
1300
|
+
* import { AlephaReact, $page } from "alepha/react";
|
|
1301
|
+
*
|
|
1302
|
+
* class AppRoutes {
|
|
1303
|
+
* // Home page
|
|
1304
|
+
* home = $page({
|
|
1305
|
+
* path: "/",
|
|
1306
|
+
* component: () => (
|
|
1307
|
+
* <div>
|
|
1308
|
+
* <h1>Welcome to Alepha</h1>
|
|
1309
|
+
* <p>Build amazing React applications!</p>
|
|
1310
|
+
* </div>
|
|
1311
|
+
* ),
|
|
1312
|
+
* });
|
|
1313
|
+
*
|
|
1314
|
+
* // About page with meta tags
|
|
1315
|
+
* about = $page({
|
|
1316
|
+
* path: "/about",
|
|
1317
|
+
* head: {
|
|
1318
|
+
* title: "About Us",
|
|
1319
|
+
* description: "Learn more about our mission",
|
|
1320
|
+
* },
|
|
1321
|
+
* component: () => (
|
|
1322
|
+
* <div>
|
|
1323
|
+
* <h1>About Us</h1>
|
|
1324
|
+
* <p>Learn more about our mission.</p>
|
|
1325
|
+
* </div>
|
|
1326
|
+
* ),
|
|
1327
|
+
* });
|
|
1328
|
+
* }
|
|
1329
|
+
*
|
|
1330
|
+
* const alepha = Alepha.create()
|
|
1331
|
+
* .with(AlephaReact)
|
|
1332
|
+
* .with(AppRoutes);
|
|
1333
|
+
*
|
|
1334
|
+
* run(alepha);
|
|
1335
|
+
* ```
|
|
1336
|
+
*
|
|
1337
|
+
* **Dynamic Routes with Parameters:**
|
|
1338
|
+
* ```tsx
|
|
1339
|
+
* class UserRoutes {
|
|
1340
|
+
* userProfile = $page({
|
|
1341
|
+
* path: "/users/:id",
|
|
1342
|
+
* schema: {
|
|
1343
|
+
* params: t.object({
|
|
1344
|
+
* id: t.string(),
|
|
1345
|
+
* }),
|
|
1346
|
+
* },
|
|
1347
|
+
* resolve: async ({ params }) => {
|
|
1348
|
+
* // Fetch user data server-side
|
|
1349
|
+
* const user = await getUserById(params.id);
|
|
1350
|
+
* return { user };
|
|
1351
|
+
* },
|
|
1352
|
+
* head: ({ user }) => ({
|
|
1353
|
+
* title: `${user.name} - Profile`,
|
|
1354
|
+
* description: `View ${user.name}'s profile`,
|
|
1355
|
+
* }),
|
|
1356
|
+
* component: ({ user }) => (
|
|
1357
|
+
* <div>
|
|
1358
|
+
* <h1>{user.name}</h1>
|
|
1359
|
+
* <p>Email: {user.email}</p>
|
|
1360
|
+
* </div>
|
|
1361
|
+
* ),
|
|
1362
|
+
* });
|
|
1363
|
+
*
|
|
1364
|
+
* userSettings = $page({
|
|
1365
|
+
* path: "/users/:id/settings",
|
|
1366
|
+
* schema: {
|
|
1367
|
+
* params: t.object({
|
|
1368
|
+
* id: t.string(),
|
|
1369
|
+
* }),
|
|
1370
|
+
* },
|
|
1371
|
+
* component: ({ params }) => (
|
|
1372
|
+
* <UserSettings userId={params.id} />
|
|
1373
|
+
* ),
|
|
1374
|
+
* });
|
|
1375
|
+
* }
|
|
1376
|
+
* ```
|
|
1377
|
+
*
|
|
1378
|
+
* **Static Generation:**
|
|
1379
|
+
* ```tsx
|
|
1380
|
+
* class BlogRoutes {
|
|
1381
|
+
* blogPost = $page({
|
|
1382
|
+
* path: "/blog/:slug",
|
|
1383
|
+
* schema: {
|
|
1384
|
+
* params: t.object({
|
|
1385
|
+
* slug: t.string(),
|
|
1386
|
+
* }),
|
|
1387
|
+
* },
|
|
1388
|
+
* static: {
|
|
1389
|
+
* entries: [
|
|
1390
|
+
* { params: { slug: "getting-started" } },
|
|
1391
|
+
* { params: { slug: "advanced-features" } },
|
|
1392
|
+
* { params: { slug: "deployment" } },
|
|
1393
|
+
* ],
|
|
1394
|
+
* },
|
|
1395
|
+
* resolve: ({ params }) => {
|
|
1396
|
+
* const post = getBlogPost(params.slug);
|
|
1397
|
+
* return { post };
|
|
1398
|
+
* },
|
|
1399
|
+
* component: ({ post }) => (
|
|
1400
|
+
* <article>
|
|
1401
|
+
* <h1>{post.title}</h1>
|
|
1402
|
+
* <div>{post.content}</div>
|
|
1403
|
+
* </article>
|
|
1404
|
+
* ),
|
|
1405
|
+
* });
|
|
1406
|
+
* }
|
|
1407
|
+
* ```
|
|
1264
1408
|
*
|
|
1265
1409
|
* @see {@link $page}
|
|
1266
1410
|
* @module alepha.react
|
|
1267
1411
|
*/
|
|
1268
1412
|
var AlephaReact = class {
|
|
1269
1413
|
name = "alepha.react";
|
|
1270
|
-
$services = (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(ReactServerProvider).with(PageDescriptorProvider);
|
|
1414
|
+
$services = (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
|
|
1271
1415
|
};
|
|
1272
1416
|
(0, __alepha_core.__bind)($page, AlephaReact);
|
|
1273
1417
|
|
|
@@ -1278,6 +1422,7 @@ exports.ClientOnly = ClientOnly_default;
|
|
|
1278
1422
|
exports.ErrorBoundary = ErrorBoundary_default;
|
|
1279
1423
|
exports.Link = Link_default;
|
|
1280
1424
|
exports.NestedView = NestedView_default;
|
|
1425
|
+
exports.NotFound = NotFoundPage;
|
|
1281
1426
|
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
1282
1427
|
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
1283
1428
|
exports.ReactServerProvider = ReactServerProvider;
|