@alepha/react 0.8.1 → 0.9.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 +0 -121
- package/dist/index.browser.js +63 -50
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +273 -383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -183
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +48 -183
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -390
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/ErrorViewer.tsx +4 -3
- 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 +34 -26
- package/src/providers/ReactBrowserProvider.ts +1 -1
- package/src/providers/ReactBrowserRenderer.ts +2 -2
- package/src/providers/ReactServerProvider.ts +15 -13
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
|
|
@@ -67,23 +71,11 @@ const ClientOnly = (props) => {
|
|
|
67
71
|
};
|
|
68
72
|
var ClientOnly_default = ClientOnly;
|
|
69
73
|
|
|
70
|
-
//#endregion
|
|
71
|
-
//#region src/contexts/RouterContext.ts
|
|
72
|
-
const RouterContext = createContext(void 0);
|
|
73
|
-
|
|
74
|
-
//#endregion
|
|
75
|
-
//#region src/hooks/useAlepha.ts
|
|
76
|
-
const useAlepha = () => {
|
|
77
|
-
const routerContext = useContext(RouterContext);
|
|
78
|
-
if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
|
|
79
|
-
return routerContext.alepha;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
74
|
//#endregion
|
|
83
75
|
//#region src/components/ErrorViewer.tsx
|
|
84
|
-
const ErrorViewer = ({ error }) => {
|
|
76
|
+
const ErrorViewer = ({ error, alepha }) => {
|
|
85
77
|
const [expanded, setExpanded] = useState(false);
|
|
86
|
-
const isProduction =
|
|
78
|
+
const isProduction = alepha.isProduction();
|
|
87
79
|
if (isProduction) return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
|
|
88
80
|
const stackLines = error.stack?.split("\n") ?? [];
|
|
89
81
|
const previewLines = stackLines.slice(0, 5);
|
|
@@ -227,6 +219,10 @@ const ErrorViewerProduction = () => {
|
|
|
227
219
|
});
|
|
228
220
|
};
|
|
229
221
|
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/contexts/RouterContext.ts
|
|
224
|
+
const RouterContext = createContext(void 0);
|
|
225
|
+
|
|
230
226
|
//#endregion
|
|
231
227
|
//#region src/contexts/RouterLayerContext.ts
|
|
232
228
|
const RouterLayerContext = createContext(void 0);
|
|
@@ -337,7 +333,7 @@ var RedirectionError = class extends Error {
|
|
|
337
333
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
338
334
|
var PageDescriptorProvider = class {
|
|
339
335
|
log = $logger();
|
|
340
|
-
env = $
|
|
336
|
+
env = $env(envSchema$1);
|
|
341
337
|
alepha = $inject(Alepha);
|
|
342
338
|
pages = [];
|
|
343
339
|
getPages() {
|
|
@@ -510,7 +506,10 @@ var PageDescriptorProvider = class {
|
|
|
510
506
|
return void 0;
|
|
511
507
|
}
|
|
512
508
|
renderError(error) {
|
|
513
|
-
return createElement(ErrorViewer_default, {
|
|
509
|
+
return createElement(ErrorViewer_default, {
|
|
510
|
+
error,
|
|
511
|
+
alepha: this.alepha
|
|
512
|
+
});
|
|
514
513
|
}
|
|
515
514
|
renderEmptyView() {
|
|
516
515
|
return createElement(NestedView_default, {});
|
|
@@ -543,18 +542,17 @@ var PageDescriptorProvider = class {
|
|
|
543
542
|
on: "configure",
|
|
544
543
|
handler: () => {
|
|
545
544
|
let hasNotFoundHandler = false;
|
|
546
|
-
const pages = this.alepha.
|
|
545
|
+
const pages = this.alepha.descriptors($page);
|
|
547
546
|
const hasParent = (it) => {
|
|
548
547
|
for (const page of pages) {
|
|
549
|
-
const children = page.
|
|
548
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
550
549
|
if (children.includes(it)) return true;
|
|
551
550
|
}
|
|
552
551
|
};
|
|
553
|
-
for (const
|
|
554
|
-
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
this.add(this.map(pages, value));
|
|
552
|
+
for (const page of pages) {
|
|
553
|
+
if (page.options.path === "/*") hasNotFoundHandler = true;
|
|
554
|
+
if (hasParent(page)) continue;
|
|
555
|
+
this.add(this.map(pages, page));
|
|
558
556
|
}
|
|
559
557
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
560
558
|
path: "/*",
|
|
@@ -568,9 +566,10 @@ var PageDescriptorProvider = class {
|
|
|
568
566
|
}
|
|
569
567
|
});
|
|
570
568
|
map(pages, target) {
|
|
571
|
-
const children = target
|
|
569
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
572
570
|
return {
|
|
573
|
-
...target
|
|
571
|
+
...target.options,
|
|
572
|
+
name: target.name,
|
|
574
573
|
parent: void 0,
|
|
575
574
|
children: children.map((it) => this.map(pages, it))
|
|
576
575
|
};
|
|
@@ -808,7 +807,7 @@ var ReactBrowserProvider = class {
|
|
|
808
807
|
hydration
|
|
809
808
|
});
|
|
810
809
|
window.addEventListener("popstate", () => {
|
|
811
|
-
if (this.state.pathname ===
|
|
810
|
+
if (this.state.pathname === this.url) return;
|
|
812
811
|
this.render();
|
|
813
812
|
});
|
|
814
813
|
}
|
|
@@ -821,7 +820,7 @@ const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
|
821
820
|
var ReactBrowserRenderer = class {
|
|
822
821
|
browserProvider = $inject(ReactBrowserProvider);
|
|
823
822
|
browserRouterProvider = $inject(BrowserRouterProvider);
|
|
824
|
-
env = $
|
|
823
|
+
env = $env(envSchema);
|
|
825
824
|
log = $logger();
|
|
826
825
|
root;
|
|
827
826
|
options = { scrollRestoration: "top" };
|
|
@@ -948,9 +947,9 @@ const useRouter = () => {
|
|
|
948
947
|
const layer = useContext(RouterLayerContext);
|
|
949
948
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
950
949
|
const pages = useMemo(() => {
|
|
951
|
-
return ctx.alepha.
|
|
950
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
952
951
|
}, []);
|
|
953
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.
|
|
952
|
+
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
954
953
|
};
|
|
955
954
|
|
|
956
955
|
//#endregion
|
|
@@ -958,11 +957,11 @@ const useRouter = () => {
|
|
|
958
957
|
const Link = (props) => {
|
|
959
958
|
React.useContext(RouterContext);
|
|
960
959
|
const router = useRouter();
|
|
961
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
960
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
962
961
|
if (!to) return null;
|
|
963
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
962
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
964
963
|
if (can && !can()) return null;
|
|
965
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
964
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
966
965
|
const anchorProps = {
|
|
967
966
|
...props,
|
|
968
967
|
to: void 0
|
|
@@ -1009,12 +1008,20 @@ const useActive = (path) => {
|
|
|
1009
1008
|
};
|
|
1010
1009
|
};
|
|
1011
1010
|
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/hooks/useAlepha.ts
|
|
1013
|
+
const useAlepha = () => {
|
|
1014
|
+
const routerContext = useContext(RouterContext);
|
|
1015
|
+
if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
|
|
1016
|
+
return routerContext.alepha;
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1012
1019
|
//#endregion
|
|
1013
1020
|
//#region src/hooks/useInject.ts
|
|
1014
1021
|
const useInject = (clazz) => {
|
|
1015
1022
|
const ctx = useContext(RouterContext);
|
|
1016
1023
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1017
|
-
return useMemo(() => ctx.alepha.
|
|
1024
|
+
return useMemo(() => ctx.alepha.inject(clazz), []);
|
|
1018
1025
|
};
|
|
1019
1026
|
|
|
1020
1027
|
//#endregion
|
|
@@ -1069,12 +1076,18 @@ const useRouterState = () => {
|
|
|
1069
1076
|
|
|
1070
1077
|
//#endregion
|
|
1071
1078
|
//#region src/index.browser.ts
|
|
1072
|
-
|
|
1073
|
-
name
|
|
1074
|
-
$
|
|
1075
|
-
|
|
1076
|
-
|
|
1079
|
+
const AlephaReact = $module({
|
|
1080
|
+
name: "alepha.react",
|
|
1081
|
+
descriptors: [$page],
|
|
1082
|
+
services: [
|
|
1083
|
+
PageDescriptorProvider,
|
|
1084
|
+
ReactBrowserRenderer,
|
|
1085
|
+
BrowserRouterProvider,
|
|
1086
|
+
ReactBrowserProvider
|
|
1087
|
+
],
|
|
1088
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer)
|
|
1089
|
+
});
|
|
1077
1090
|
|
|
1078
1091
|
//#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 };
|
|
1092
|
+
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
1093
|
//# sourceMappingURL=index.browser.js.map
|