@alepha/react 0.8.1 → 0.9.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/README.md +0 -121
- package/dist/index.browser.js +44 -34
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +252 -366
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -182
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +47 -183
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +258 -373
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/Link.tsx +3 -5
- package/src/descriptors/$page.ts +30 -49
- package/src/hooks/useInject.ts +1 -1
- package/src/hooks/useRouter.ts +2 -2
- package/src/index.browser.ts +13 -8
- package/src/index.ts +12 -130
- package/src/providers/PageDescriptorProvider.ts +33 -25
- package/src/providers/ReactBrowserRenderer.ts +2 -2
- package/src/providers/ReactServerProvider.ts +9 -11
package/README.md
CHANGED
|
@@ -23,127 +23,6 @@ The React module enables building modern React applications using the `$page` de
|
|
|
23
23
|
It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
24
24
|
type safety and schema validation for route parameters and data.
|
|
25
25
|
|
|
26
|
-
**Key Features:**
|
|
27
|
-
- Declarative page definition with `$page` descriptor
|
|
28
|
-
- Server-side rendering (SSR) with automatic hydration
|
|
29
|
-
- Type-safe routing with parameter validation
|
|
30
|
-
- Schema-based data resolution and validation
|
|
31
|
-
- SEO-friendly meta tag management
|
|
32
|
-
- Automatic code splitting and lazy loading
|
|
33
|
-
- Client-side navigation with browser history
|
|
34
|
-
|
|
35
|
-
**Basic Usage:**
|
|
36
|
-
```ts
|
|
37
|
-
import { Alepha, run, t } from "alepha";
|
|
38
|
-
import { AlephaReact, $page } from "alepha/react";
|
|
39
|
-
|
|
40
|
-
class AppRoutes {
|
|
41
|
-
// Home page
|
|
42
|
-
home = $page({
|
|
43
|
-
path: "/",
|
|
44
|
-
component: () => (
|
|
45
|
-
<div>
|
|
46
|
-
<h1>Welcome to Alepha</h1>
|
|
47
|
-
<p>Build amazing React applications!</p>
|
|
48
|
-
</div>
|
|
49
|
-
),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// About page with meta tags
|
|
53
|
-
about = $page({
|
|
54
|
-
path: "/about",
|
|
55
|
-
head: {
|
|
56
|
-
title: "About Us",
|
|
57
|
-
description: "Learn more about our mission",
|
|
58
|
-
},
|
|
59
|
-
component: () => (
|
|
60
|
-
<div>
|
|
61
|
-
<h1>About Us</h1>
|
|
62
|
-
<p>Learn more about our mission.</p>
|
|
63
|
-
</div>
|
|
64
|
-
),
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const alepha = Alepha.create()
|
|
69
|
-
.with(AlephaReact)
|
|
70
|
-
.with(AppRoutes);
|
|
71
|
-
|
|
72
|
-
run(alepha);
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**Dynamic Routes with Parameters:**
|
|
76
|
-
```tsx
|
|
77
|
-
class UserRoutes {
|
|
78
|
-
userProfile = $page({
|
|
79
|
-
path: "/users/:id",
|
|
80
|
-
schema: {
|
|
81
|
-
params: t.object({
|
|
82
|
-
id: t.string(),
|
|
83
|
-
}),
|
|
84
|
-
},
|
|
85
|
-
resolve: async ({ params }) => {
|
|
86
|
-
// Fetch user data server-side
|
|
87
|
-
const user = await getUserById(params.id);
|
|
88
|
-
return { user };
|
|
89
|
-
},
|
|
90
|
-
head: ({ user }) => ({
|
|
91
|
-
title: `${user.name} - Profile`,
|
|
92
|
-
description: `View ${user.name}'s profile`,
|
|
93
|
-
}),
|
|
94
|
-
component: ({ user }) => (
|
|
95
|
-
<div>
|
|
96
|
-
<h1>{user.name}</h1>
|
|
97
|
-
<p>Email: {user.email}</p>
|
|
98
|
-
</div>
|
|
99
|
-
),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
userSettings = $page({
|
|
103
|
-
path: "/users/:id/settings",
|
|
104
|
-
schema: {
|
|
105
|
-
params: t.object({
|
|
106
|
-
id: t.string(),
|
|
107
|
-
}),
|
|
108
|
-
},
|
|
109
|
-
component: ({ params }) => (
|
|
110
|
-
<UserSettings userId={params.id} />
|
|
111
|
-
),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**Static Generation:**
|
|
117
|
-
```tsx
|
|
118
|
-
class BlogRoutes {
|
|
119
|
-
blogPost = $page({
|
|
120
|
-
path: "/blog/:slug",
|
|
121
|
-
schema: {
|
|
122
|
-
params: t.object({
|
|
123
|
-
slug: t.string(),
|
|
124
|
-
}),
|
|
125
|
-
},
|
|
126
|
-
static: {
|
|
127
|
-
entries: [
|
|
128
|
-
{ params: { slug: "getting-started" } },
|
|
129
|
-
{ params: { slug: "advanced-features" } },
|
|
130
|
-
{ params: { slug: "deployment" } },
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
resolve: ({ params }) => {
|
|
134
|
-
const post = getBlogPost(params.slug);
|
|
135
|
-
return { post };
|
|
136
|
-
},
|
|
137
|
-
component: ({ post }) => (
|
|
138
|
-
<article>
|
|
139
|
-
<h1>{post.title}</h1>
|
|
140
|
-
<div>{post.content}</div>
|
|
141
|
-
</article>
|
|
142
|
-
),
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
26
|
## API Reference
|
|
148
27
|
|
|
149
28
|
### Descriptors
|
package/dist/index.browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $hook, $inject, $logger,
|
|
1
|
+
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, NotImplementedError, createDescriptor, t } from "@alepha/core";
|
|
2
2
|
import { AlephaServer } from "@alepha/server";
|
|
3
3
|
import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
|
|
4
4
|
import { RouterProvider } from "@alepha/router";
|
|
@@ -7,21 +7,25 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
8
8
|
|
|
9
9
|
//#region src/descriptors/$page.ts
|
|
10
|
-
const KEY = "PAGE";
|
|
11
10
|
/**
|
|
12
11
|
* Main descriptor for defining a React route in the application.
|
|
13
12
|
*/
|
|
14
13
|
const $page = (options) => {
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
[KIND]: KEY,
|
|
18
|
-
[OPTIONS]: options,
|
|
19
|
-
render: () => {
|
|
20
|
-
throw new NotImplementedError(KEY);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
14
|
+
return createDescriptor(PageDescriptor, options);
|
|
23
15
|
};
|
|
24
|
-
|
|
16
|
+
var PageDescriptor = class extends Descriptor {
|
|
17
|
+
get name() {
|
|
18
|
+
return this.options.name ?? this.config.propertyKey;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
|
|
22
|
+
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
23
|
+
*/
|
|
24
|
+
async render(options) {
|
|
25
|
+
throw new NotImplementedError("");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
$page[KIND] = PageDescriptor;
|
|
25
29
|
|
|
26
30
|
//#endregion
|
|
27
31
|
//#region src/components/NotFound.tsx
|
|
@@ -337,7 +341,7 @@ var RedirectionError = class extends Error {
|
|
|
337
341
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
338
342
|
var PageDescriptorProvider = class {
|
|
339
343
|
log = $logger();
|
|
340
|
-
env = $
|
|
344
|
+
env = $env(envSchema$1);
|
|
341
345
|
alepha = $inject(Alepha);
|
|
342
346
|
pages = [];
|
|
343
347
|
getPages() {
|
|
@@ -543,18 +547,17 @@ var PageDescriptorProvider = class {
|
|
|
543
547
|
on: "configure",
|
|
544
548
|
handler: () => {
|
|
545
549
|
let hasNotFoundHandler = false;
|
|
546
|
-
const pages = this.alepha.
|
|
550
|
+
const pages = this.alepha.descriptors($page);
|
|
547
551
|
const hasParent = (it) => {
|
|
548
552
|
for (const page of pages) {
|
|
549
|
-
const children = page.
|
|
553
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
550
554
|
if (children.includes(it)) return true;
|
|
551
555
|
}
|
|
552
556
|
};
|
|
553
|
-
for (const
|
|
554
|
-
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
this.add(this.map(pages, value));
|
|
557
|
+
for (const page of pages) {
|
|
558
|
+
if (page.options.path === "/*") hasNotFoundHandler = true;
|
|
559
|
+
if (hasParent(page)) continue;
|
|
560
|
+
this.add(this.map(pages, page));
|
|
558
561
|
}
|
|
559
562
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
560
563
|
path: "/*",
|
|
@@ -568,9 +571,10 @@ var PageDescriptorProvider = class {
|
|
|
568
571
|
}
|
|
569
572
|
});
|
|
570
573
|
map(pages, target) {
|
|
571
|
-
const children = target
|
|
574
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
572
575
|
return {
|
|
573
|
-
...target
|
|
576
|
+
...target.options,
|
|
577
|
+
name: target.name,
|
|
574
578
|
parent: void 0,
|
|
575
579
|
children: children.map((it) => this.map(pages, it))
|
|
576
580
|
};
|
|
@@ -821,7 +825,7 @@ const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
|
821
825
|
var ReactBrowserRenderer = class {
|
|
822
826
|
browserProvider = $inject(ReactBrowserProvider);
|
|
823
827
|
browserRouterProvider = $inject(BrowserRouterProvider);
|
|
824
|
-
env = $
|
|
828
|
+
env = $env(envSchema);
|
|
825
829
|
log = $logger();
|
|
826
830
|
root;
|
|
827
831
|
options = { scrollRestoration: "top" };
|
|
@@ -948,9 +952,9 @@ const useRouter = () => {
|
|
|
948
952
|
const layer = useContext(RouterLayerContext);
|
|
949
953
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
950
954
|
const pages = useMemo(() => {
|
|
951
|
-
return ctx.alepha.
|
|
955
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
952
956
|
}, []);
|
|
953
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.
|
|
957
|
+
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
954
958
|
};
|
|
955
959
|
|
|
956
960
|
//#endregion
|
|
@@ -958,11 +962,11 @@ const useRouter = () => {
|
|
|
958
962
|
const Link = (props) => {
|
|
959
963
|
React.useContext(RouterContext);
|
|
960
964
|
const router = useRouter();
|
|
961
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
965
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
962
966
|
if (!to) return null;
|
|
963
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
967
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
964
968
|
if (can && !can()) return null;
|
|
965
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
969
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
966
970
|
const anchorProps = {
|
|
967
971
|
...props,
|
|
968
972
|
to: void 0
|
|
@@ -1014,7 +1018,7 @@ const useActive = (path) => {
|
|
|
1014
1018
|
const useInject = (clazz) => {
|
|
1015
1019
|
const ctx = useContext(RouterContext);
|
|
1016
1020
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1017
|
-
return useMemo(() => ctx.alepha.
|
|
1021
|
+
return useMemo(() => ctx.alepha.inject(clazz), []);
|
|
1018
1022
|
};
|
|
1019
1023
|
|
|
1020
1024
|
//#endregion
|
|
@@ -1069,12 +1073,18 @@ const useRouterState = () => {
|
|
|
1069
1073
|
|
|
1070
1074
|
//#endregion
|
|
1071
1075
|
//#region src/index.browser.ts
|
|
1072
|
-
|
|
1073
|
-
name
|
|
1074
|
-
$
|
|
1075
|
-
|
|
1076
|
-
|
|
1076
|
+
const AlephaReact = $module({
|
|
1077
|
+
name: "alepha.react",
|
|
1078
|
+
descriptors: [$page],
|
|
1079
|
+
services: [
|
|
1080
|
+
PageDescriptorProvider,
|
|
1081
|
+
ReactBrowserRenderer,
|
|
1082
|
+
BrowserRouterProvider,
|
|
1083
|
+
ReactBrowserProvider
|
|
1084
|
+
],
|
|
1085
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer)
|
|
1086
|
+
});
|
|
1077
1087
|
|
|
1078
1088
|
//#endregion
|
|
1079
|
-
export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
|
|
1089
|
+
export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
|
|
1080
1090
|
//# sourceMappingURL=index.browser.js.map
|