@lazarv/react-server 0.0.0-experimental-43e79e6-20230928
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/LICENSE +21 -0
- package/README.md +5 -0
- package/bin/cli.mjs +93 -0
- package/bin/commands/build.mjs +19 -0
- package/bin/commands/dev.mjs +23 -0
- package/bin/commands/start.mjs +16 -0
- package/bin/loader.mjs +38 -0
- package/client/ActionState.mjs +16 -0
- package/client/ClientOnly.jsx +14 -0
- package/client/ClientProvider.jsx +243 -0
- package/client/ErrorBoundary.jsx +45 -0
- package/client/FlightContext.mjs +3 -0
- package/client/Link.jsx +59 -0
- package/client/Params.mjs +15 -0
- package/client/ReactServerComponent.jsx +77 -0
- package/client/Refresh.jsx +52 -0
- package/client/components.mjs +28 -0
- package/client/context.mjs +6 -0
- package/client/entry.client.jsx +146 -0
- package/client/index.jsx +6 -0
- package/client/navigation.jsx +4 -0
- package/config/context.mjs +37 -0
- package/config/index.mjs +114 -0
- package/lib/build/action.mjs +57 -0
- package/lib/build/banner.mjs +13 -0
- package/lib/build/chunks.mjs +26 -0
- package/lib/build/client.mjs +114 -0
- package/lib/build/custom-logger.mjs +13 -0
- package/lib/build/dependencies.mjs +54 -0
- package/lib/build/resolve.mjs +101 -0
- package/lib/build/server.mjs +142 -0
- package/lib/build/static.mjs +89 -0
- package/lib/dev/action.mjs +63 -0
- package/lib/dev/create-logger.mjs +52 -0
- package/lib/dev/create-server.mjs +208 -0
- package/lib/dev/modules.mjs +20 -0
- package/lib/dev/ssr-handler.mjs +135 -0
- package/lib/handlers/error.mjs +153 -0
- package/lib/handlers/not-found.mjs +5 -0
- package/lib/handlers/redirect.mjs +1 -0
- package/lib/handlers/rewrite.mjs +1 -0
- package/lib/handlers/static.mjs +120 -0
- package/lib/handlers/trailing-slash.mjs +12 -0
- package/lib/plugins/react-server.mjs +73 -0
- package/lib/plugins/use-client.mjs +135 -0
- package/lib/plugins/use-server.mjs +175 -0
- package/lib/start/action.mjs +110 -0
- package/lib/start/create-server.mjs +111 -0
- package/lib/start/manifest.mjs +104 -0
- package/lib/start/ssr-handler.mjs +134 -0
- package/lib/sys.mjs +49 -0
- package/lib/utils/merge.mjs +31 -0
- package/lib/utils/server-address.mjs +14 -0
- package/memory-cache/index.mjs +125 -0
- package/package.json +81 -0
- package/react-server.d.ts +209 -0
- package/server/ErrorBoundary.jsx +14 -0
- package/server/RemoteComponent.jsx +210 -0
- package/server/Route.jsx +108 -0
- package/server/actions.mjs +72 -0
- package/server/cache.mjs +19 -0
- package/server/client-component.mjs +62 -0
- package/server/context.mjs +32 -0
- package/server/cookies.mjs +14 -0
- package/server/entry.server.jsx +972 -0
- package/server/error-boundary.jsx +2 -0
- package/server/http-headers.mjs +8 -0
- package/server/http-status.mjs +6 -0
- package/server/index.mjs +14 -0
- package/server/logger.mjs +15 -0
- package/server/module-loader.mjs +20 -0
- package/server/redirects.mjs +45 -0
- package/server/remote-component.jsx +2 -0
- package/server/request.mjs +37 -0
- package/server/revalidate.mjs +22 -0
- package/server/rewrites.mjs +0 -0
- package/server/router.jsx +6 -0
- package/server/runtime.mjs +32 -0
- package/server/symbols.mjs +24 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
declare module "@lazarv/react-server" {
|
|
2
|
+
import type { RequestContextExtensions } from "@hattip/compose";
|
|
3
|
+
import type { CookieSerializeOptions } from "@hattip/cookie";
|
|
4
|
+
|
|
5
|
+
export function server$<T extends (...args: any[]) => any>(action: T): T;
|
|
6
|
+
export function cache$<T extends React.FC>(
|
|
7
|
+
Component: T,
|
|
8
|
+
ttl?: number | true
|
|
9
|
+
): T;
|
|
10
|
+
export function redirect(url: string, status?: number): void;
|
|
11
|
+
|
|
12
|
+
export function useRequest(): Request;
|
|
13
|
+
export function useResponse(): Response;
|
|
14
|
+
export function useUrl(): URL;
|
|
15
|
+
export function useFormData(): FormData;
|
|
16
|
+
export function rewrite(pathname: string): void;
|
|
17
|
+
|
|
18
|
+
export function revalidate(key?: string): void;
|
|
19
|
+
export function status(status?: number, statusText?: string): void;
|
|
20
|
+
export function headers(headers?: Record<string, string>): void;
|
|
21
|
+
|
|
22
|
+
export type Cookies = RequestContextExtensions["cookie"];
|
|
23
|
+
export function cookie(): Cookies;
|
|
24
|
+
export function setCookie(
|
|
25
|
+
name: string,
|
|
26
|
+
value: string,
|
|
27
|
+
options?: CookieSerializeOptions
|
|
28
|
+
): void;
|
|
29
|
+
export function deleteCookie(
|
|
30
|
+
name: string,
|
|
31
|
+
options?: CookieSerializeOptions
|
|
32
|
+
): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare module "@lazarv/react-server/client" {
|
|
36
|
+
export type ReactServerClientContext = {
|
|
37
|
+
registerOutlet(outlet: string, url: string): void;
|
|
38
|
+
refresh(outlet?: string): Promise<void>;
|
|
39
|
+
prefetch(
|
|
40
|
+
url: string,
|
|
41
|
+
options?: { outlet?: string; ttl?: number }
|
|
42
|
+
): Promise<void>;
|
|
43
|
+
navigate(
|
|
44
|
+
url: string,
|
|
45
|
+
options?: { outlet?: string; push?: boolean; rollback?: number }
|
|
46
|
+
): Promise<void>;
|
|
47
|
+
replace(
|
|
48
|
+
url: string,
|
|
49
|
+
options?: { outlet?: string; rollback?: number }
|
|
50
|
+
): Promise<void>;
|
|
51
|
+
subscribe(
|
|
52
|
+
url: string,
|
|
53
|
+
listener: (
|
|
54
|
+
to: string,
|
|
55
|
+
done?: (err: unknown | null, result: unknown) => void
|
|
56
|
+
) => void
|
|
57
|
+
): () => void;
|
|
58
|
+
getFlightResponse(): Promise<Response | null>;
|
|
59
|
+
};
|
|
60
|
+
export function useClient(): ReactServerClientContext;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
declare module "@lazarv/react-server/config" {
|
|
64
|
+
export type ReactServerConfig = any;
|
|
65
|
+
export function loadConfig<T extends Record<string, unknown>>(
|
|
66
|
+
initialConfig: T
|
|
67
|
+
): Promise<ReactServerConfig>;
|
|
68
|
+
export function defineConfig<T extends Record<string, unknown>>(
|
|
69
|
+
config: T
|
|
70
|
+
): ReactServerConfig;
|
|
71
|
+
|
|
72
|
+
export function forRoot(config?: ReactServerConfig): ReactServerConfig;
|
|
73
|
+
export function forChild(config?: ReactServerConfig): ReactServerConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare module "@lazarv/react-server/error-boundary" {
|
|
77
|
+
import type { ErrorBoundaryProps } from "react-error-boundary";
|
|
78
|
+
|
|
79
|
+
export type ReactServerErrorBoundaryProps = React.PropsWithChildren<
|
|
80
|
+
Omit<ErrorBoundaryProps, "fallback"> & { fallback: React.ReactNode }
|
|
81
|
+
>;
|
|
82
|
+
const ErrorBoundary: React.FC<ReactServerErrorBoundaryProps>;
|
|
83
|
+
export default ErrorBoundary;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare module "@lazarv/react-server/memory-cache" {
|
|
87
|
+
export interface ReactServerCache {
|
|
88
|
+
get<T = unknown>(keys: string[]): Promise<T | undefined>;
|
|
89
|
+
set<T = unknown>(keys: string[], value: T): Promise<void>;
|
|
90
|
+
has(keys: string[]): Promise<boolean>;
|
|
91
|
+
setExpiry(keys: string[], ttl: number): Promise<void>;
|
|
92
|
+
hasExpiry(keys: string[], ttl: number): Promise<boolean>;
|
|
93
|
+
delete(keys: string[]): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class MemoryCache implements ReactServerCache {
|
|
97
|
+
get<T = unknown>(keys: string[]): Promise<T | undefined>;
|
|
98
|
+
set<T = unknown>(keys: string[], value: T): Promise<void>;
|
|
99
|
+
has(keys: string[]): Promise<boolean>;
|
|
100
|
+
setExpiry(keys: string[], ttl: number): Promise<void>;
|
|
101
|
+
hasExpiry(keys: string[], ttl: number): Promise<boolean>;
|
|
102
|
+
delete(keys: string[]): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function useCache<T>(
|
|
106
|
+
keys: string[],
|
|
107
|
+
value: (() => Promise<T>) | T,
|
|
108
|
+
ttl?: number,
|
|
109
|
+
force?: boolean
|
|
110
|
+
): Promise<T>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
declare module "@lazarv/react-server/navigation" {
|
|
114
|
+
import type { ReactServerRouting } from "@lazarv/react-server/router";
|
|
115
|
+
|
|
116
|
+
export const Link: React.FC<
|
|
117
|
+
React.PropsWithChildren<{
|
|
118
|
+
to: ReactServerRouting["path"];
|
|
119
|
+
target?: string;
|
|
120
|
+
transition?: boolean;
|
|
121
|
+
push?: boolean;
|
|
122
|
+
replace?: boolean;
|
|
123
|
+
prefetch?: boolean;
|
|
124
|
+
ttl?: number;
|
|
125
|
+
rollback?: number;
|
|
126
|
+
onNavigate?: () => void;
|
|
127
|
+
onError?: (error: unknown) => void;
|
|
128
|
+
}> &
|
|
129
|
+
React.DetailedHTMLProps<
|
|
130
|
+
React.HTMLAttributes<HTMLAnchorElement>,
|
|
131
|
+
HTMLAnchorElement
|
|
132
|
+
>
|
|
133
|
+
>;
|
|
134
|
+
export const Refresh: React.FC<
|
|
135
|
+
React.PropsWithChildren<{
|
|
136
|
+
url?: string;
|
|
137
|
+
outlet?: string;
|
|
138
|
+
transition?: boolean;
|
|
139
|
+
prefetch?: boolean;
|
|
140
|
+
ttl?: number;
|
|
141
|
+
onRefresh?: () => void;
|
|
142
|
+
onError?: (error: unknown) => void;
|
|
143
|
+
}> &
|
|
144
|
+
React.DetailedHTMLProps<
|
|
145
|
+
React.HTMLAttributes<HTMLAnchorElement>,
|
|
146
|
+
HTMLAnchorElement
|
|
147
|
+
>
|
|
148
|
+
>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
declare module "@lazarv/react-server/remote-component" {
|
|
152
|
+
const RemoteComponent: React.FC<{
|
|
153
|
+
url: string;
|
|
154
|
+
outlet?: string;
|
|
155
|
+
ttl?: number;
|
|
156
|
+
request?: RequestInit;
|
|
157
|
+
}>;
|
|
158
|
+
export default RemoteComponent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
declare module "@lazarv/react-server/router" {
|
|
162
|
+
interface ReactServerRouting {
|
|
163
|
+
path: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type RouteParams = Record<string, string | string[]>;
|
|
167
|
+
export type RouteMatchers = Record<string, (value: string) => boolean>;
|
|
168
|
+
|
|
169
|
+
export const ClientOnly: React.FC<React.PropsWithChildren>;
|
|
170
|
+
export const Route: React.FC<
|
|
171
|
+
React.PropsWithChildren<{
|
|
172
|
+
path: string;
|
|
173
|
+
exact?: boolean;
|
|
174
|
+
matchers?: RouteMatchers;
|
|
175
|
+
element?: React.ReactElement;
|
|
176
|
+
render?: (
|
|
177
|
+
props: React.PropsWithChildren<RouteParams>
|
|
178
|
+
) => React.ReactElement;
|
|
179
|
+
standalone?: boolean;
|
|
180
|
+
remote?: boolean;
|
|
181
|
+
fallback?: boolean;
|
|
182
|
+
}>
|
|
183
|
+
>;
|
|
184
|
+
|
|
185
|
+
export type ActionState<T, E> = {
|
|
186
|
+
formData: FormData | null;
|
|
187
|
+
data: T | null;
|
|
188
|
+
error: E | null;
|
|
189
|
+
actionId: string | null;
|
|
190
|
+
};
|
|
191
|
+
export function useActionState<
|
|
192
|
+
T extends (
|
|
193
|
+
...args: any[]
|
|
194
|
+
) => T extends (...args: any[]) => infer R ? R : any,
|
|
195
|
+
E = Error,
|
|
196
|
+
>(action: T): ActionState<ReturnType<T>, E>;
|
|
197
|
+
|
|
198
|
+
export type MatchOptions = {
|
|
199
|
+
exact?: boolean;
|
|
200
|
+
fallback?: boolean;
|
|
201
|
+
matchers?: RouteMatchers;
|
|
202
|
+
};
|
|
203
|
+
export function useMatch<T = RouteParams>(
|
|
204
|
+
path: string,
|
|
205
|
+
options?: MatchOptions
|
|
206
|
+
): T | null;
|
|
207
|
+
|
|
208
|
+
export function useParams<T = RouteParams>(): T;
|
|
209
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ErrorBoundary from "@lazarv/react-server/client/ErrorBoundary.jsx";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
|
|
4
|
+
export default async function ReactServerErrorBoundary({
|
|
5
|
+
fallback = null,
|
|
6
|
+
children,
|
|
7
|
+
...props
|
|
8
|
+
}) {
|
|
9
|
+
return (
|
|
10
|
+
<Suspense fallback={fallback}>
|
|
11
|
+
<ErrorBoundary {...props}>{children}</ErrorBoundary>
|
|
12
|
+
</Suspense>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useCache } from "@lazarv/react-server/memory-cache";
|
|
2
|
+
import { server$ } from "@lazarv/react-server/server/actions.mjs";
|
|
3
|
+
import { context$, getContext } from "@lazarv/react-server/server/context.mjs";
|
|
4
|
+
import { createFromFetch } from "react-server-dom-webpack/client.edge";
|
|
5
|
+
|
|
6
|
+
import ReactServerComponent from "../client/ReactServerComponent.jsx";
|
|
7
|
+
import { HTTP_CONTEXT } from "./symbols.mjs";
|
|
8
|
+
|
|
9
|
+
export const REMOTE_CACHE = Symbol.for("REMOTE_CACHE");
|
|
10
|
+
|
|
11
|
+
const streamOptions = ({ url, ttl, outlet, request }) => ({
|
|
12
|
+
async callServer(...args) {
|
|
13
|
+
const [id, [data]] = args;
|
|
14
|
+
const formData = new FormData();
|
|
15
|
+
Object.entries(data).forEach(([k, v]) => {
|
|
16
|
+
if (!k.startsWith("$ACTION_ID_")) {
|
|
17
|
+
formData.append(k, v);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
formData.append(`$ACTION_ID_${id}`, "");
|
|
21
|
+
return useCache(
|
|
22
|
+
[url, REMOTE_CACHE],
|
|
23
|
+
() =>
|
|
24
|
+
getRemoteFlightResponse({
|
|
25
|
+
url,
|
|
26
|
+
ttl,
|
|
27
|
+
request: {
|
|
28
|
+
method: "POST",
|
|
29
|
+
body: formData,
|
|
30
|
+
headers: {
|
|
31
|
+
...(request?.headers ?? {}),
|
|
32
|
+
accept: "text/x-component;standalone;remote",
|
|
33
|
+
outlet,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
ttl,
|
|
38
|
+
true
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
function proxyServerReference(parent, key, value, url, outlet) {
|
|
43
|
+
if (!value) return value;
|
|
44
|
+
else if (Array.isArray(value)) {
|
|
45
|
+
value.forEach((v, k) => proxyServerReference(value, k, v, url, outlet));
|
|
46
|
+
} else if (
|
|
47
|
+
value &&
|
|
48
|
+
typeof value === "object" &&
|
|
49
|
+
value.$$typeof?.toString() !== "Symbol(react.server_context)"
|
|
50
|
+
) {
|
|
51
|
+
Object.entries(value).forEach(([k, v]) =>
|
|
52
|
+
proxyServerReference(value, k, v, url, outlet)
|
|
53
|
+
);
|
|
54
|
+
} else if (
|
|
55
|
+
typeof value === "function" &&
|
|
56
|
+
typeof value.$$FORM_ACTION === "function" &&
|
|
57
|
+
!value.$$remote_server_action
|
|
58
|
+
) {
|
|
59
|
+
const proxy = server$((...args) => {
|
|
60
|
+
value(...args);
|
|
61
|
+
});
|
|
62
|
+
proxy.$$bound = [
|
|
63
|
+
{
|
|
64
|
+
__react_server_remote_component_url__: url,
|
|
65
|
+
__react_server_remote_component_outlet__: outlet,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
proxy.$$remote_server_action = true;
|
|
69
|
+
parent[key] = proxy;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function getRemoteFlightResponse({ url, ttl, outlet, request = {} }) {
|
|
73
|
+
const Component = createFromFetch(
|
|
74
|
+
fetch(url, {
|
|
75
|
+
...request,
|
|
76
|
+
headers: {
|
|
77
|
+
...(request?.headers ?? {}),
|
|
78
|
+
accept: "text/x-component;standalone;remote",
|
|
79
|
+
outlet,
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
streamOptions({ url, ttl, outlet, request })
|
|
83
|
+
);
|
|
84
|
+
const promise = new Promise((resolve, reject) => {
|
|
85
|
+
Component.then((value) => {
|
|
86
|
+
try {
|
|
87
|
+
proxyServerReference(null, null, value, url, outlet);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
reject(e);
|
|
90
|
+
}
|
|
91
|
+
resolve(value);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
promise._response = Component._response;
|
|
95
|
+
return Component;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function FlightComponent({ url, ttl = Infinity, outlet = null, request = {} }) {
|
|
99
|
+
const key = `__react_server_remote_component_outlet_${outlet}__`;
|
|
100
|
+
const accept = getContext(HTTP_CONTEXT).request.headers.get("accept");
|
|
101
|
+
const Component = useCache(
|
|
102
|
+
[url, accept, REMOTE_CACHE],
|
|
103
|
+
async () => {
|
|
104
|
+
const res = await fetch(url, {
|
|
105
|
+
...request,
|
|
106
|
+
headers: {
|
|
107
|
+
...(request?.headers ?? {}),
|
|
108
|
+
accept: `${accept};standalone;remote`,
|
|
109
|
+
outlet,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const [message, ...stack] = (await res.text()).split("\n");
|
|
115
|
+
const remoteError = new Error(
|
|
116
|
+
`Failed to load remote component: ${message.replace(
|
|
117
|
+
/^Error:\s*/,
|
|
118
|
+
""
|
|
119
|
+
)}`
|
|
120
|
+
);
|
|
121
|
+
remoteError.stack = stack.map((l) => l.trim()).join("\n");
|
|
122
|
+
throw remoteError;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const reader = res.body.getReader();
|
|
126
|
+
const Component = { outlet, html: "", rsc: "" };
|
|
127
|
+
|
|
128
|
+
const decoder = new TextDecoder();
|
|
129
|
+
let done = false;
|
|
130
|
+
let value = "";
|
|
131
|
+
while (!done) {
|
|
132
|
+
const { value: chunk, done: _done } = await reader.read();
|
|
133
|
+
done = _done;
|
|
134
|
+
const str = decoder.decode(chunk);
|
|
135
|
+
value += str;
|
|
136
|
+
const lines = value.split("\n");
|
|
137
|
+
value = lines.pop();
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
if (/^[0-9a-f]+:/.test(line)) {
|
|
140
|
+
Component.rsc += line + "\n";
|
|
141
|
+
if (/^0:/.test(line)) {
|
|
142
|
+
if (!done) {
|
|
143
|
+
Component.stream = new ReadableStream({
|
|
144
|
+
async start(controller) {
|
|
145
|
+
let done = false;
|
|
146
|
+
let value = "";
|
|
147
|
+
let html = "";
|
|
148
|
+
let rsc = "";
|
|
149
|
+
|
|
150
|
+
while (!done) {
|
|
151
|
+
const { value: chunk, done: _done } = await reader.read();
|
|
152
|
+
done = _done;
|
|
153
|
+
if (chunk) {
|
|
154
|
+
const str = decoder.decode(chunk);
|
|
155
|
+
value += str;
|
|
156
|
+
const lines = value.split("\n");
|
|
157
|
+
value = lines.pop();
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (/^[0-9a-f]+:/.test(line)) {
|
|
160
|
+
rsc += line + "\n";
|
|
161
|
+
} else {
|
|
162
|
+
html += line;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!value && html && rsc) {
|
|
166
|
+
controller.enqueue({ outlet, html, rsc });
|
|
167
|
+
html = "";
|
|
168
|
+
rsc = "";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
controller.enqueue({ outlet, html: html || value, rsc });
|
|
174
|
+
controller.close();
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return Component;
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
Component.html += line;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (/^[0-9a-f]+:/.test(value)) {
|
|
186
|
+
Component.rsc += value + "\n";
|
|
187
|
+
} else {
|
|
188
|
+
Component.html += value;
|
|
189
|
+
}
|
|
190
|
+
Component.outlet = outlet;
|
|
191
|
+
return Component;
|
|
192
|
+
},
|
|
193
|
+
ttl
|
|
194
|
+
);
|
|
195
|
+
context$(key, Component);
|
|
196
|
+
return <>{key}</>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default function RemoteComponent({
|
|
200
|
+
url,
|
|
201
|
+
outlet = null,
|
|
202
|
+
ttl = Infinity,
|
|
203
|
+
request = {},
|
|
204
|
+
}) {
|
|
205
|
+
return (
|
|
206
|
+
<ReactServerComponent url={url} outlet={outlet} standalone remote>
|
|
207
|
+
<FlightComponent url={url} ttl={ttl} outlet={outlet} request={request} />
|
|
208
|
+
</ReactServerComponent>
|
|
209
|
+
);
|
|
210
|
+
}
|
package/server/Route.jsx
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ParamsContext } from "@lazarv/react-server/client/Params.mjs";
|
|
2
|
+
import { context$, getContext } from "@lazarv/react-server/server/context.mjs";
|
|
3
|
+
import { useRequest, useUrl } from "@lazarv/react-server/server/request.mjs";
|
|
4
|
+
import { ROUTE_MATCH } from "@lazarv/react-server/server/symbols.mjs";
|
|
5
|
+
|
|
6
|
+
export function useMatch(path, options = {}) {
|
|
7
|
+
if (path === "*" || options.fallback) {
|
|
8
|
+
if (getContext(ROUTE_MATCH)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const paramToArray = new Set();
|
|
15
|
+
const paramMatch = new Set();
|
|
16
|
+
const { pathname: rawPathname } = useUrl();
|
|
17
|
+
const pathname = decodeURIComponent(rawPathname);
|
|
18
|
+
const regexp =
|
|
19
|
+
path instanceof RegExp
|
|
20
|
+
? path
|
|
21
|
+
: new RegExp(
|
|
22
|
+
path === "/" && !options.exact
|
|
23
|
+
? "^\\/(.*)$"
|
|
24
|
+
: `^${path
|
|
25
|
+
.replace(/\[u\+([^\]]+)\]/g, (_, code) => `\\u${code}`)
|
|
26
|
+
.replace(/\/?\[(\[?[^\]]+\]?)\]/g, (_, name) => {
|
|
27
|
+
let optional = false;
|
|
28
|
+
if (name[0] === "[" && name[name.length - 1] === "]") {
|
|
29
|
+
name = name.slice(1, -1);
|
|
30
|
+
optional = true;
|
|
31
|
+
}
|
|
32
|
+
if (name.startsWith("...")) {
|
|
33
|
+
name = name.slice(3);
|
|
34
|
+
paramToArray.add(name);
|
|
35
|
+
return `(\\/${optional ? "?" : ""}(?<${name}>.*))${
|
|
36
|
+
optional ? "*" : "+"
|
|
37
|
+
}`;
|
|
38
|
+
}
|
|
39
|
+
if (name.includes("=")) {
|
|
40
|
+
const [paramName, matcher] = name.split("=");
|
|
41
|
+
name = paramName;
|
|
42
|
+
paramMatch.add({ name, matcher });
|
|
43
|
+
}
|
|
44
|
+
return `\\/?(?<${name}>[^/]+)${optional ? "?" : ""}`;
|
|
45
|
+
})}${options.exact ? "$" : "(\\/([^/]+)?)*$"}`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const match = pathname.match(regexp);
|
|
49
|
+
if (!match) return null;
|
|
50
|
+
|
|
51
|
+
for (const { name, matcher } of paramMatch.values()) {
|
|
52
|
+
if (!options.matchers?.[matcher]?.(match.groups[name])) return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...match.groups,
|
|
57
|
+
...Array.from(paramToArray.values()).reduce((obj, key) => {
|
|
58
|
+
obj[key] = match.groups[key]?.split("/") ?? [];
|
|
59
|
+
return obj;
|
|
60
|
+
}, {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default function Route({
|
|
65
|
+
path,
|
|
66
|
+
exact,
|
|
67
|
+
matchers,
|
|
68
|
+
element,
|
|
69
|
+
render,
|
|
70
|
+
standalone,
|
|
71
|
+
remote,
|
|
72
|
+
fallback,
|
|
73
|
+
children,
|
|
74
|
+
}) {
|
|
75
|
+
const { headers } = useRequest();
|
|
76
|
+
const params = useMatch(path, { exact, matchers, fallback });
|
|
77
|
+
|
|
78
|
+
if (!params) return null;
|
|
79
|
+
|
|
80
|
+
if (standalone !== false) {
|
|
81
|
+
context$(ROUTE_MATCH, params);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const accept = headers.get("accept");
|
|
85
|
+
const acceptStandalone = accept?.includes(";standalone");
|
|
86
|
+
const acceptRemote = accept?.includes(";remote");
|
|
87
|
+
|
|
88
|
+
if (acceptStandalone && standalone === false) return <>{children}</>;
|
|
89
|
+
if ((remote === false && acceptRemote) || (remote === true && !acceptRemote))
|
|
90
|
+
return null;
|
|
91
|
+
|
|
92
|
+
const hasParams = Reflect.ownKeys(params).length > 0;
|
|
93
|
+
if (render)
|
|
94
|
+
return hasParams ? (
|
|
95
|
+
<ParamsContext.Provider value={params}>
|
|
96
|
+
{render({ ...params, children })}
|
|
97
|
+
</ParamsContext.Provider>
|
|
98
|
+
) : (
|
|
99
|
+
render({ ...params, children })
|
|
100
|
+
);
|
|
101
|
+
if (element)
|
|
102
|
+
return hasParams ? (
|
|
103
|
+
<ParamsContext.Provider value={params}>{element}</ParamsContext.Provider>
|
|
104
|
+
) : (
|
|
105
|
+
element
|
|
106
|
+
);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
export const asyncLocalStorage = (globalThis.__react_server_actions__ =
|
|
5
|
+
globalThis.__react_server_actions__ || new AsyncLocalStorage());
|
|
6
|
+
|
|
7
|
+
export function ref$(type, map, fn) {
|
|
8
|
+
let id;
|
|
9
|
+
try {
|
|
10
|
+
throw new Error();
|
|
11
|
+
} catch (e) {
|
|
12
|
+
const hash = createHash("md5");
|
|
13
|
+
hash.update(e.stack.replaceAll(/\n\s*/g, " "));
|
|
14
|
+
id = hash.digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { actionId } = asyncLocalStorage.getStore() ?? {};
|
|
18
|
+
if (actionId === id && map.has(id)) {
|
|
19
|
+
throw Promise.reject();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (map.has(id)) {
|
|
23
|
+
return map.get(id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const proxy = new Proxy(fn, {
|
|
27
|
+
get(target, prop, receiver) {
|
|
28
|
+
if (prop === "bind") {
|
|
29
|
+
return (thisArg, ...args) => {
|
|
30
|
+
const bound = target.bind(thisArg, ...args);
|
|
31
|
+
bound.$$id = id;
|
|
32
|
+
bound.$$typeof = type;
|
|
33
|
+
bound.$$async = true;
|
|
34
|
+
bound.$$bound = thisArg;
|
|
35
|
+
bound.$$FORM_ACTION = (name) => {
|
|
36
|
+
const data = new FormData();
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
method: "POST",
|
|
40
|
+
encType: "multipart/form-data",
|
|
41
|
+
data,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
map.set(bound.$$id, bound);
|
|
45
|
+
return bound;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return Reflect.get(target, prop, receiver);
|
|
49
|
+
},
|
|
50
|
+
apply(target, thisArg, args) {
|
|
51
|
+
return target.apply(thisArg, args);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const bound = proxy.bind(null);
|
|
56
|
+
if (actionId === id) {
|
|
57
|
+
throw Promise.reject();
|
|
58
|
+
}
|
|
59
|
+
return bound;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const serverReferenceMap = new Map();
|
|
63
|
+
export function server$(fn) {
|
|
64
|
+
if (typeof window !== "undefined") {
|
|
65
|
+
throw new Error("server$ can only be used on the server");
|
|
66
|
+
}
|
|
67
|
+
return ref$(Symbol.for("react.server.reference"), serverReferenceMap, fn);
|
|
68
|
+
}
|
|
69
|
+
export async function callServerReference(id, ...args) {
|
|
70
|
+
const proxy = serverReferenceMap.get(id);
|
|
71
|
+
return proxy.apply(proxy.$$bound, args);
|
|
72
|
+
}
|
package/server/cache.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getContext } from "@lazarv/react-server/server/context.mjs";
|
|
2
|
+
|
|
3
|
+
import { useRequest, useUrl } from "./request.mjs";
|
|
4
|
+
import { CACHE_CONTEXT, FLIGHT_CACHE, HTML_CACHE } from "./symbols.mjs";
|
|
5
|
+
|
|
6
|
+
export function cache$(Component, ttl = Infinity) {
|
|
7
|
+
return (props) => {
|
|
8
|
+
const url = useUrl()?.toString();
|
|
9
|
+
const accept = useRequest()?.headers?.get?.("accept");
|
|
10
|
+
const cache = getContext(CACHE_CONTEXT);
|
|
11
|
+
if (ttl === true) {
|
|
12
|
+
ttl = Infinity;
|
|
13
|
+
}
|
|
14
|
+
const expiry = Date.now() + ttl;
|
|
15
|
+
cache.setExpiry([url, accept, FLIGHT_CACHE], expiry);
|
|
16
|
+
cache.setExpiry([url, accept, HTML_CACHE], expiry);
|
|
17
|
+
return Component(props);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { context$, getContext } from "@lazarv/react-server/server/context.mjs";
|
|
2
|
+
import {
|
|
3
|
+
CLIENT_COMPONENTS,
|
|
4
|
+
HTTP_CONTEXT,
|
|
5
|
+
MANIFEST,
|
|
6
|
+
} from "@lazarv/react-server/server/symbols.mjs";
|
|
7
|
+
|
|
8
|
+
export const clientCache = (globalThis.__react_server_client_components__ =
|
|
9
|
+
globalThis.__react_server_client_components__ || new Map());
|
|
10
|
+
|
|
11
|
+
export const clientReferenceMap = new Proxy(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get(_, $$id) {
|
|
15
|
+
let def = clientCache.get($$id);
|
|
16
|
+
const [id, name = "default"] = $$id.split("::");
|
|
17
|
+
if (!clientCache.has($$id)) {
|
|
18
|
+
const manifest = getContext(MANIFEST);
|
|
19
|
+
if (!manifest) {
|
|
20
|
+
def = {
|
|
21
|
+
id,
|
|
22
|
+
chunks: [],
|
|
23
|
+
name,
|
|
24
|
+
async: true,
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
const { client, server } = manifest;
|
|
28
|
+
const filename = id.replace(`${process.cwd()}/.react-server/`, "");
|
|
29
|
+
const serverEntry = Object.values(server).find(
|
|
30
|
+
(entry) => entry.file === filename
|
|
31
|
+
);
|
|
32
|
+
const clientEntry = Object.values(client).find((entry) =>
|
|
33
|
+
serverEntry.src.endsWith(entry.src)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const httpContext = getContext(HTTP_CONTEXT);
|
|
37
|
+
const origin =
|
|
38
|
+
httpContext?.request?.headers?.get("x-forwarded-for") ||
|
|
39
|
+
new URL(httpContext?.url).origin;
|
|
40
|
+
def = {
|
|
41
|
+
id: `${origin}/${clientEntry.file}`,
|
|
42
|
+
chunks: [],
|
|
43
|
+
name,
|
|
44
|
+
async: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
clientCache.set($$id, def);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let clientComponents = getContext(CLIENT_COMPONENTS);
|
|
51
|
+
if (!clientComponents) {
|
|
52
|
+
clientComponents = new Set();
|
|
53
|
+
context$(CLIENT_COMPONENTS, clientComponents);
|
|
54
|
+
}
|
|
55
|
+
if (!clientComponents.has(def.id)) {
|
|
56
|
+
clientComponents.add(def.id);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return def;
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
);
|