@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
@@ -0,0 +1,73 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { extname, join, relative } from "node:path";
4
+ import { createInterface } from "node:readline";
5
+
6
+ import glob from "fast-glob";
7
+
8
+ import packageJson from "../../package.json" assert { type: "json" };
9
+ import { cwd } from "../sys.mjs";
10
+
11
+ const __require = createRequire(import.meta.url);
12
+ const hmrSrc = `/${packageJson.name}/client/hmr.mjs`;
13
+
14
+ async function readFirstLine(path) {
15
+ const inputStream = createReadStream(path);
16
+ try {
17
+ for await (const line of createInterface(inputStream)) return line;
18
+ return ""; // If the file is empty.
19
+ } finally {
20
+ inputStream.destroy(); // Destroy file stream.
21
+ }
22
+ }
23
+
24
+ export default function viteReactServer(type) {
25
+ return {
26
+ name: "react-server",
27
+ async config(config) {
28
+ if (type) {
29
+ const extensions = config.resolve?.extensions || [
30
+ ".mjs",
31
+ ".js",
32
+ ".mts",
33
+ ".ts",
34
+ ".jsx",
35
+ ".tsx",
36
+ ];
37
+ const pattern = join(
38
+ cwd(),
39
+ `**/*{.${extensions.map((ext) => ext.slice(1)).join(",")}}`
40
+ );
41
+ const entries = await glob(pattern, {
42
+ ignore: ["**/node_modules/**/*"],
43
+ });
44
+ for (const entry of entries) {
45
+ const firstLine = await readFirstLine(entry);
46
+ if (
47
+ firstLine.includes(`"use client";`) ||
48
+ firstLine.includes(`'use client';`)
49
+ ) {
50
+ const specifier = relative(cwd(), entry);
51
+ if (!specifier.startsWith("..")) {
52
+ const name = specifier.replace(extname(specifier), "");
53
+ config.build.rollupOptions.input[`${type}/${name}`] = entry;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ },
59
+ load(id) {
60
+ if (id === hmrSrc) {
61
+ return `
62
+ import RefreshRuntime from "/@react-refresh";
63
+ RefreshRuntime.injectIntoGlobalHook(window);
64
+ window.$RefreshReg$ = () => {};
65
+ window.$RefreshSig$ = () => (type) => type;
66
+ window.__vite_plugin_react_preamble_installed__ = true;
67
+ console.log("Hot Module Replacement installed.");
68
+ import("${__require.resolve("@lazarv/react-server/client/entry.client.jsx")}");
69
+ `;
70
+ }
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,135 @@
1
+ import * as acorn from "acorn";
2
+ import * as escodegen from "escodegen";
3
+ import * as estraverse from "estraverse";
4
+
5
+ export default function useClient() {
6
+ return {
7
+ name: "use-client",
8
+ async transform(code, id) {
9
+ const firstLine = code.slice(0, code.indexOf("\n"));
10
+ if (
11
+ firstLine.includes('"use client"') ||
12
+ firstLine.includes("'use client'")
13
+ ) {
14
+ const ast = acorn.parse(code, {
15
+ sourceType: "module",
16
+ ecmaVersion: 2021,
17
+ sourceFile: id,
18
+ locations: true,
19
+ });
20
+
21
+ estraverse.replace(ast, {
22
+ leave(node) {
23
+ if (node.type === "Program") {
24
+ node.body.unshift({
25
+ type: "ImportDeclaration",
26
+ specifiers: [
27
+ {
28
+ type: "ImportSpecifier",
29
+ imported: {
30
+ type: "Identifier",
31
+ name: "client$",
32
+ },
33
+ local: {
34
+ type: "Identifier",
35
+ name: "__react_server_client$__",
36
+ },
37
+ },
38
+ ],
39
+ source: {
40
+ type: "Literal",
41
+ value: "@lazarv/react-server/client/components.mjs",
42
+ },
43
+ loc: {
44
+ start: { line: 1, column: 0 },
45
+ end: { line: 1, column: 0 },
46
+ },
47
+ });
48
+ }
49
+ },
50
+ enter(node) {
51
+ if (node.type === "ExportNamedDeclaration") {
52
+ if (node.declaration.type === "VariableDeclaration") {
53
+ for (const declaration of node.declaration.declarations) {
54
+ if (
55
+ declaration.init.type === "ArrowFunctionExpression" ||
56
+ declaration.init.type === "FunctionExpression"
57
+ ) {
58
+ declaration.init = {
59
+ type: "CallExpression",
60
+ callee: {
61
+ type: "Identifier",
62
+ name: "__react_server_client$__",
63
+ },
64
+ arguments: [
65
+ declaration.init,
66
+ { type: "Literal", value: declaration.id.name },
67
+ ],
68
+ };
69
+ }
70
+ }
71
+ } else if (
72
+ node.declaration.type === "FunctionDeclaration" ||
73
+ node.declaration.type === "Identifier"
74
+ ) {
75
+ node.declaration = {
76
+ type: "VariableDeclaration",
77
+ kind: "const",
78
+ declarations: [
79
+ {
80
+ type: "VariableDeclarator",
81
+ id: {
82
+ type: "Identifier",
83
+ name: node.declaration.id.name,
84
+ },
85
+ init: {
86
+ type: "CallExpression",
87
+ callee: {
88
+ type: "Identifier",
89
+ name: "__react_server_client$__",
90
+ },
91
+ arguments: [
92
+ node.declaration,
93
+ { type: "Literal", value: node.declaration.id.name },
94
+ ],
95
+ },
96
+ },
97
+ ],
98
+ };
99
+ }
100
+ } else if (node.type === "ExportDefaultDeclaration") {
101
+ if (
102
+ node.declaration.type === "ArrowFunctionExpression" ||
103
+ node.declaration.type === "FunctionExpression" ||
104
+ node.declaration.type === "FunctionDeclaration" ||
105
+ node.declaration.type === "Identifier"
106
+ ) {
107
+ node.declaration = {
108
+ type: "CallExpression",
109
+ callee: {
110
+ type: "Identifier",
111
+ name: "__react_server_client$__",
112
+ },
113
+ arguments: [
114
+ node.declaration,
115
+ { type: "Literal", value: "default" },
116
+ ],
117
+ };
118
+ }
119
+ }
120
+ },
121
+ });
122
+
123
+ const gen = escodegen.generate(ast, {
124
+ sourceMap: true,
125
+ sourceMapWithCode: true,
126
+ });
127
+
128
+ return {
129
+ code: gen.code,
130
+ map: gen.map.toString(),
131
+ };
132
+ }
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,175 @@
1
+ import * as acorn from "acorn";
2
+ import * as escodegen from "escodegen";
3
+ import * as estraverse from "estraverse";
4
+
5
+ export default function useServer() {
6
+ return {
7
+ name: "use-server",
8
+ async transform(code, id) {
9
+ // exclude node_modules
10
+ if (/node_modules/.test(id)) return;
11
+
12
+ if (code.includes('"use server"') || code.includes("'use server'")) {
13
+ try {
14
+ const ast = acorn.parse(code, {
15
+ sourceType: "module",
16
+ ecmaVersion: 2021,
17
+ sourceFile: id,
18
+ locations: true,
19
+ });
20
+
21
+ let serverRoot = false;
22
+ let exportContext = false;
23
+ const result = estraverse.replace(ast, {
24
+ leave(node) {
25
+ if (
26
+ node.type === "ExportNamedDeclaration" ||
27
+ node.type === "ExportDefaultDeclaration"
28
+ ) {
29
+ exportContext = false;
30
+ }
31
+
32
+ if (node.type === "Program") {
33
+ node.body.unshift({
34
+ type: "ImportDeclaration",
35
+ specifiers: [
36
+ {
37
+ type: "ImportSpecifier",
38
+ imported: {
39
+ type: "Identifier",
40
+ name: "server$",
41
+ },
42
+ local: {
43
+ type: "Identifier",
44
+ name: "__react_server_server$__",
45
+ },
46
+ },
47
+ ],
48
+ source: {
49
+ type: "Literal",
50
+ value: "@lazarv/react-server",
51
+ },
52
+ loc: {
53
+ start: { line: 1, column: 0 },
54
+ end: { line: 1, column: 0 },
55
+ },
56
+ });
57
+ }
58
+ },
59
+ enter(node, parent) {
60
+ if (
61
+ node.type === "ExpressionStatement" &&
62
+ node.directive === "use server" &&
63
+ parent.type === "Program" &&
64
+ parent.body.indexOf(node) === 0
65
+ ) {
66
+ serverRoot = true;
67
+ }
68
+
69
+ if (
70
+ node.type === "ExportNamedDeclaration" ||
71
+ node.type === "ExportDefaultDeclaration"
72
+ ) {
73
+ exportContext = true;
74
+ }
75
+
76
+ if (node.type === "BlockStatement") {
77
+ exportContext = false;
78
+ }
79
+
80
+ let actionName;
81
+ let serverNode;
82
+ if (
83
+ (node.type === "FunctionDeclaration" ||
84
+ node.type === "FunctionExpression" ||
85
+ node.type === "ArrowFunctionExpression") &&
86
+ ((serverRoot && exportContext) ||
87
+ (node.body?.type === "BlockStatement" &&
88
+ node.body?.body?.[0]?.type === "ExpressionStatement" &&
89
+ node.body?.body?.[0]?.directive === "use server"))
90
+ ) {
91
+ if (node.id?.type === "Identifier") {
92
+ actionName = node.id.name;
93
+ }
94
+ serverNode = {
95
+ type: "CallExpression",
96
+ callee: {
97
+ type: "Identifier",
98
+ name: "__react_server_server$__",
99
+ },
100
+ arguments: [node],
101
+ loc: node.loc,
102
+ };
103
+ }
104
+
105
+ if (serverNode) {
106
+ if (parent.type === "Property") {
107
+ parent.value = serverNode;
108
+ } else if (parent.type === "ArrayExpression") {
109
+ parent.elements[parent.elements.indexOf(node)] = serverNode;
110
+ } else if (parent.type === "VariableDeclarator") {
111
+ parent.init = serverNode;
112
+ } else if (
113
+ parent.type === "AssignmentExpression" ||
114
+ parent.type === "AssignmentPattern"
115
+ ) {
116
+ parent.right = serverNode;
117
+ } else if (parent.type === "ReturnStatement") {
118
+ parent.argument = serverNode;
119
+ } else if (parent.type === "CallExpression") {
120
+ parent.arguments[parent.arguments.indexOf(node)] = serverNode;
121
+ } else if (parent.type === "ExportDefaultDeclaration") {
122
+ parent.declaration = serverNode;
123
+ } else if (parent.type === "ExportNamedDeclaration") {
124
+ parent.declaration = {
125
+ type: "VariableDeclaration",
126
+ kind: "const",
127
+ declarations: [
128
+ {
129
+ type: "VariableDeclarator",
130
+ id: {
131
+ type: "Identifier",
132
+ name: actionName,
133
+ },
134
+ init: serverNode,
135
+ },
136
+ ],
137
+ loc: parent.declaration.loc,
138
+ };
139
+ } else if (parent.type === "BlockStatement") {
140
+ parent.body[parent.body.indexOf(node)] = {
141
+ type: "VariableDeclaration",
142
+ kind: "const",
143
+ declarations: [
144
+ {
145
+ type: "VariableDeclarator",
146
+ id: {
147
+ type: "Identifier",
148
+ name: actionName,
149
+ },
150
+ init: serverNode,
151
+ },
152
+ ],
153
+ loc: node.loc,
154
+ };
155
+ }
156
+ }
157
+ },
158
+ });
159
+
160
+ const gen = escodegen.generate(result, {
161
+ sourceMap: true,
162
+ sourceMapWithCode: true,
163
+ });
164
+
165
+ return {
166
+ code: gen.code,
167
+ map: gen.map.toString(),
168
+ };
169
+ } catch (e) {
170
+ console.error(e);
171
+ }
172
+ }
173
+ },
174
+ };
175
+ }
@@ -0,0 +1,110 @@
1
+ import cluster from "node:cluster";
2
+ import { once } from "node:events";
3
+ import { createRequire } from "node:module";
4
+ import { availableParallelism } from "node:os";
5
+
6
+ import { loadConfig } from "../../config/index.mjs";
7
+ import { logger } from "../../server/logger.mjs";
8
+ import { init$ as runtime_init$, runtime$ } from "../../server/runtime.mjs";
9
+ import {
10
+ CONFIG_CONTEXT,
11
+ CONFIG_ROOT,
12
+ LOGGER_CONTEXT,
13
+ SERVER_CONTEXT,
14
+ } from "../../server/symbols.mjs";
15
+ import getServerAddresses from "../utils/server-address.mjs";
16
+ import createServer from "./create-server.mjs";
17
+
18
+ const __require = createRequire(import.meta.url);
19
+
20
+ function primary(numCPUs) {
21
+ // fork workers
22
+ for (let i = 0; i < numCPUs; i++) {
23
+ cluster.fork();
24
+ }
25
+
26
+ cluster.on("exit", () => {
27
+ process.exit(1);
28
+ });
29
+
30
+ process.on("SIGINT", () => {
31
+ process.exit(0);
32
+ });
33
+ process.on("SIGTERM", () => {
34
+ process.exit(0);
35
+ });
36
+ }
37
+
38
+ async function worker(root, options) {
39
+ const config = await loadConfig();
40
+ const configRoot = config[CONFIG_ROOT];
41
+
42
+ await runtime_init$(async () => {
43
+ runtime$(CONFIG_CONTEXT, config);
44
+
45
+ if (!configRoot?.logger || typeof configRoot?.logger === "string") {
46
+ const { default: loggerModule } = await import(
47
+ __require.resolve(configRoot?.logger ?? "pino")
48
+ );
49
+ const logger = loggerModule();
50
+ runtime$(LOGGER_CONTEXT, logger);
51
+ } else {
52
+ runtime$(LOGGER_CONTEXT, configRoot?.logger);
53
+ }
54
+
55
+ const server = await createServer(root, options);
56
+
57
+ const port = options.port ?? configRoot.port;
58
+ const host = options.host ?? configRoot.host;
59
+ const listenerHost = host === true ? undefined : host;
60
+
61
+ const listener = server.listen(port, listenerHost);
62
+ runtime$(SERVER_CONTEXT, listener);
63
+ await once(listener, "listening");
64
+
65
+ getServerAddresses(listener).forEach((address) =>
66
+ logger.info(
67
+ `worker #${process.pid} listening on ${
68
+ config.server?.https || options.https ? "https" : "http"
69
+ }://${address.address}:${listener.address().port}`
70
+ )
71
+ );
72
+ });
73
+ }
74
+
75
+ export default async function start(root, options) {
76
+ if (options.build) {
77
+ const { default: build } = await import("../build/action.mjs");
78
+ await build(options.build, options);
79
+ }
80
+
81
+ try {
82
+ const config = await loadConfig();
83
+ const configRoot = config[CONFIG_ROOT];
84
+
85
+ try {
86
+ let numCPUs = parseInt(
87
+ process.env.REACT_SERVER_CLUSTER || configRoot?.cluster,
88
+ 10
89
+ );
90
+
91
+ if (isNaN(numCPUs) && process.env.REACT_SERVER_CLUSTER) {
92
+ numCPUs = availableParallelism();
93
+ }
94
+
95
+ if (
96
+ numCPUs > 1 &&
97
+ (process.env.REACT_SERVER_CLUSTER || configRoot?.cluster) &&
98
+ cluster.isPrimary
99
+ ) {
100
+ primary(numCPUs);
101
+ } else {
102
+ worker(root, options);
103
+ }
104
+ } catch (error) {
105
+ console.error(error);
106
+ }
107
+ } catch (error) {
108
+ console.error(error);
109
+ }
110
+ }
@@ -0,0 +1,111 @@
1
+ import { join } from "node:path";
2
+
3
+ import { createMiddleware } from "@hattip/adapter-node";
4
+ import { compose } from "@hattip/compose";
5
+ import { cookie } from "@hattip/cookie";
6
+ import { cors } from "@hattip/cors";
7
+ import { parseMultipartFormData } from "@hattip/multipart";
8
+
9
+ import { MemoryCache } from "../../memory-cache/index.mjs";
10
+ import { getRuntime, runtime$ } from "../../server/runtime.mjs";
11
+ import {
12
+ CONFIG_CONTEXT,
13
+ CONFIG_ROOT,
14
+ FORM_DATA_PARSER,
15
+ LOGGER_CONTEXT,
16
+ MEMORY_CACHE_CONTEXT,
17
+ } from "../../server/symbols.mjs";
18
+ import notFoundHandler from "../handlers/not-found.mjs";
19
+ import staticHandler from "../handlers/static.mjs";
20
+ import trailingSlashHandler from "../handlers/trailing-slash.mjs";
21
+ import { cwd } from "../sys.mjs";
22
+ import ssrHandler from "./ssr-handler.mjs";
23
+
24
+ export default async function createServer(root, options) {
25
+ const config = getRuntime(CONFIG_CONTEXT)?.[CONFIG_ROOT] ?? {};
26
+ const logger = getRuntime(LOGGER_CONTEXT);
27
+
28
+ const initialRuntime = {
29
+ [MEMORY_CACHE_CONTEXT]: new MemoryCache(),
30
+ [FORM_DATA_PARSER]: parseMultipartFormData,
31
+ };
32
+ runtime$(
33
+ typeof config.runtime === "function"
34
+ ? config.runtime(initialRuntime) ?? initialRuntime
35
+ : {
36
+ ...initialRuntime,
37
+ ...config.runtime,
38
+ }
39
+ );
40
+
41
+ const publicDir =
42
+ typeof config.publicDir === "string" ? config.publicDir : "public";
43
+ const initialHandlers = [
44
+ await staticHandler("{client,assets}", { cwd: ".react-server" }),
45
+ await staticHandler(join(cwd(), ".react-server/dist"), {
46
+ cwd: ".react-server/dist",
47
+ }),
48
+ await staticHandler(join(cwd(), ".react-server"), {
49
+ cwd: ".react-server",
50
+ }),
51
+ ...(config.publicDir !== false
52
+ ? [
53
+ await staticHandler(join(cwd(), publicDir), {
54
+ cwd: publicDir,
55
+ }),
56
+ ]
57
+ : []),
58
+ await trailingSlashHandler(),
59
+ cookie(),
60
+ ...(config.handlers?.pre ?? []),
61
+ await ssrHandler(root),
62
+ ...(config.handlers?.post ?? []),
63
+ await notFoundHandler(),
64
+ ];
65
+ if (options.cors) {
66
+ logger.info("CORS enabled");
67
+ initialHandlers.unshift(cors());
68
+ }
69
+
70
+ const middlewares = createMiddleware(
71
+ compose(
72
+ typeof config.handlers === "function"
73
+ ? config.handlers(initialHandlers) ?? initialHandlers
74
+ : [...initialHandlers, ...(config.handlers ?? [])]
75
+ ),
76
+ {
77
+ origin:
78
+ config.server?.origin ??
79
+ options.origin ??
80
+ process.env.ORIGIN ??
81
+ `${
82
+ config.server?.https || options.https ? "https" : "http"
83
+ }://localhost:${config.server?.port ?? options.port}`,
84
+ trustProxy: config.server?.trustProxy ?? options.trustProxy,
85
+ }
86
+ );
87
+
88
+ const httpsOptions = config.server?.https ?? options.https;
89
+ if (!httpsOptions) {
90
+ const { createServer } = await import("node:http");
91
+ return createServer(middlewares);
92
+ }
93
+
94
+ // #484 fallback to http1 when proxy is needed.
95
+ if (config.server?.proxy) {
96
+ const { createServer } = await import("node:https");
97
+ return createServer(httpsOptions, middlewares);
98
+ } else {
99
+ const { createSecureServer } = await import("node:http2");
100
+ return createSecureServer(
101
+ {
102
+ // Manually increase the session memory to prevent 502 ENHANCE_YOUR_CALM
103
+ // errors on large numbers of requests
104
+ maxSessionMemory: 1000,
105
+ ...httpsOptions,
106
+ allowHTTP1: true,
107
+ },
108
+ middlewares
109
+ );
110
+ }
111
+ }