@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,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { startTransition, useContext, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { ClientContext, useClient } from "./context.mjs";
|
|
6
|
+
import { FlightContext } from "./FlightContext.mjs";
|
|
7
|
+
|
|
8
|
+
function FlightComponent({ standalone = false, remote = false, children }) {
|
|
9
|
+
const { url, outlet } = useContext(FlightContext);
|
|
10
|
+
const client = useClient();
|
|
11
|
+
const { registerOutlet, subscribe, getFlightResponse } = client;
|
|
12
|
+
const [Component, setComponent] = useState(
|
|
13
|
+
remote
|
|
14
|
+
? getFlightResponse(url, { outlet, standalone })
|
|
15
|
+
: children || getFlightResponse(url, { outlet, standalone })
|
|
16
|
+
);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let mounted = true;
|
|
21
|
+
const unregisterOutlet = registerOutlet(outlet, url);
|
|
22
|
+
const unsubscribe = subscribe(outlet || url, (to, callback) => {
|
|
23
|
+
const Component = getFlightResponse(to, { outlet, standalone });
|
|
24
|
+
Component.then(
|
|
25
|
+
() => {
|
|
26
|
+
if (!mounted) return;
|
|
27
|
+
startTransition(async () => {
|
|
28
|
+
setError(null);
|
|
29
|
+
const {
|
|
30
|
+
value: { data: result },
|
|
31
|
+
} = Component.value.props;
|
|
32
|
+
setComponent(Component);
|
|
33
|
+
callback(null, result);
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
() => {
|
|
37
|
+
if (!mounted) return;
|
|
38
|
+
startTransition(() => {
|
|
39
|
+
setError(Component.reason);
|
|
40
|
+
const {
|
|
41
|
+
value: { data: result },
|
|
42
|
+
} = Component.value.props;
|
|
43
|
+
callback(Component.reason, result);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
mounted = false;
|
|
50
|
+
unregisterOutlet();
|
|
51
|
+
unsubscribe();
|
|
52
|
+
};
|
|
53
|
+
}, [url, outlet, standalone, subscribe, getFlightResponse]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ClientContext.Provider value={{ ...client, error }}>
|
|
57
|
+
{Component}
|
|
58
|
+
</ClientContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {import("react").PropsWithChildren<{ url: string, standalone?: boolean }>} ReactServerComponentProps
|
|
64
|
+
* @param { ReactServerComponentProps } props
|
|
65
|
+
*/
|
|
66
|
+
export default function ReactServerComponent({
|
|
67
|
+
url,
|
|
68
|
+
outlet = null,
|
|
69
|
+
standalone,
|
|
70
|
+
children,
|
|
71
|
+
}) {
|
|
72
|
+
return (
|
|
73
|
+
<FlightContext.Provider value={{ url, outlet }}>
|
|
74
|
+
<FlightComponent standalone={standalone}>{children}</FlightComponent>
|
|
75
|
+
</FlightContext.Provider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useClient } from "@lazarv/react-server/client";
|
|
4
|
+
import { startTransition, useCallback } from "react";
|
|
5
|
+
|
|
6
|
+
export default function Refresh({
|
|
7
|
+
url,
|
|
8
|
+
outlet,
|
|
9
|
+
transition,
|
|
10
|
+
prefetch: prefetchEnabled,
|
|
11
|
+
ttl = Infinity,
|
|
12
|
+
onRefresh,
|
|
13
|
+
onError,
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}) {
|
|
17
|
+
const { refresh, prefetch } = useClient();
|
|
18
|
+
|
|
19
|
+
const tryRefresh = useCallback(async () => {
|
|
20
|
+
try {
|
|
21
|
+
await refresh(outlet || url);
|
|
22
|
+
onRefresh?.();
|
|
23
|
+
} catch (e) {
|
|
24
|
+
onError?.(e);
|
|
25
|
+
}
|
|
26
|
+
}, [refresh, outlet, url, onRefresh, onError]);
|
|
27
|
+
|
|
28
|
+
const handleRefresh = async (e) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
if (transition !== false) {
|
|
31
|
+
startTransition(tryRefresh);
|
|
32
|
+
} else {
|
|
33
|
+
tryRefresh();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handlePrefetch = () =>
|
|
38
|
+
prefetchEnabled === true && prefetch(url, { outlet, ttl });
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<a
|
|
42
|
+
{...props}
|
|
43
|
+
href={url}
|
|
44
|
+
onClick={handleRefresh}
|
|
45
|
+
onFocus={handlePrefetch}
|
|
46
|
+
onMouseOver={handlePrefetch}
|
|
47
|
+
onTouchStart={handlePrefetch}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</a>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function client$(Component, name = "default") {
|
|
2
|
+
if (typeof window !== "undefined") return Component;
|
|
3
|
+
|
|
4
|
+
let id = "default";
|
|
5
|
+
try {
|
|
6
|
+
throw new Error();
|
|
7
|
+
} catch (e) {
|
|
8
|
+
const [, , source] = e.stack.split("\n");
|
|
9
|
+
id =
|
|
10
|
+
/\((.*):[0-9]+:[0-9]+\)/.exec(source)?.[1] ??
|
|
11
|
+
/file:\/\/(.*):[0-9]+:[0-9]+/.exec(source)?.[1] ??
|
|
12
|
+
"default";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Object.defineProperties(Component, {
|
|
16
|
+
$$typeof: {
|
|
17
|
+
value: Symbol.for("react.client.reference"),
|
|
18
|
+
},
|
|
19
|
+
$$id: {
|
|
20
|
+
value: `${id}::${name}`,
|
|
21
|
+
},
|
|
22
|
+
$$async: {
|
|
23
|
+
value: true,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return Component;
|
|
28
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import ClientProvider, {
|
|
2
|
+
PAGE_ROOT,
|
|
3
|
+
} from "@lazarv/react-server/client/ClientProvider.jsx";
|
|
4
|
+
import ReactServerComponent from "@lazarv/react-server/client/ReactServerComponent.jsx";
|
|
5
|
+
import { startTransition, StrictMode } from "react";
|
|
6
|
+
import { hydrateRoot } from "react-dom/client";
|
|
7
|
+
|
|
8
|
+
function ReactServer() {
|
|
9
|
+
return (
|
|
10
|
+
<ClientProvider>
|
|
11
|
+
<ReactServerComponent outlet={PAGE_ROOT} url={location.href} />
|
|
12
|
+
</ClientProvider>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (import.meta.env.DEV) {
|
|
17
|
+
var formatRegExp = /%[sdj%]/g;
|
|
18
|
+
const format = (f, ...args) => {
|
|
19
|
+
let i = 0;
|
|
20
|
+
const len = args.length;
|
|
21
|
+
const str = String(f).replace(formatRegExp, function (x) {
|
|
22
|
+
if (x === "%%") return "%";
|
|
23
|
+
if (i >= len) return x;
|
|
24
|
+
switch (x) {
|
|
25
|
+
case "%s":
|
|
26
|
+
return String(args[i++]);
|
|
27
|
+
case "%d":
|
|
28
|
+
return Number(args[i++]);
|
|
29
|
+
case "%j":
|
|
30
|
+
try {
|
|
31
|
+
return JSON.stringify(args[i++]);
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return "[Circular]";
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
return x;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return str;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const { ErrorOverlay } = await import("/@vite/client");
|
|
43
|
+
|
|
44
|
+
const showErrorOverlay = async (error, source, force) => {
|
|
45
|
+
if (
|
|
46
|
+
localStorage.getItem("react-server:overlay") === "false" ||
|
|
47
|
+
sessionStorage.getItem("react-server:overlay") === "false"
|
|
48
|
+
) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!window.__react_server_error_overlay__) {
|
|
53
|
+
if (typeof error === "string") {
|
|
54
|
+
const [message, ...stack] = error.split("\n");
|
|
55
|
+
error = {
|
|
56
|
+
message,
|
|
57
|
+
stack: stack.join("\n"),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
error.plugin = "@lazarv/react-server";
|
|
61
|
+
const [, ...stacklines] = error.stack.split("\n");
|
|
62
|
+
error.stack = stacklines.map((it) => it.trim()).join("\n");
|
|
63
|
+
const [, id, line, column] = (stacklines?.[0] ?? "").match(
|
|
64
|
+
/\((.*):([0-9]+):([0-9]+)\)/
|
|
65
|
+
);
|
|
66
|
+
error.id = id || source;
|
|
67
|
+
|
|
68
|
+
if (!force && error.id.startsWith("http")) {
|
|
69
|
+
return setTimeout(() => showErrorOverlay(error, source, true));
|
|
70
|
+
}
|
|
71
|
+
window.__react_server_error_overlay__ = true;
|
|
72
|
+
|
|
73
|
+
error.loc = {
|
|
74
|
+
file: id,
|
|
75
|
+
column: parseInt(column, 10),
|
|
76
|
+
line: parseInt(line, 10),
|
|
77
|
+
length: 0,
|
|
78
|
+
lineText: "",
|
|
79
|
+
namespace: "",
|
|
80
|
+
suggestion: "",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const sourceFile = await fetch(
|
|
85
|
+
error.id.startsWith("http")
|
|
86
|
+
? error.id.replace("/@fs/", "/@source/")
|
|
87
|
+
: `/@source${error.id}`
|
|
88
|
+
);
|
|
89
|
+
const sourceCode = await sourceFile.text();
|
|
90
|
+
|
|
91
|
+
const lines = sourceCode.split("\n");
|
|
92
|
+
const start = Math.max(0, error.loc.line - 3);
|
|
93
|
+
const end = Math.min(lines.length, error.loc.line + 3);
|
|
94
|
+
const frame = lines
|
|
95
|
+
.slice(start, end)
|
|
96
|
+
.flatMap((l, i) => {
|
|
97
|
+
const curr = i + start;
|
|
98
|
+
const indent = " ".repeat(
|
|
99
|
+
Math.max(start, end).toString().length - curr.toString().length
|
|
100
|
+
);
|
|
101
|
+
return [
|
|
102
|
+
`${indent}${curr} | ${l}`,
|
|
103
|
+
...(i === 2
|
|
104
|
+
? [
|
|
105
|
+
`${indent}${curr
|
|
106
|
+
.toString()
|
|
107
|
+
.replaceAll(/./g, " ")} |${" ".repeat(
|
|
108
|
+
error.loc.column
|
|
109
|
+
)}^`,
|
|
110
|
+
]
|
|
111
|
+
: []),
|
|
112
|
+
];
|
|
113
|
+
})
|
|
114
|
+
.join("\n");
|
|
115
|
+
error.frame = `${error.message}\n${frame}\n`;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// noop
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const overlay = new ErrorOverlay(error);
|
|
121
|
+
overlay.addEventListener("click", () => {
|
|
122
|
+
window.__react_server_error_overlay__ = false;
|
|
123
|
+
});
|
|
124
|
+
document.body.appendChild(overlay);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const originalConsoleError = console.error;
|
|
129
|
+
console.error = (...args) => {
|
|
130
|
+
showErrorOverlay(format(...args));
|
|
131
|
+
originalConsoleError(...args);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
135
|
+
showErrorOverlay(error, source, lineno, colno, message);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
startTransition(() => {
|
|
140
|
+
hydrateRoot(
|
|
141
|
+
document,
|
|
142
|
+
<StrictMode>
|
|
143
|
+
<ReactServer />
|
|
144
|
+
</StrictMode>
|
|
145
|
+
);
|
|
146
|
+
});
|
package/client/index.jsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getContext } from "@lazarv/react-server/server/context.mjs";
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_CONTEXT,
|
|
4
|
+
CONFIG_ROOT,
|
|
5
|
+
} from "@lazarv/react-server/server/symbols.mjs";
|
|
6
|
+
|
|
7
|
+
export function forRoot(externalConfig) {
|
|
8
|
+
const config = getContext(CONFIG_CONTEXT) ?? externalConfig;
|
|
9
|
+
if (!config) {
|
|
10
|
+
throw new Error("Config not loaded");
|
|
11
|
+
}
|
|
12
|
+
return config[CONFIG_ROOT];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function forChild(url, externalConfig) {
|
|
16
|
+
if (typeof url !== "string") {
|
|
17
|
+
url = url.pathname;
|
|
18
|
+
}
|
|
19
|
+
const config = getContext(CONFIG_CONTEXT) ?? externalConfig;
|
|
20
|
+
if (!config) {
|
|
21
|
+
throw new Error("Config not loaded");
|
|
22
|
+
}
|
|
23
|
+
if (!config[url]) {
|
|
24
|
+
const key = Object.keys(config)
|
|
25
|
+
.find((it) => url.startsWith(it))
|
|
26
|
+
?.sort(([aKey], [bKey]) => bKey.length - aKey.length)?.[0];
|
|
27
|
+
|
|
28
|
+
if (key) {
|
|
29
|
+
const value = config[key];
|
|
30
|
+
if (value) {
|
|
31
|
+
config[url] = config[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return config[url] ?? forRoot(externalConfig);
|
|
37
|
+
}
|
package/config/index.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { build } from "esbuild";
|
|
5
|
+
import glob from "fast-glob";
|
|
6
|
+
|
|
7
|
+
import { CONFIG_PARENT, CONFIG_ROOT } from "../server/symbols.mjs";
|
|
8
|
+
export * from "./context.mjs";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { readFile, stat } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
import * as sys from "../lib/sys.mjs";
|
|
13
|
+
import merge from "../lib/utils/merge.mjs";
|
|
14
|
+
|
|
15
|
+
const cwd = sys.cwd();
|
|
16
|
+
const defaultConfig = {};
|
|
17
|
+
|
|
18
|
+
export async function loadConfig(initialConfig) {
|
|
19
|
+
const config = {};
|
|
20
|
+
const configFiles = (
|
|
21
|
+
await glob(
|
|
22
|
+
join(
|
|
23
|
+
cwd,
|
|
24
|
+
"**/{react-server,+*,vite}.config.{json,js,ts,mjs,mts,ts.mjs,mts.mjs}"
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
).map((file) => relative(cwd, file));
|
|
28
|
+
|
|
29
|
+
for await (const file of configFiles) {
|
|
30
|
+
try {
|
|
31
|
+
const key = dirname(file);
|
|
32
|
+
const filename = basename(file);
|
|
33
|
+
|
|
34
|
+
let configModule;
|
|
35
|
+
const src = join(cwd, key, filename);
|
|
36
|
+
if (/\.m?ts$/.test(filename)) {
|
|
37
|
+
const hash = createHash("shake256", { outputLength: 4 })
|
|
38
|
+
.update(await readFile(src, "utf8"))
|
|
39
|
+
.digest("hex");
|
|
40
|
+
try {
|
|
41
|
+
await stat(
|
|
42
|
+
`${join(cwd, ".react-server", key, filename)}.${hash}.mjs`
|
|
43
|
+
);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
await build({
|
|
46
|
+
absWorkingDir: filename.includes("vite.config")
|
|
47
|
+
? join(fileURLToPath(import.meta.url), "../..")
|
|
48
|
+
: cwd,
|
|
49
|
+
entryPoints: [src],
|
|
50
|
+
outfile: `${join(cwd, ".react-server", key, filename)}.${hash}.mjs`,
|
|
51
|
+
bundle: true,
|
|
52
|
+
platform: "node",
|
|
53
|
+
format: "esm",
|
|
54
|
+
external: filename.includes("vite.config") ? ["*"] : [],
|
|
55
|
+
minify: true,
|
|
56
|
+
tsconfig: join(cwd, "tsconfig.json"),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
configModule = (
|
|
60
|
+
await import(
|
|
61
|
+
pathToFileURL(
|
|
62
|
+
`${join(cwd, ".react-server", key, filename)}.${hash}.mjs`
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
).default;
|
|
66
|
+
} else {
|
|
67
|
+
configModule = (
|
|
68
|
+
await import(
|
|
69
|
+
pathToFileURL(src),
|
|
70
|
+
filename.endsWith(".json")
|
|
71
|
+
? { assert: { type: "json" } }
|
|
72
|
+
: undefined
|
|
73
|
+
)
|
|
74
|
+
).default;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
config[key] = merge(config[key] ?? {}, configModule);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error(e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const configKeys = Object.keys(config);
|
|
84
|
+
const root = configKeys.includes(".")
|
|
85
|
+
? "."
|
|
86
|
+
: configKeys.find((key) => configKeys.every((it) => it.startsWith(key)));
|
|
87
|
+
config[CONFIG_ROOT] = config[root] = merge(
|
|
88
|
+
{},
|
|
89
|
+
defaultConfig,
|
|
90
|
+
initialConfig,
|
|
91
|
+
{ root },
|
|
92
|
+
config[root]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
for (const key of configKeys) {
|
|
96
|
+
if (key === CONFIG_ROOT || key === root) continue;
|
|
97
|
+
merge(
|
|
98
|
+
config[key],
|
|
99
|
+
...configKeys
|
|
100
|
+
.filter((it) => it !== key && key.startsWith(it))
|
|
101
|
+
.sort((a, b) => b.length - a.length)
|
|
102
|
+
.map((key, index, parentConfigArray) => ({
|
|
103
|
+
...config[key],
|
|
104
|
+
[CONFIG_PARENT]: parentConfigArray[index - 1] ?? config[CONFIG_ROOT],
|
|
105
|
+
}))
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return config;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function defineConfig(config) {
|
|
113
|
+
return config;
|
|
114
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { rimraf } from "rimraf";
|
|
4
|
+
|
|
5
|
+
import { loadConfig } from "../../config/index.mjs";
|
|
6
|
+
import { ContextStorage } from "../../server/context.mjs";
|
|
7
|
+
import { CONFIG_CONTEXT } from "../../server/symbols.mjs";
|
|
8
|
+
import { cwd, setEnv } from "../sys.mjs";
|
|
9
|
+
import clientBuild from "./client.mjs";
|
|
10
|
+
import serverBuild from "./server.mjs";
|
|
11
|
+
import staticSiteGenerator from "./static.mjs";
|
|
12
|
+
|
|
13
|
+
export default async function build(root, options) {
|
|
14
|
+
const config = await loadConfig();
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
17
|
+
return new Promise(async (resolve) => {
|
|
18
|
+
ContextStorage.run(
|
|
19
|
+
{
|
|
20
|
+
[CONFIG_CONTEXT]: config,
|
|
21
|
+
},
|
|
22
|
+
async () => {
|
|
23
|
+
try {
|
|
24
|
+
if (!options.dev) {
|
|
25
|
+
// enforce production mode
|
|
26
|
+
setEnv("NODE_ENV", "production");
|
|
27
|
+
}
|
|
28
|
+
// empty out dir
|
|
29
|
+
if (options.server && options.client)
|
|
30
|
+
await rimraf(join(cwd(), ".react-server"));
|
|
31
|
+
else if (options.server)
|
|
32
|
+
await rimraf(join(cwd(), ".react-server/server"));
|
|
33
|
+
else if (options.client)
|
|
34
|
+
await rimraf(join(cwd(), ".react-server/client"));
|
|
35
|
+
// build server
|
|
36
|
+
if (options.server) {
|
|
37
|
+
await serverBuild(root, options);
|
|
38
|
+
// empty line
|
|
39
|
+
console.log();
|
|
40
|
+
}
|
|
41
|
+
// build client
|
|
42
|
+
if (options.client) {
|
|
43
|
+
await clientBuild(root, options);
|
|
44
|
+
}
|
|
45
|
+
// static export
|
|
46
|
+
if (options.export) {
|
|
47
|
+
await rimraf(join(cwd(), ".react-server/dist"));
|
|
48
|
+
await staticSiteGenerator(root, options);
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(e);
|
|
52
|
+
}
|
|
53
|
+
resolve();
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import colors from "picocolors";
|
|
2
|
+
|
|
3
|
+
import packageJson from "../../package.json" assert { type: "json" };
|
|
4
|
+
|
|
5
|
+
export default function banner(target, dev) {
|
|
6
|
+
console.log(
|
|
7
|
+
`${colors.cyan(
|
|
8
|
+
`${packageJson.name.split("/").pop()}/${packageJson.version}`,
|
|
9
|
+
)} ${colors.green(
|
|
10
|
+
`building ${target} for ${dev ? "development" : "production"}`,
|
|
11
|
+
)}`,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as dependencies from "./dependencies.mjs";
|
|
2
|
+
|
|
3
|
+
export const serverChunks = {
|
|
4
|
+
react: [
|
|
5
|
+
dependencies.react,
|
|
6
|
+
dependencies.reactJsxRuntime,
|
|
7
|
+
dependencies.reactDomClient,
|
|
8
|
+
dependencies.reactDomServerEdge,
|
|
9
|
+
dependencies.reactServerDomWebpackClientBrowser,
|
|
10
|
+
dependencies.reactServerDomWebpackClientEdge,
|
|
11
|
+
dependencies.reactServerDomWebpackServerEdge,
|
|
12
|
+
dependencies.reactErrorBoundary,
|
|
13
|
+
dependencies.scheduler,
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const clientChunks = {
|
|
18
|
+
react: [
|
|
19
|
+
dependencies.react,
|
|
20
|
+
dependencies.reactJsxRuntime,
|
|
21
|
+
dependencies.reactDomClient,
|
|
22
|
+
dependencies.reactServerDomWebpackClientBrowser,
|
|
23
|
+
dependencies.reactErrorBoundary,
|
|
24
|
+
dependencies.scheduler,
|
|
25
|
+
],
|
|
26
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import replace from "@rollup/plugin-replace";
|
|
4
|
+
import viteReact from "@vitejs/plugin-react";
|
|
5
|
+
import { build as viteBuild } from "vite";
|
|
6
|
+
|
|
7
|
+
import { forRoot } from "../../config/index.mjs";
|
|
8
|
+
import merge from "../../lib/utils/merge.mjs";
|
|
9
|
+
import packageJson from "../../package.json" assert { type: "json" };
|
|
10
|
+
import viteReactServer from "../plugins/react-server.mjs";
|
|
11
|
+
import rollupUseClient from "../plugins/use-client.mjs";
|
|
12
|
+
import * as sys from "../sys.mjs";
|
|
13
|
+
import banner from "./banner.mjs";
|
|
14
|
+
import { clientChunks } from "./chunks.mjs";
|
|
15
|
+
import customLogger from "./custom-logger.mjs";
|
|
16
|
+
import { clientAlias } from "./resolve.mjs";
|
|
17
|
+
|
|
18
|
+
const __require = createRequire(import.meta.url);
|
|
19
|
+
const cwd = sys.cwd();
|
|
20
|
+
|
|
21
|
+
export default async function clientBuild(_, options) {
|
|
22
|
+
banner("client", options.dev);
|
|
23
|
+
const config = forRoot();
|
|
24
|
+
const buildConfig = {
|
|
25
|
+
root: __require.resolve(`${packageJson.name}`),
|
|
26
|
+
resolve: {
|
|
27
|
+
alias: [...clientAlias(options.dev), ...(config.resolve?.alias ?? [])],
|
|
28
|
+
},
|
|
29
|
+
customLogger,
|
|
30
|
+
build: {
|
|
31
|
+
target: "esnext",
|
|
32
|
+
outDir: ".react-server",
|
|
33
|
+
emptyOutDir: false,
|
|
34
|
+
minify: options.minify,
|
|
35
|
+
manifest: "client/manifest.json",
|
|
36
|
+
sourcemap: options.sourcemap,
|
|
37
|
+
rollupOptions: {
|
|
38
|
+
preserveEntrySignatures: "allow-extension",
|
|
39
|
+
output: {
|
|
40
|
+
dir: ".react-server",
|
|
41
|
+
format: "esm",
|
|
42
|
+
entryFileNames: "[name].[hash].mjs",
|
|
43
|
+
chunkFileNames: "client/@lazarv/react-server/[name].[hash].mjs",
|
|
44
|
+
manualChunks: (id) => {
|
|
45
|
+
if (clientChunks["react"].includes(id)) return "react";
|
|
46
|
+
if (id.includes("@lazarv/react-server") && id.endsWith(".mjs")) {
|
|
47
|
+
return "react-server";
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
input: {
|
|
52
|
+
"client/index": __require.resolve(
|
|
53
|
+
"@lazarv/react-server/client/entry.client.jsx",
|
|
54
|
+
{ paths: [cwd] }
|
|
55
|
+
),
|
|
56
|
+
"client/@lazarv/react-server/client/ClientOnly": __require.resolve(
|
|
57
|
+
"@lazarv/react-server/client/ClientOnly.jsx",
|
|
58
|
+
{ paths: [cwd] }
|
|
59
|
+
),
|
|
60
|
+
"client/@lazarv/react-server/client/ErrorBoundary": __require.resolve(
|
|
61
|
+
"@lazarv/react-server/client/ErrorBoundary.jsx",
|
|
62
|
+
{ paths: [cwd] }
|
|
63
|
+
),
|
|
64
|
+
"client/@lazarv/react-server/client/Link": __require.resolve(
|
|
65
|
+
"@lazarv/react-server/client/Link.jsx",
|
|
66
|
+
{ paths: [cwd] }
|
|
67
|
+
),
|
|
68
|
+
"client/@lazarv/react-server/client/Refresh": __require.resolve(
|
|
69
|
+
"@lazarv/react-server/client/Refresh.jsx",
|
|
70
|
+
{ paths: [cwd] }
|
|
71
|
+
),
|
|
72
|
+
"client/@lazarv/react-server/client/ReactServerComponent":
|
|
73
|
+
__require.resolve(
|
|
74
|
+
"@lazarv/react-server/client/ReactServerComponent.jsx",
|
|
75
|
+
{ paths: [cwd] }
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
plugins: [
|
|
79
|
+
replace({
|
|
80
|
+
preventAssignment: true,
|
|
81
|
+
"process.env.NODE_ENV": JSON.stringify(
|
|
82
|
+
options.dev ? "development" : "production"
|
|
83
|
+
),
|
|
84
|
+
}),
|
|
85
|
+
rollupUseClient("client"),
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
plugins: [
|
|
90
|
+
viteReactServer("client"),
|
|
91
|
+
viteReact(),
|
|
92
|
+
...(config.plugins ?? []),
|
|
93
|
+
],
|
|
94
|
+
css: {
|
|
95
|
+
...config.css,
|
|
96
|
+
postcss: process.cwd(),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let viteConfig = buildConfig;
|
|
101
|
+
|
|
102
|
+
if (typeof config.build?.client?.config === "function")
|
|
103
|
+
viteConfig = config.build?.client?.config(buildConfig);
|
|
104
|
+
|
|
105
|
+
if (typeof config.build?.client?.config === "object")
|
|
106
|
+
viteConfig = merge(buildConfig, config.build?.client?.config);
|
|
107
|
+
|
|
108
|
+
if (typeof config.vite === "function") viteConfig = config.vite(viteConfig);
|
|
109
|
+
|
|
110
|
+
if (typeof config.vite === "object")
|
|
111
|
+
viteConfig = merge(viteConfig, config.vite);
|
|
112
|
+
|
|
113
|
+
return viteBuild(viteConfig);
|
|
114
|
+
}
|