@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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/bin/cli.mjs +93 -0
  4. package/bin/commands/build.mjs +19 -0
  5. package/bin/commands/dev.mjs +23 -0
  6. package/bin/commands/start.mjs +16 -0
  7. package/bin/loader.mjs +38 -0
  8. package/client/ActionState.mjs +16 -0
  9. package/client/ClientOnly.jsx +14 -0
  10. package/client/ClientProvider.jsx +243 -0
  11. package/client/ErrorBoundary.jsx +45 -0
  12. package/client/FlightContext.mjs +3 -0
  13. package/client/Link.jsx +59 -0
  14. package/client/Params.mjs +15 -0
  15. package/client/ReactServerComponent.jsx +77 -0
  16. package/client/Refresh.jsx +52 -0
  17. package/client/components.mjs +28 -0
  18. package/client/context.mjs +6 -0
  19. package/client/entry.client.jsx +146 -0
  20. package/client/index.jsx +6 -0
  21. package/client/navigation.jsx +4 -0
  22. package/config/context.mjs +37 -0
  23. package/config/index.mjs +114 -0
  24. package/lib/build/action.mjs +57 -0
  25. package/lib/build/banner.mjs +13 -0
  26. package/lib/build/chunks.mjs +26 -0
  27. package/lib/build/client.mjs +114 -0
  28. package/lib/build/custom-logger.mjs +13 -0
  29. package/lib/build/dependencies.mjs +54 -0
  30. package/lib/build/resolve.mjs +101 -0
  31. package/lib/build/server.mjs +142 -0
  32. package/lib/build/static.mjs +89 -0
  33. package/lib/dev/action.mjs +63 -0
  34. package/lib/dev/create-logger.mjs +52 -0
  35. package/lib/dev/create-server.mjs +208 -0
  36. package/lib/dev/modules.mjs +20 -0
  37. package/lib/dev/ssr-handler.mjs +135 -0
  38. package/lib/handlers/error.mjs +153 -0
  39. package/lib/handlers/not-found.mjs +5 -0
  40. package/lib/handlers/redirect.mjs +1 -0
  41. package/lib/handlers/rewrite.mjs +1 -0
  42. package/lib/handlers/static.mjs +120 -0
  43. package/lib/handlers/trailing-slash.mjs +12 -0
  44. package/lib/plugins/react-server.mjs +73 -0
  45. package/lib/plugins/use-client.mjs +135 -0
  46. package/lib/plugins/use-server.mjs +175 -0
  47. package/lib/start/action.mjs +110 -0
  48. package/lib/start/create-server.mjs +111 -0
  49. package/lib/start/manifest.mjs +104 -0
  50. package/lib/start/ssr-handler.mjs +134 -0
  51. package/lib/sys.mjs +49 -0
  52. package/lib/utils/merge.mjs +31 -0
  53. package/lib/utils/server-address.mjs +14 -0
  54. package/memory-cache/index.mjs +125 -0
  55. package/package.json +81 -0
  56. package/react-server.d.ts +209 -0
  57. package/server/ErrorBoundary.jsx +14 -0
  58. package/server/RemoteComponent.jsx +210 -0
  59. package/server/Route.jsx +108 -0
  60. package/server/actions.mjs +72 -0
  61. package/server/cache.mjs +19 -0
  62. package/server/client-component.mjs +62 -0
  63. package/server/context.mjs +32 -0
  64. package/server/cookies.mjs +14 -0
  65. package/server/entry.server.jsx +972 -0
  66. package/server/error-boundary.jsx +2 -0
  67. package/server/http-headers.mjs +8 -0
  68. package/server/http-status.mjs +6 -0
  69. package/server/index.mjs +14 -0
  70. package/server/logger.mjs +15 -0
  71. package/server/module-loader.mjs +20 -0
  72. package/server/redirects.mjs +45 -0
  73. package/server/remote-component.jsx +2 -0
  74. package/server/request.mjs +37 -0
  75. package/server/revalidate.mjs +22 -0
  76. package/server/rewrites.mjs +0 -0
  77. package/server/router.jsx +6 -0
  78. package/server/runtime.mjs +32 -0
  79. package/server/symbols.mjs +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Viktor Lázár
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @lazarv/react-server
2
+
3
+ Experimental React meta-framework using Vite.
4
+
5
+ See details at https://github.com/lazarv/react-server.
package/bin/cli.mjs ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ import "./loader.mjs";
4
+
5
+ import { format } from "node:util";
6
+
7
+ if (typeof process !== "undefined") {
8
+ // patch process.emit to ignore ExperimentalWarning
9
+ const originalEmit = process.emit;
10
+ process.emit = function (name, data, ...args) {
11
+ if (
12
+ name === "warning" &&
13
+ typeof data === "object" &&
14
+ data.name === "ExperimentalWarning"
15
+ //if you want to only stop certain messages, test for the message here:
16
+ //&& data.message.includes(`Fetch API`)
17
+ ) {
18
+ return false;
19
+ }
20
+ return originalEmit.call(process, name, data, ...args);
21
+ };
22
+ }
23
+
24
+ const oldConsoleError = console.error;
25
+ console.error = function (message, ...args) {
26
+ if (!message) return;
27
+ // suppress warning about multiple react renderers using the same context
28
+ if (
29
+ typeof message === "string" &&
30
+ message.includes(
31
+ "Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported."
32
+ )
33
+ ) {
34
+ return;
35
+ }
36
+ // throw on other warnings
37
+ if (
38
+ message?.startsWith?.("Warning:") ||
39
+ message?.message?.startsWith?.("Warning:")
40
+ ) {
41
+ const error =
42
+ typeof message === "string"
43
+ ? new Error(format(message, ...args))
44
+ : message;
45
+ const stack = error.stack?.split?.("\n") ?? [];
46
+ if (
47
+ stack.find(
48
+ (line) => line.includes("at printWarning") && line.includes("/react@")
49
+ )
50
+ ) {
51
+ if (typeof message === "string") {
52
+ throw error;
53
+ } else {
54
+ return;
55
+ }
56
+ }
57
+ }
58
+ return oldConsoleError.call(console, message, ...args);
59
+ };
60
+
61
+ import { fileURLToPath } from "node:url";
62
+
63
+ import cac from "cac";
64
+ import glob from "fast-glob";
65
+
66
+ import { argv, exit } from "../lib/sys.mjs";
67
+
68
+ const { default: packageJson } = await import("../package.json", {
69
+ assert: { type: "json" },
70
+ });
71
+ const commands = await glob("commands/*.mjs", {
72
+ cwd: fileURLToPath(new URL(".", import.meta.url)),
73
+ });
74
+
75
+ const cli = cac(packageJson.name.split("/").pop());
76
+
77
+ for (const command of commands) {
78
+ const { default: command_init$ } = await import(`./${command}`);
79
+ await command_init$?.(cli);
80
+ }
81
+
82
+ cli.help();
83
+ cli.version(packageJson.version);
84
+
85
+ try {
86
+ cli.parse(argv(), {
87
+ run: false,
88
+ });
89
+ await cli.runMatchedCommand();
90
+ } catch (error) {
91
+ console.error(error.stack);
92
+ exit(1);
93
+ }
@@ -0,0 +1,19 @@
1
+ export default (cli) =>
2
+ cli
3
+ .command("build [root]", "build for production")
4
+ .option("--minify", "minify", { default: false })
5
+ .option(
6
+ "--sourcemap [type]",
7
+ "[boolean|inline|hidden] generate source map",
8
+ {
9
+ default: false,
10
+ }
11
+ )
12
+ .option("--no-color", "disable color output", { default: false })
13
+ .option("--dev", "[boolean] development mode", { default: false })
14
+ .option("--server", "[boolean] build server", { default: true })
15
+ .option("--client", "[boolean] build client", { default: true })
16
+ .option("--export", "[boolean] static export", { default: false })
17
+ .action(async (...args) =>
18
+ (await import("../../lib/build/action.mjs")).default(...args)
19
+ );
@@ -0,0 +1,23 @@
1
+ import { setEnv } from "../../lib/sys.mjs";
2
+
3
+ export default (cli) =>
4
+ cli
5
+ .command("[root]", "start server in development mode")
6
+ .option("--host [host]", "[string] host to listen on", {
7
+ default: "localhost",
8
+ })
9
+ .option("--port <port>", "[number] port to listen on", { default: 3000 })
10
+ .option("--https", "[boolean] use HTTPS protocol", { default: false })
11
+ .option("--open [url]", "[boolean|string] open browser on server start", {
12
+ default: false,
13
+ })
14
+ .option("--cors", "enable CORS", { default: false })
15
+ .option("--force", "force optimize deps", { default: false })
16
+ .option("--clear-screen", "clear screen on server start", {
17
+ default: false,
18
+ })
19
+ .option("--no-color", "disable color output", { default: false })
20
+ .action(async (...args) => {
21
+ setEnv("NODE_ENV", "development");
22
+ (await import("../../lib/dev/action.mjs")).default(...args);
23
+ });
@@ -0,0 +1,16 @@
1
+ export default (cli) =>
2
+ cli
3
+ .command("start [root]", "start server in production mode")
4
+ .option("--host [host]", "[string] host to listen on", {
5
+ default: "localhost",
6
+ })
7
+ .option("--port <port>", "[number] port to listen on", { default: 3000 })
8
+ .option("--https", "[boolean] use HTTPS protocol", { default: false })
9
+ .option("--cors", "[boolean] enable CORS", { default: false })
10
+ .option("--origin <origin>", "[string] origin", { default: "" })
11
+ .option("--trust-proxy", "[boolean] trust proxy", { default: false })
12
+ .option("--build <root>", "[string] build root", { default: "" })
13
+ .option("--dev", "[boolean] development mode", { default: false })
14
+ .action(async (...args) =>
15
+ (await import("../../lib/start/action.mjs")).default(...args)
16
+ );
package/bin/loader.mjs ADDED
@@ -0,0 +1,38 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ import moduleAlias from "module-alias";
4
+ const __require = createRequire(import.meta.url);
5
+
6
+ const react = __require.resolve("react");
7
+ const reactJsxRuntime = __require.resolve("react/jsx-runtime");
8
+ const reactJsxDevRuntime = __require.resolve("react/jsx-dev-runtime");
9
+ const reactDom = __require.resolve("react-dom");
10
+ const reactDomClient = __require.resolve("react-dom/client");
11
+ const reactDomServerEdge = __require.resolve("react-dom/server.edge");
12
+ const reactServerDomWebpackClientBrowser = __require.resolve(
13
+ "react-server-dom-webpack/client.browser"
14
+ );
15
+ const reactServerDomWebpackClientEdge = __require.resolve(
16
+ "react-server-dom-webpack/client.edge"
17
+ );
18
+ const reactServerDomWebpackServerEdge = __require.resolve(
19
+ "react-server-dom-webpack/server.edge"
20
+ );
21
+ const reactErrorBoundary = __require.resolve("react-error-boundary");
22
+
23
+ const moduleAliases = {
24
+ react,
25
+ "react/jsx-runtime": reactJsxRuntime,
26
+ "react/jsx-dev-runtime": reactJsxDevRuntime,
27
+ "react-dom": reactDom,
28
+ "react-dom/client": reactDomClient,
29
+ "react-dom/server.edge": reactDomServerEdge,
30
+ "react-server-dom-webpack/client.browser": reactServerDomWebpackClientBrowser,
31
+ "react-server-dom-webpack/client.edge": reactServerDomWebpackClientEdge,
32
+ "react-server-dom-webpack/server.edge": reactServerDomWebpackServerEdge,
33
+ "react-error-boundary": reactErrorBoundary,
34
+ };
35
+
36
+ Object.entries(moduleAliases).forEach(([pkg, resolved]) =>
37
+ moduleAlias.addAlias(pkg, resolved)
38
+ );
@@ -0,0 +1,16 @@
1
+ import { createServerContext, useContext } from "react";
2
+
3
+ export const ActionStateContext = createServerContext("ActionStateContext", {
4
+ formData: null,
5
+ data: null,
6
+ error: null,
7
+ actionId: null,
8
+ });
9
+
10
+ export function useActionState(action) {
11
+ const { formData, data, error, actionId } = useContext(ActionStateContext);
12
+ if (actionId !== action.$$id) {
13
+ return { formData: null, data: null, error: null, actionId: action.$$id };
14
+ }
15
+ return { formData, data, error, actionId };
16
+ }
@@ -0,0 +1,14 @@
1
+ "use client";
2
+
3
+ import { startTransition, useEffect, useState } from "react";
4
+
5
+ export default function ClientOnly({ children }) {
6
+ const [client, setClient] = useState(false);
7
+ useEffect(() => {
8
+ startTransition(() => {
9
+ setClient(true);
10
+ });
11
+ }, []);
12
+
13
+ return client ? <>{children}</> : null;
14
+ }
@@ -0,0 +1,243 @@
1
+ import {
2
+ createFromFetch,
3
+ createFromReadableStream,
4
+ encodeReply,
5
+ } from "react-server-dom-webpack/client.browser";
6
+
7
+ import { ClientContext } from "./context.mjs";
8
+
9
+ export const PAGE_ROOT = "PAGE_ROOT";
10
+ let activeChunk = null;
11
+ const cache = new Map();
12
+ const listeners = new Map();
13
+ const outlets = new Map();
14
+ const prefetching = new Map();
15
+ const flightCache = new Map();
16
+ const registerOutlet = (outlet, url) => {
17
+ outlets.set(outlet, url);
18
+ return () => outlets.delete(outlet);
19
+ };
20
+ const subscribe = (url, listener) => {
21
+ if (!listeners.has(url)) {
22
+ listeners.set(url, new Set());
23
+ }
24
+ const urlListeners = listeners.get(url);
25
+ urlListeners.add(listener);
26
+ return () => urlListeners.delete(listener);
27
+ };
28
+ const emit = (url, to = url, callback = () => {}) => {
29
+ if (!listeners.has(url)) return;
30
+ const urlListeners = listeners.get(url);
31
+ for (const listener of urlListeners) listener(to, callback);
32
+ };
33
+ const prefetch = (to, { outlet = PAGE_ROOT, ttl = Infinity }) => {
34
+ if (prefetching.get(outlet) !== to) {
35
+ cache.delete(outlet);
36
+ cache.delete(to);
37
+ prefetching.set(outlet, to);
38
+ const key = `${outlet}:${to}`;
39
+ if (flightCache.has(key)) {
40
+ cache.set(outlet, flightCache.get(key));
41
+ } else {
42
+ getFlightResponse(to, {
43
+ outlet,
44
+ standalone: outlet !== PAGE_ROOT,
45
+ });
46
+ flightCache.set(key, cache.get(outlet));
47
+ if (typeof ttl === "number" && ttl < Infinity) {
48
+ setTimeout(() => {
49
+ if (flightCache.has(key)) {
50
+ flightCache.delete(key);
51
+ }
52
+ }, ttl);
53
+ }
54
+ }
55
+ }
56
+ };
57
+ const refresh = async (outlet = PAGE_ROOT) => {
58
+ return new Promise((resolve, reject) => {
59
+ const url = outlets.get(outlet) || PAGE_ROOT;
60
+ if (prefetching.get(outlet) === url) {
61
+ prefetching.delete(outlet);
62
+ } else {
63
+ cache.delete(url);
64
+ cache.delete(outlet);
65
+ }
66
+ emit(outlet, url, (err) => {
67
+ if (err) reject(err);
68
+ else {
69
+ activeChunk = cache.get(outlet);
70
+ resolve();
71
+ }
72
+ });
73
+ });
74
+ };
75
+ const navigate = (to, { outlet = PAGE_ROOT, push, rollback = 0 }) => {
76
+ return new Promise((resolve, reject) => {
77
+ if (outlet === PAGE_ROOT) {
78
+ if (typeof rollback === "number" && rollback > 0) {
79
+ const key = `${outlet}:${location.href}`;
80
+ if (!flightCache.has(key)) {
81
+ const timeoutKey = `${key}:timeout`;
82
+ if (flightCache.has(timeoutKey)) {
83
+ clearTimeout(flightCache.get(timeoutKey));
84
+ flightCache.delete(timeoutKey);
85
+ }
86
+ flightCache.set(key, activeChunk);
87
+ flightCache.set(
88
+ timeoutKey,
89
+ setTimeout(() => {
90
+ if (flightCache.has(key)) {
91
+ flightCache.delete(key);
92
+ }
93
+ }, rollback)
94
+ );
95
+ }
96
+ }
97
+ outlets.set(outlet, to);
98
+ if (push !== false) {
99
+ history.pushState(null, "", to);
100
+ } else {
101
+ history.replaceState(null, "", to);
102
+ }
103
+ }
104
+ if (prefetching.get(outlet) === to) {
105
+ prefetching.delete(outlet);
106
+ } else {
107
+ cache.delete(to);
108
+ cache.delete(outlet);
109
+ }
110
+ emit(outlet, to, (err) => {
111
+ if (err) reject(err);
112
+ else {
113
+ activeChunk = cache.get(outlet);
114
+ resolve();
115
+ }
116
+ });
117
+ });
118
+ };
119
+ const replace = (to, options) => {
120
+ return navigate(to, { ...options, push: false });
121
+ };
122
+ window.addEventListener("popstate", () => {
123
+ const key = `${PAGE_ROOT}:${location.href}`;
124
+ if (flightCache.has(key)) {
125
+ cache.set(PAGE_ROOT, flightCache.get(key));
126
+ flightCache.delete(key);
127
+ const timeoutKey = `${key}:timeout`;
128
+ if (flightCache.has(timeoutKey)) {
129
+ clearTimeout(flightCache.get(timeoutKey));
130
+ flightCache.delete(timeoutKey);
131
+ }
132
+ } else {
133
+ cache.delete(PAGE_ROOT);
134
+ cache.delete(location.href);
135
+ }
136
+ outlets.set(PAGE_ROOT, location.href);
137
+ emit(PAGE_ROOT, location.href, (err) => {
138
+ if (!err) {
139
+ activeChunk = cache.get(PAGE_ROOT);
140
+ }
141
+ });
142
+ });
143
+ const streamOptions = (outlet) => ({
144
+ async callServer(id, args) {
145
+ // eslint-disable-next-line no-async-promise-executor
146
+ return new Promise(async (resolve, reject) => {
147
+ try {
148
+ let formData = await encodeReply(args);
149
+ let target = outlet;
150
+ let url = outlet || PAGE_ROOT;
151
+ // if (args?.length > 1) {
152
+ // const remote = args[0];
153
+ // if (
154
+ // typeof remote?.__react_server_remote_component_url__ === "string" &&
155
+ // remote?.__react_server_remote_component_url__
156
+ // ) {
157
+ // url = remote.__react_server_remote_component_url__;
158
+ // }
159
+ // if (
160
+ // typeof remote?.__react_server_remote_component_outlet__ ===
161
+ // "string" &&
162
+ // remote?.__react_server_remote_component_outlet__
163
+ // ) {
164
+ // target = remote.__react_server_remote_component_outlet__;
165
+ // }
166
+ // }
167
+ cache.delete(url);
168
+ cache.delete(target);
169
+ getFlightResponse(outlets.get(target) || url, {
170
+ method: "POST",
171
+ body: formData,
172
+ outlet: target,
173
+ standalone: target !== PAGE_ROOT,
174
+ headers: {
175
+ "React-Server-Action": id,
176
+ },
177
+ });
178
+ emit(target, url, (err, result) => {
179
+ if (err) reject(err);
180
+ else resolve(result);
181
+ });
182
+ } catch (e) {
183
+ reject(e);
184
+ }
185
+ });
186
+ },
187
+ });
188
+ function getFlightResponse(url, options = {}) {
189
+ if (!cache.has(options.outlet || url)) {
190
+ if (
191
+ self[`__flightStream__${options.outlet || PAGE_ROOT}__`] &&
192
+ !self[`__flightHydration__${options.outlet || PAGE_ROOT}__`]
193
+ ) {
194
+ cache.set(
195
+ options.outlet || url,
196
+ createFromReadableStream(
197
+ self[`__flightStream__${options.outlet || PAGE_ROOT}__`].readable,
198
+ streamOptions(options.outlet || url)
199
+ )
200
+ );
201
+ self[`__flightHydration__${options.outlet || PAGE_ROOT}__`] = true;
202
+ activeChunk = cache.get(options.outlet || url);
203
+ } else {
204
+ cache.set(
205
+ options.outlet || url,
206
+ createFromFetch(
207
+ fetch(url === PAGE_ROOT ? location.href : url, {
208
+ method: options.method,
209
+ body: options.body,
210
+ headers: {
211
+ accept: `text/x-component${
212
+ options.standalone && url !== PAGE_ROOT ? ";standalone" : ""
213
+ }`,
214
+ "React-Server-Outlet": options.outlet || PAGE_ROOT,
215
+ ...options.headers,
216
+ },
217
+ }),
218
+ streamOptions(options.outlet || url)
219
+ )
220
+ );
221
+ }
222
+ }
223
+
224
+ return cache.get(options.outlet || url);
225
+ }
226
+
227
+ export default function ClientProvider({ children }) {
228
+ return (
229
+ <ClientContext.Provider
230
+ value={{
231
+ registerOutlet,
232
+ refresh,
233
+ prefetch,
234
+ navigate,
235
+ replace,
236
+ subscribe,
237
+ getFlightResponse,
238
+ }}
239
+ >
240
+ {children}
241
+ </ClientContext.Provider>
242
+ );
243
+ }
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useClient } from "@lazarv/react-server/client";
4
+ import { useContext, useEffect } from "react";
5
+ import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
6
+
7
+ import { FlightContext } from "./FlightContext.mjs";
8
+
9
+ function ResetErrorBoundary() {
10
+ const { url, outlet } = useContext(FlightContext);
11
+ const { resetBoundary } = useErrorBoundary();
12
+ const { subscribe } = useClient();
13
+
14
+ useEffect(() => {
15
+ return subscribe(outlet || url, () => resetBoundary());
16
+ }, []);
17
+
18
+ return null;
19
+ }
20
+
21
+ /**
22
+ * @typedef {import("react").PropsWithChildren<Omit<import("react-error-boundary").ErrorBoundaryProps, 'fallback'> & { fallback: import("react").ReactNode }>} ReactServerErrorBoundaryProps
23
+ * @param { ReactServerErrorBoundaryProps } props
24
+ */
25
+ export default function ReactServerErrorBoundary({
26
+ component: FallbackComponent,
27
+ render: fallbackRender,
28
+ children,
29
+ ...props
30
+ }) {
31
+ return (
32
+ <ErrorBoundary
33
+ {...props}
34
+ fallbackRender={(props) => (
35
+ <>
36
+ <ResetErrorBoundary />
37
+ {FallbackComponent && <FallbackComponent {...props} />}
38
+ {fallbackRender?.(props)}
39
+ </>
40
+ )}
41
+ >
42
+ {children}
43
+ </ErrorBoundary>
44
+ );
45
+ }
@@ -0,0 +1,3 @@
1
+ import { createServerContext } from "react";
2
+
3
+ export const FlightContext = createServerContext("FlightContext", "/");
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { useClient } from "@lazarv/react-server/client";
4
+ import { startTransition, useCallback } from "react";
5
+
6
+ export default function Link({
7
+ to,
8
+ target,
9
+ transition,
10
+ push,
11
+ replace,
12
+ prefetch: prefetchEnabled,
13
+ ttl = Infinity,
14
+ rollback = false,
15
+ onNavigate,
16
+ onError,
17
+ children,
18
+ ...props
19
+ }) {
20
+ const { prefetch, navigate } = useClient();
21
+
22
+ const tryNavigate = useCallback(async () => {
23
+ try {
24
+ await navigate(to, {
25
+ outlet: target,
26
+ push: replace ? false : push,
27
+ rollback,
28
+ });
29
+ onNavigate?.();
30
+ } catch (e) {
31
+ onError?.(e);
32
+ }
33
+ }, []);
34
+
35
+ const handleNavigate = async (e) => {
36
+ e.preventDefault();
37
+ if (transition !== false) {
38
+ startTransition(tryNavigate);
39
+ } else {
40
+ tryNavigate();
41
+ }
42
+ };
43
+
44
+ const handlePrefetch = () =>
45
+ prefetchEnabled === true && prefetch(to, { outlet: target, ttl });
46
+
47
+ return (
48
+ <a
49
+ {...props}
50
+ href={to}
51
+ onClick={handleNavigate}
52
+ onFocus={handlePrefetch}
53
+ onMouseOver={handlePrefetch}
54
+ onTouchStart={handlePrefetch}
55
+ >
56
+ {children}
57
+ </a>
58
+ );
59
+ }
@@ -0,0 +1,15 @@
1
+ import { getContext } from "@lazarv/react-server/server/context.mjs";
2
+ import { ROUTE_MATCH } from "@lazarv/react-server/server/symbols.mjs";
3
+ import { createServerContext, useContext } from "react";
4
+
5
+ export const ParamsContext = createServerContext("ParamsContext", {});
6
+
7
+ export function useParams() {
8
+ if (import.meta.env.SSR) {
9
+ const params = getContext(ROUTE_MATCH);
10
+ if (typeof params !== "undefined") {
11
+ return params;
12
+ }
13
+ }
14
+ return useContext(ParamsContext) ?? {};
15
+ }