@alepha/react 0.14.1 → 0.14.2
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/index.browser.js +1488 -4
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +1827 -4
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +54 -937
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +132 -2010
- package/dist/core/index.js.map +1 -1
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +6 -1
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.browser.js +3 -1
- package/dist/head/index.browser.js.map +1 -1
- package/dist/head/index.d.ts +552 -8
- package/dist/head/index.d.ts.map +1 -1
- package/dist/head/index.js +15 -1
- package/dist/head/index.js.map +1 -1
- package/dist/{core → router}/index.browser.js +126 -516
- package/dist/router/index.browser.js.map +1 -0
- package/dist/router/index.d.ts +1334 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +1939 -0
- package/dist/router/index.js.map +1 -0
- package/package.json +12 -6
- package/src/auth/index.ts +1 -1
- package/src/auth/services/ReactAuth.ts +1 -1
- package/src/core/components/ClientOnly.tsx +14 -0
- package/src/core/components/ErrorBoundary.tsx +3 -2
- package/src/core/contexts/AlephaContext.ts +3 -0
- package/src/core/contexts/AlephaProvider.tsx +2 -1
- package/src/core/index.ts +13 -102
- package/src/form/services/FormModel.ts +5 -0
- package/src/head/index.ts +16 -7
- package/src/head/interfaces/Head.ts +3 -0
- package/src/head/providers/HeadProvider.ts +6 -1
- package/src/head/providers/ServerHeadProvider.ts +20 -0
- package/src/{core → router}/components/ErrorViewer.tsx +2 -0
- package/src/router/components/Link.tsx +21 -0
- package/src/{core → router}/components/NestedView.tsx +3 -5
- package/src/router/components/NotFound.tsx +30 -0
- package/src/router/errors/Redirection.ts +28 -0
- package/src/{core → router}/hooks/useActive.ts +6 -2
- package/src/{core → router}/hooks/useQueryParams.ts +2 -2
- package/src/{core → router}/hooks/useRouter.ts +1 -1
- package/src/{core → router}/hooks/useRouterState.ts +1 -1
- package/src/{core → router}/index.browser.ts +14 -12
- package/src/{core/index.shared-router.ts → router/index.shared.ts} +6 -3
- package/src/router/index.ts +125 -0
- package/src/{core → router}/primitives/$page.ts +1 -1
- package/src/{core → router}/providers/ReactBrowserProvider.ts +3 -13
- package/src/{core → router}/providers/ReactBrowserRendererProvider.ts +3 -0
- package/src/{core → router}/providers/ReactBrowserRouterProvider.ts +3 -0
- package/src/{core → router}/providers/ReactPageProvider.ts +5 -3
- package/src/{core → router}/providers/ReactServerProvider.ts +9 -28
- package/src/{core → router}/services/ReactPageServerService.ts +3 -0
- package/src/{core → router}/services/ReactPageService.ts +5 -5
- package/src/{core → router}/services/ReactRouter.ts +26 -5
- package/dist/core/index.browser.js.map +0 -1
- package/dist/core/index.native.js +0 -403
- package/dist/core/index.native.js.map +0 -1
- package/src/core/components/Link.tsx +0 -18
- package/src/core/components/NotFound.tsx +0 -27
- package/src/core/errors/Redirection.ts +0 -13
- package/src/core/hooks/useSchema.ts +0 -88
- package/src/core/index.native.ts +0 -21
- package/src/core/index.shared.ts +0 -9
- /package/src/{core → router}/contexts/RouterLayerContext.ts +0 -0
package/src/head/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "@alepha/react";
|
|
1
|
+
import { AlephaReact } from "@alepha/react";
|
|
2
|
+
import type {
|
|
3
|
+
PageConfigSchema,
|
|
4
|
+
TPropsDefault,
|
|
5
|
+
TPropsParentDefault,
|
|
6
|
+
} from "@alepha/react/router";
|
|
7
7
|
import { $module } from "alepha";
|
|
8
8
|
import { $head } from "./primitives/$head.ts";
|
|
9
9
|
import type { Head } from "./interfaces/Head.ts";
|
|
@@ -20,7 +20,8 @@ export * from "./providers/ServerHeadProvider.ts";
|
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Augment PagePrimitiveOptions in router module
|
|
24
|
+
declare module "@alepha/react/router" {
|
|
24
25
|
interface PagePrimitiveOptions<
|
|
25
26
|
TConfig extends PageConfigSchema = PageConfigSchema,
|
|
26
27
|
TProps extends object = TPropsDefault,
|
|
@@ -28,7 +29,10 @@ declare module "@alepha/react" {
|
|
|
28
29
|
> {
|
|
29
30
|
head?: Head | ((props: TProps, previous?: Head) => Head);
|
|
30
31
|
}
|
|
32
|
+
}
|
|
31
33
|
|
|
34
|
+
// Augment ReactRouterState in router module
|
|
35
|
+
declare module "@alepha/react/router" {
|
|
32
36
|
interface ReactRouterState {
|
|
33
37
|
head: Head;
|
|
34
38
|
}
|
|
@@ -39,6 +43,11 @@ declare module "@alepha/react" {
|
|
|
39
43
|
/**
|
|
40
44
|
* Fill `<head>` server & client side.
|
|
41
45
|
*
|
|
46
|
+
* Generate SEO-friendly meta tags and titles for your React application using AlephaReactHead module.
|
|
47
|
+
*
|
|
48
|
+
* This module provides services and primitives to manage the document head both on the server and client side,
|
|
49
|
+
* ensuring that your application is optimized for search engines and social media sharing.
|
|
50
|
+
*
|
|
42
51
|
* @see {@link ServerHeadProvider}
|
|
43
52
|
* @module alepha.react.head
|
|
44
53
|
*/
|
|
@@ -73,7 +73,10 @@ export interface SimpleHead {
|
|
|
73
73
|
bodyAttributes?: Record<string, string>;
|
|
74
74
|
/** Meta tags - supports both name and property attributes */
|
|
75
75
|
meta?: Array<HeadMeta>;
|
|
76
|
+
/** Link tags (e.g., stylesheets, preload, canonical) */
|
|
76
77
|
link?: Array<{ rel: string; href: string }>;
|
|
78
|
+
/** Script tags - any valid script attributes (src, type, async, defer, etc.) */
|
|
79
|
+
script?: Array<Record<string, string | boolean>>;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
export interface HeadMeta {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageRoute, ReactRouterState } from "@alepha/react";
|
|
1
|
+
import type { PageRoute, ReactRouterState } from "@alepha/react/router";
|
|
2
2
|
import { $inject } from "alepha";
|
|
3
3
|
import { SeoExpander } from "../helpers/SeoExpander.ts";
|
|
4
4
|
import type { Head } from "../interfaces/Head.ts";
|
|
@@ -33,6 +33,7 @@ export class HeadProvider {
|
|
|
33
33
|
...head,
|
|
34
34
|
meta: [...(state.head.meta ?? []), ...meta, ...(head.meta ?? [])],
|
|
35
35
|
link: [...(state.head.link ?? []), ...link, ...(head.link ?? [])],
|
|
36
|
+
script: [...(state.head.script ?? []), ...(head.script ?? [])],
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -90,5 +91,9 @@ export class HeadProvider {
|
|
|
90
91
|
if (head.link) {
|
|
91
92
|
state.head.link = [...(state.head.link ?? []), ...(head.link ?? [])];
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
if (head.script) {
|
|
96
|
+
state.head.script = [...(state.head.script ?? []), ...(head.script ?? [])];
|
|
97
|
+
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -68,6 +68,12 @@ export class ServerHeadProvider {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
if (head.script) {
|
|
72
|
+
for (const script of head.script) {
|
|
73
|
+
headContent += this.renderScriptTag(script);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
// Inject into <head>...</head>
|
|
72
78
|
result = result.replace(
|
|
73
79
|
/<head([^>]*)>(.*?)<\/head>/is,
|
|
@@ -124,4 +130,18 @@ export class ServerHeadProvider {
|
|
|
124
130
|
}
|
|
125
131
|
return "";
|
|
126
132
|
}
|
|
133
|
+
|
|
134
|
+
protected renderScriptTag(script: Record<string, string | boolean>): string {
|
|
135
|
+
const attrs = Object.entries(script)
|
|
136
|
+
.filter(([, value]) => value !== false)
|
|
137
|
+
.map(([key, value]) => {
|
|
138
|
+
// Boolean attributes - render without value if true
|
|
139
|
+
if (value === true) {
|
|
140
|
+
return key;
|
|
141
|
+
}
|
|
142
|
+
return `${key}="${this.escapeHtml(String(value))}"`;
|
|
143
|
+
})
|
|
144
|
+
.join(" ");
|
|
145
|
+
return `<script ${attrs}></script>\n`;
|
|
146
|
+
}
|
|
127
147
|
}
|
|
@@ -47,6 +47,8 @@ const ErrorViewer = ({ error, alepha }: ErrorViewerProps) => {
|
|
|
47
47
|
|
|
48
48
|
export default ErrorViewer;
|
|
49
49
|
|
|
50
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
51
|
+
|
|
50
52
|
/**
|
|
51
53
|
* Parse stack trace string into structured frames
|
|
52
54
|
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AnchorHTMLAttributes, createElement } from "react";
|
|
2
|
+
import { useRouter } from "../hooks/useRouter.ts";
|
|
3
|
+
|
|
4
|
+
export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
5
|
+
href: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Link component for client-side navigation.
|
|
10
|
+
*
|
|
11
|
+
* It's a simple wrapper around an anchor (`<a>`) element using the `useRouter` hook.
|
|
12
|
+
*/
|
|
13
|
+
const Link = (props: LinkProps) => {
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
|
|
16
|
+
return createElement(
|
|
17
|
+
"a", { ...props, ...router.anchor(props.href) }, props.children
|
|
18
|
+
)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default Link;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { memo, type ReactNode, use, useRef, useState } from "react";
|
|
2
|
+
import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
|
|
2
3
|
import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
|
|
3
|
-
import type { ErrorHandler, PageAnimation } from "../primitives/$page.ts";
|
|
4
4
|
import { Redirection } from "../errors/Redirection.ts";
|
|
5
|
-
import { useEvents } from "../hooks/useEvents.ts";
|
|
6
5
|
import { useRouterState } from "../hooks/useRouterState.ts";
|
|
7
|
-
import type {
|
|
8
|
-
import ErrorBoundary from "./ErrorBoundary.tsx";
|
|
6
|
+
import type { PageAnimation } from "../primitives/$page.ts";
|
|
9
7
|
import ErrorViewer from "./ErrorViewer.tsx";
|
|
10
|
-
import { useAlepha } from "
|
|
8
|
+
import { ErrorBoundary, useAlepha, useEvents } from "@alepha/react";
|
|
11
9
|
|
|
12
10
|
export interface NestedViewProps {
|
|
13
11
|
children?: ReactNode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default 404 Not Found page component.
|
|
5
|
+
*/
|
|
6
|
+
const NotFound = (props: { style?: CSSProperties }) => (
|
|
7
|
+
<div
|
|
8
|
+
style={{
|
|
9
|
+
width: "100%",
|
|
10
|
+
minHeight: "90vh",
|
|
11
|
+
boxSizing: "border-box",
|
|
12
|
+
display: "flex",
|
|
13
|
+
flexDirection: "column",
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
textAlign: "center",
|
|
17
|
+
fontFamily:
|
|
18
|
+
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
19
|
+
padding: "2rem",
|
|
20
|
+
...props.style,
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<div style={{ fontSize: "6rem", fontWeight: 200, lineHeight: 1 }}>404</div>
|
|
24
|
+
<div style={{ fontSize: "0.875rem", marginTop: "1rem", opacity: 0.6 }}>
|
|
25
|
+
Page not found
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
export default NotFound;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AlephaError } from "alepha";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Used for Redirection during the page loading.
|
|
5
|
+
*
|
|
6
|
+
* Depends on the context, it can be thrown or just returned.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { Redirection } from "@alepha/react";
|
|
11
|
+
*
|
|
12
|
+
* const MyPage = $page({
|
|
13
|
+
* resolve: async () => {
|
|
14
|
+
* if (needRedirect) {
|
|
15
|
+
* throw new Redirection("/new-path");
|
|
16
|
+
* }
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class Redirection extends AlephaError {
|
|
22
|
+
public readonly redirect: string;
|
|
23
|
+
|
|
24
|
+
constructor(redirect: string) {
|
|
25
|
+
super("Redirection");
|
|
26
|
+
this.redirect = redirect;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -8,11 +8,15 @@ export interface UseActiveOptions {
|
|
|
8
8
|
startWith?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Hook to determine if a given route is active and to provide anchor props for navigation.
|
|
13
|
+
* This hook refreshes on router state changes.
|
|
14
|
+
*/
|
|
11
15
|
export const useActive = (args: string | UseActiveOptions): UseActiveHook => {
|
|
16
|
+
useRouterState();
|
|
17
|
+
|
|
12
18
|
const router = useRouter();
|
|
13
19
|
const [isPending, setPending] = useState(false);
|
|
14
|
-
const state = useRouterState();
|
|
15
|
-
const current = state.url.pathname;
|
|
16
20
|
|
|
17
21
|
const options: UseActiveOptions =
|
|
18
22
|
typeof args === "string" ? { href: args } : { ...args, href: args.href };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Alepha, Static, TObject } from "alepha";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { useAlepha } from "
|
|
3
|
+
import { useAlepha } from "@alepha/react";
|
|
4
4
|
import { useRouter } from "./useRouter.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Hook to manage query parameters in the URL using a defined schema.
|
|
8
8
|
*/
|
|
9
9
|
export const useQueryParams = <T extends TObject>(
|
|
10
10
|
schema: T,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AlephaError } from "alepha";
|
|
2
2
|
import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
|
|
3
|
-
import { useStore } from "
|
|
3
|
+
import { useStore } from "@alepha/react";
|
|
4
4
|
|
|
5
5
|
export const useRouterState = (): ReactRouterState => {
|
|
6
6
|
const [state] = useStore("alepha.react.router.state");
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { $module } from "alepha";
|
|
2
|
-
import { AlephaDateTime } from "alepha/datetime";
|
|
3
|
-
import { AlephaServer } from "alepha/server";
|
|
4
|
-
import { AlephaServerLinks } from "alepha/server/links";
|
|
5
2
|
import { $page } from "./primitives/$page.ts";
|
|
6
|
-
import {
|
|
3
|
+
import { ReactRouter } from "./services/ReactRouter.ts";
|
|
7
4
|
import { ReactBrowserRendererProvider } from "./providers/ReactBrowserRendererProvider.ts";
|
|
8
5
|
import { ReactBrowserRouterProvider } from "./providers/ReactBrowserRouterProvider.ts";
|
|
9
|
-
import { ReactPageProvider } from "./providers/ReactPageProvider.ts";
|
|
10
6
|
import { ReactPageService } from "./services/ReactPageService.ts";
|
|
11
|
-
import {
|
|
7
|
+
import { ReactPageProvider } from "./providers/ReactPageProvider.ts";
|
|
8
|
+
import { ReactBrowserProvider } from "./providers/ReactBrowserProvider.ts";
|
|
9
|
+
import { AlephaDateTime } from "alepha/datetime";
|
|
10
|
+
import { AlephaServer } from "alepha/server";
|
|
11
|
+
import { AlephaServerLinks } from "alepha/server/links";
|
|
12
|
+
import { AlephaReact } from "@alepha/react";
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
14
15
|
|
|
15
16
|
export * from "./index.shared.ts";
|
|
16
|
-
export * from "./
|
|
17
|
-
export * from "./providers/
|
|
18
|
-
export * from "./providers/
|
|
19
|
-
export * from "./providers/ReactPageProvider.ts";
|
|
17
|
+
export * from "./providers/ReactBrowserProvider.ts"
|
|
18
|
+
export * from "./providers/ReactBrowserRouterProvider.ts"
|
|
19
|
+
export * from "./providers/ReactBrowserRendererProvider.ts"
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
22
22
|
|
|
23
|
-
export const
|
|
24
|
-
name: "alepha.react",
|
|
23
|
+
export const AlephaReactRouter = $module({
|
|
24
|
+
name: "alepha.react.router",
|
|
25
25
|
primitives: [$page],
|
|
26
26
|
services: [
|
|
27
27
|
ReactPageProvider,
|
|
@@ -33,6 +33,7 @@ export const AlephaReact = $module({
|
|
|
33
33
|
],
|
|
34
34
|
register: (alepha) =>
|
|
35
35
|
alepha
|
|
36
|
+
.with(AlephaReact)
|
|
36
37
|
.with(AlephaDateTime)
|
|
37
38
|
.with(AlephaServer)
|
|
38
39
|
.with(AlephaServerLinks)
|
|
@@ -42,3 +43,4 @@ export const AlephaReact = $module({
|
|
|
42
43
|
.with(ReactBrowserRendererProvider)
|
|
43
44
|
.with(ReactRouter),
|
|
44
45
|
});
|
|
46
|
+
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { default as ClientOnly } from "./components/ClientOnly.tsx";
|
|
2
|
-
export { default as ErrorBoundary } from "./components/ErrorBoundary.tsx";
|
|
3
1
|
export { default as ErrorViewer } from "./components/ErrorViewer.tsx";
|
|
2
|
+
export type * from "./components/ErrorViewer.tsx";
|
|
4
3
|
export { default as Link, type LinkProps } from "./components/Link.tsx";
|
|
4
|
+
export type * from "./components/Link.tsx";
|
|
5
5
|
export { default as NestedView } from "./components/NestedView.tsx";
|
|
6
|
+
export type * from "./components/NestedView.tsx";
|
|
6
7
|
export { default as NotFound } from "./components/NotFound.tsx";
|
|
8
|
+
export type * from "./components/NotFound.tsx";
|
|
7
9
|
export * from "./contexts/RouterLayerContext.ts";
|
|
8
10
|
export * from "./primitives/$page.ts";
|
|
9
11
|
export * from "./errors/Redirection.ts";
|
|
@@ -11,5 +13,6 @@ export * from "./hooks/useActive.ts";
|
|
|
11
13
|
export * from "./hooks/useQueryParams.ts";
|
|
12
14
|
export * from "./hooks/useRouter.ts";
|
|
13
15
|
export * from "./hooks/useRouterState.ts";
|
|
14
|
-
export * from "./hooks/useSchema.ts";
|
|
15
16
|
export * from "./services/ReactRouter.ts";
|
|
17
|
+
export * from "./services/ReactPageService.ts"
|
|
18
|
+
export * from "./providers/ReactPageProvider.ts";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { AlephaReact } from "@alepha/react";
|
|
2
|
+
import { $module } from "alepha";
|
|
3
|
+
import { $page, type PageAnimation } from "./primitives/$page.ts";
|
|
4
|
+
import { ReactRouter } from "./services/ReactRouter.ts";
|
|
5
|
+
import { ReactPageProvider, type ReactRouterState } from "./providers/ReactPageProvider.ts";
|
|
6
|
+
import { AlephaServer, type ServerRequest } from "alepha/server";
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import type { ReactHydrationState } from "./providers/ReactBrowserProvider.ts";
|
|
9
|
+
import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
10
|
+
import { ReactPageServerService } from "./services/ReactPageServerService.ts";
|
|
11
|
+
import { AlephaServerCache } from "alepha/server/cache";
|
|
12
|
+
import { AlephaServerLinks } from "alepha/server/links";
|
|
13
|
+
import { ReactPageService } from "./services/ReactPageService.ts";
|
|
14
|
+
import { AlephaDateTime } from "alepha/datetime";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export * from "./index.shared.ts";
|
|
19
|
+
export * from "./providers/ReactPageProvider.ts";
|
|
20
|
+
export * from "./providers/ReactBrowserProvider.ts";
|
|
21
|
+
export * from "./providers/ReactServerProvider.ts";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
declare module "alepha" {
|
|
26
|
+
interface State {
|
|
27
|
+
"alepha.react.router.state"?: ReactRouterState;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Hooks {
|
|
31
|
+
/**
|
|
32
|
+
* Fires when the React application is starting to be rendered on the server.
|
|
33
|
+
*/
|
|
34
|
+
"react:server:render:begin": {
|
|
35
|
+
request?: ServerRequest;
|
|
36
|
+
state: ReactRouterState;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Fires when the React application has been rendered on the server.
|
|
40
|
+
*/
|
|
41
|
+
"react:server:render:end": {
|
|
42
|
+
request?: ServerRequest;
|
|
43
|
+
state: ReactRouterState;
|
|
44
|
+
html: string;
|
|
45
|
+
};
|
|
46
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Fires when the React application is being rendered on the browser.
|
|
49
|
+
*/
|
|
50
|
+
"react:browser:render": {
|
|
51
|
+
root: HTMLElement;
|
|
52
|
+
element: ReactNode;
|
|
53
|
+
state: ReactRouterState;
|
|
54
|
+
hydration?: ReactHydrationState;
|
|
55
|
+
};
|
|
56
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
57
|
+
// SPECIFIC: Route transitions
|
|
58
|
+
/**
|
|
59
|
+
* Fires when a route transition is starting.
|
|
60
|
+
*/
|
|
61
|
+
"react:transition:begin": {
|
|
62
|
+
previous: ReactRouterState;
|
|
63
|
+
state: ReactRouterState;
|
|
64
|
+
animation?: PageAnimation;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Fires when a route transition has succeeded.
|
|
68
|
+
*/
|
|
69
|
+
"react:transition:success": {
|
|
70
|
+
state: ReactRouterState;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Fires when a route transition has failed.
|
|
74
|
+
*/
|
|
75
|
+
"react:transition:error": {
|
|
76
|
+
state: ReactRouterState;
|
|
77
|
+
error: Error;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Fires when a route transition has completed, regardless of success or failure.
|
|
81
|
+
*/
|
|
82
|
+
"react:transition:end": {
|
|
83
|
+
state: ReactRouterState;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Provides declarative routing with the `$page` primitive for building type-safe React routes.
|
|
92
|
+
*
|
|
93
|
+
* This module enables:
|
|
94
|
+
* - URL pattern matching with parameters (e.g., `/users/:id`)
|
|
95
|
+
* - Nested routing with parent-child relationships
|
|
96
|
+
* - Type-safe URL parameter and query string validation
|
|
97
|
+
* - Server-side data fetching with the `resolve` function
|
|
98
|
+
* - Lazy loading and code splitting
|
|
99
|
+
* - Page animations and error handling
|
|
100
|
+
*
|
|
101
|
+
* @see {@link $page}
|
|
102
|
+
* @module alepha.react.router
|
|
103
|
+
*/
|
|
104
|
+
export const AlephaReactRouter = $module({
|
|
105
|
+
name: "alepha.react.router",
|
|
106
|
+
primitives: [$page],
|
|
107
|
+
services: [
|
|
108
|
+
ReactPageProvider,
|
|
109
|
+
ReactPageService,
|
|
110
|
+
ReactRouter, ReactServerProvider, ReactPageServerService],
|
|
111
|
+
register: (alepha) =>
|
|
112
|
+
alepha
|
|
113
|
+
.with(AlephaReact)
|
|
114
|
+
.with(AlephaDateTime)
|
|
115
|
+
.with(AlephaServer)
|
|
116
|
+
.with(AlephaServerCache)
|
|
117
|
+
.with(AlephaServerLinks)
|
|
118
|
+
.with({
|
|
119
|
+
provide: ReactPageService,
|
|
120
|
+
use: ReactPageServerService,
|
|
121
|
+
})
|
|
122
|
+
.with(ReactServerProvider)
|
|
123
|
+
.with(ReactPageProvider)
|
|
124
|
+
.with(ReactRouter),
|
|
125
|
+
});
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
import type { ServerRequest } from "alepha/server";
|
|
11
11
|
import type { ServerRouteCache } from "alepha/server/cache";
|
|
12
12
|
import type { FC, ReactNode } from "react";
|
|
13
|
-
import type { ClientOnlyProps } from "../components/ClientOnly.tsx";
|
|
14
13
|
import type { Redirection } from "../errors/Redirection.ts";
|
|
15
14
|
import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
|
|
16
15
|
import { ReactPageService } from "../services/ReactPageService.ts";
|
|
16
|
+
import type { ClientOnlyProps } from "@alepha/react";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Main primitive for defining a React route in the application.
|
|
@@ -18,6 +18,9 @@ import type {
|
|
|
18
18
|
ReactRouterState,
|
|
19
19
|
TransitionOptions,
|
|
20
20
|
} from "./ReactPageProvider.ts";
|
|
21
|
+
import type { RouterGoOptions } from "../services/ReactRouter.ts";
|
|
22
|
+
|
|
23
|
+
export type { RouterGoOptions } from "../services/ReactRouter.ts";
|
|
21
24
|
|
|
22
25
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
23
26
|
|
|
@@ -297,19 +300,6 @@ export class ReactBrowserProvider {
|
|
|
297
300
|
|
|
298
301
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
299
302
|
|
|
300
|
-
export interface RouterGoOptions {
|
|
301
|
-
replace?: boolean;
|
|
302
|
-
match?: TransitionOptions;
|
|
303
|
-
params?: Record<string, string>;
|
|
304
|
-
query?: Record<string, string>;
|
|
305
|
-
meta?: Record<string, any>;
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Recreate the whole page, ignoring the current state.
|
|
309
|
-
*/
|
|
310
|
-
force?: boolean;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
303
|
export type ReactHydrationState = {
|
|
314
304
|
layers?: Array<PreviousLayerData>;
|
|
315
305
|
} & {
|
|
@@ -2,6 +2,9 @@ import { $hook } from "alepha";
|
|
|
2
2
|
import { $logger } from "alepha/logger";
|
|
3
3
|
import { createRoot, hydrateRoot, type Root } from "react-dom/client";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Browser specific React renderer (react-dom/client interface)
|
|
7
|
+
*/
|
|
5
8
|
export class ReactBrowserRendererProvider {
|
|
6
9
|
protected readonly log = $logger();
|
|
7
10
|
protected root?: Root;
|
|
@@ -16,6 +16,9 @@ export interface BrowserRoute extends Route {
|
|
|
16
16
|
page: PageRoute;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Implementation of AlephaRouter for React in browser environment.
|
|
21
|
+
*/
|
|
19
22
|
export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
|
|
20
23
|
protected readonly log = $logger();
|
|
21
24
|
protected readonly alepha = $inject(Alepha);
|
|
@@ -10,19 +10,18 @@ import {
|
|
|
10
10
|
} from "alepha";
|
|
11
11
|
import { $logger } from "alepha/logger";
|
|
12
12
|
import { createElement, type ReactNode, StrictMode } from "react";
|
|
13
|
-
import ClientOnly from "
|
|
13
|
+
import { AlephaContext, ClientOnly } from "@alepha/react";
|
|
14
14
|
import ErrorViewer from "../components/ErrorViewer.tsx";
|
|
15
15
|
import NestedView from "../components/NestedView.tsx";
|
|
16
16
|
import NotFoundPage from "../components/NotFound.tsx";
|
|
17
|
-
import { AlephaContext } from "../contexts/AlephaContext.ts";
|
|
18
17
|
import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
|
|
18
|
+
import { Redirection } from "../errors/Redirection.ts";
|
|
19
19
|
import {
|
|
20
20
|
$page,
|
|
21
21
|
type ErrorHandler,
|
|
22
22
|
type PagePrimitive,
|
|
23
23
|
type PagePrimitiveOptions,
|
|
24
24
|
} from "../primitives/$page.ts";
|
|
25
|
-
import { Redirection } from "../errors/Redirection.ts";
|
|
26
25
|
|
|
27
26
|
const envSchema = t.object({
|
|
28
27
|
REACT_STRICT_MODE: t.boolean({ default: true }),
|
|
@@ -32,6 +31,9 @@ declare module "alepha" {
|
|
|
32
31
|
export interface Env extends Partial<Static<typeof envSchema>> {}
|
|
33
32
|
}
|
|
34
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Handle page routes for React applications. (Browser and Server)
|
|
36
|
+
*/
|
|
35
37
|
export class ReactPageProvider {
|
|
36
38
|
protected readonly log = $logger();
|
|
37
39
|
protected readonly env = $env(envSchema);
|
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
$atom,
|
|
5
|
-
$env,
|
|
6
|
-
$hook,
|
|
7
|
-
$inject,
|
|
8
|
-
$use,
|
|
9
|
-
Alepha,
|
|
10
|
-
AlephaError,
|
|
11
|
-
type Static,
|
|
12
|
-
t,
|
|
13
|
-
} from "alepha";
|
|
3
|
+
import { $atom, $env, $hook, $inject, $use, Alepha, AlephaError, type Static, t, } from "alepha";
|
|
14
4
|
import { $logger } from "alepha/logger";
|
|
15
|
-
import {
|
|
16
|
-
type ServerHandler,
|
|
17
|
-
ServerProvider,
|
|
18
|
-
ServerRouterProvider,
|
|
19
|
-
ServerTimingProvider,
|
|
20
|
-
} from "alepha/server";
|
|
5
|
+
import { type ServerHandler, ServerRouterProvider, ServerTimingProvider, } from "alepha/server";
|
|
21
6
|
import { ServerLinksProvider } from "alepha/server/links";
|
|
22
7
|
import { ServerStaticProvider } from "alepha/server/static";
|
|
23
8
|
import { renderToString } from "react-dom/server";
|
|
24
|
-
import {
|
|
25
|
-
$page,
|
|
26
|
-
type PagePrimitiveRenderOptions,
|
|
27
|
-
type PagePrimitiveRenderResult,
|
|
28
|
-
} from "../primitives/$page.ts";
|
|
29
9
|
import { Redirection } from "../errors/Redirection.ts";
|
|
10
|
+
import { $page, type PagePrimitiveRenderOptions, type PagePrimitiveRenderResult, } from "../primitives/$page.ts";
|
|
30
11
|
import type { ReactHydrationState } from "./ReactBrowserProvider.ts";
|
|
31
|
-
import {
|
|
32
|
-
type PageRoute,
|
|
33
|
-
ReactPageProvider,
|
|
34
|
-
type ReactRouterState,
|
|
35
|
-
} from "./ReactPageProvider.ts";
|
|
12
|
+
import { type PageRoute, ReactPageProvider, type ReactRouterState, } from "./ReactPageProvider.ts";
|
|
36
13
|
|
|
37
14
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
38
15
|
|
|
@@ -84,12 +61,16 @@ declare module "alepha" {
|
|
|
84
61
|
|
|
85
62
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
86
63
|
|
|
64
|
+
/**
|
|
65
|
+
* React server provider responsible for SSR and static file serving.
|
|
66
|
+
*
|
|
67
|
+
* Use `react-dom/server` under the hood.
|
|
68
|
+
*/
|
|
87
69
|
export class ReactServerProvider {
|
|
88
70
|
protected readonly log = $logger();
|
|
89
71
|
protected readonly alepha = $inject(Alepha);
|
|
90
72
|
protected readonly env = $env(envSchema);
|
|
91
73
|
protected readonly pageApi = $inject(ReactPageProvider);
|
|
92
|
-
protected readonly serverProvider = $inject(ServerProvider);
|
|
93
74
|
protected readonly serverStaticProvider = $inject(ServerStaticProvider);
|
|
94
75
|
protected readonly serverRouterProvider = $inject(ServerRouterProvider);
|
|
95
76
|
protected readonly serverTimingProvider = $inject(ServerTimingProvider);
|
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
import { ReactServerProvider } from "../providers/ReactServerProvider.ts";
|
|
8
8
|
import { ReactPageService } from "./ReactPageService.ts";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* $page methods for server-side.
|
|
12
|
+
*/
|
|
10
13
|
export class ReactPageServerService extends ReactPageService {
|
|
11
14
|
protected readonly reactServerProvider = $inject(ReactServerProvider);
|
|
12
15
|
protected readonly serverProvider = $inject(ServerProvider);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AlephaError } from "alepha";
|
|
2
|
-
import type {
|
|
3
|
-
PagePrimitiveRenderOptions,
|
|
4
|
-
PagePrimitiveRenderResult,
|
|
5
|
-
} from "../primitives/$page.ts";
|
|
2
|
+
import type { PagePrimitiveRenderOptions, PagePrimitiveRenderResult, } from "../../router/primitives/$page.ts";
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* $page methods interface.
|
|
6
|
+
*/
|
|
7
|
+
export abstract class ReactPageService {
|
|
8
8
|
public fetch(
|
|
9
9
|
pathname: string,
|
|
10
10
|
options: PagePrimitiveRenderOptions = {},
|