@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
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
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
|
+
}
|
package/client/Link.jsx
ADDED
|
@@ -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
|
+
}
|