gemi 0.1.0
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/.eslintrc.js +10 -0
- package/client/Form.tsx +9 -0
- package/client/index.ts +2 -0
- package/client/useRouter.ts +1 -0
- package/http/ApiRouter.ts +1 -0
- package/http/Controller.ts +1 -0
- package/http/ViewRouter.ts +1 -0
- package/http/index.ts +3 -0
- package/package.json +31 -0
- package/server/Server.ts +42 -0
- package/server/dev.ts +176 -0
- package/server/index.ts +1 -0
- package/tsconfig.json +10 -0
- package/tsconfig.lint.json +8 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @type {import("eslint").Linter.Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
root: true,
|
|
4
|
+
extends: ["@repo/eslint-config/react-internal.js"],
|
|
5
|
+
parser: "@typescript-eslint/parser",
|
|
6
|
+
parserOptions: {
|
|
7
|
+
project: "./tsconfig.lint.json",
|
|
8
|
+
tsconfigRootDir: __dirname,
|
|
9
|
+
},
|
|
10
|
+
};
|
package/client/Form.tsx
ADDED
package/client/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function useRouter() {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class ApiRouter {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class Controller {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class ViewRouter {}
|
package/http/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gemi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"devDependencies": {
|
|
5
|
+
"@repo/eslint-config": "*",
|
|
6
|
+
"@repo/typescript-config": "*",
|
|
7
|
+
"@types/eslint": "^8.56.5",
|
|
8
|
+
"@types/node": "^20.11.24",
|
|
9
|
+
"@types/react": "^18.2.61",
|
|
10
|
+
"@types/react-dom": "^18.2.19",
|
|
11
|
+
"eslint": "^8.57.0",
|
|
12
|
+
"react": "^18.2.0",
|
|
13
|
+
"tsup": "^8.1.0",
|
|
14
|
+
"typescript": "^5.3.3"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./http": "./dist/http/index.mjs",
|
|
18
|
+
"./client": "./dist/client/index.mjs",
|
|
19
|
+
"./server": "./dist/server/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"private": false,
|
|
22
|
+
"modulel": true,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"lint": "eslint . --max-warnings 0",
|
|
25
|
+
"build": "tsup ./http/index.ts ./client/index.ts ./server/index.ts --format esm --no-splitting --external vite",
|
|
26
|
+
"build:types": "tsc"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"urlpattern-polyfill": "^10.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/server/Server.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type RequestHandler = (
|
|
2
|
+
req: Request,
|
|
3
|
+
next: () => Response | Promise<Response>,
|
|
4
|
+
) => Response | Promise<Response>;
|
|
5
|
+
|
|
6
|
+
export class Server {
|
|
7
|
+
requestHandlers = new Map<string, RequestHandler>();
|
|
8
|
+
|
|
9
|
+
constructor(private urlPattern: any) {}
|
|
10
|
+
|
|
11
|
+
public use(pattern: string, ...requestHandlers: RequestHandler[]) {
|
|
12
|
+
const requestHandler = requestHandlers.reverse().reduce(
|
|
13
|
+
(acc, handler) => {
|
|
14
|
+
return (req: Request) =>
|
|
15
|
+
handler(req, () => acc(req, () => new Response("404")));
|
|
16
|
+
},
|
|
17
|
+
() => new Response("404", { status: 404 }),
|
|
18
|
+
);
|
|
19
|
+
this.requestHandlers.set(pattern, requestHandler);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public fetch(req: Request) {
|
|
23
|
+
const { pathname } = new URL(req.url);
|
|
24
|
+
const handlerCandidates: [string, RequestHandler][] = [];
|
|
25
|
+
|
|
26
|
+
for (const [p, rh] of this.requestHandlers.entries()) {
|
|
27
|
+
const pattern = new this.urlPattern({ pathname: p });
|
|
28
|
+
if (pattern.test({ pathname })) {
|
|
29
|
+
handlerCandidates.push([p, rh]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const [, handler] = handlerCandidates.sort(
|
|
33
|
+
([pa], [pb]) => pb.length - pa.length,
|
|
34
|
+
)[0];
|
|
35
|
+
|
|
36
|
+
if (handler) {
|
|
37
|
+
return handler(req, () => new Response("404"));
|
|
38
|
+
} else {
|
|
39
|
+
return new Response("404 not found", { status: 404 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/server/dev.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import "react";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Server } from "./Server";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
import { renderToReadableStream } from "react-dom/server.browser";
|
|
6
|
+
|
|
7
|
+
const rootDir = process.cwd();
|
|
8
|
+
|
|
9
|
+
const appDir = path.join(rootDir, "app");
|
|
10
|
+
const frameworkDir = path.join(rootDir, "framework");
|
|
11
|
+
|
|
12
|
+
export async function startDevServer() {
|
|
13
|
+
console.log("Starting server...");
|
|
14
|
+
const root = process.cwd();
|
|
15
|
+
|
|
16
|
+
const { URLPattern } = await import("urlpattern-polyfill");
|
|
17
|
+
const server = new Server(URLPattern);
|
|
18
|
+
|
|
19
|
+
const vite = await (
|
|
20
|
+
await import("vite")
|
|
21
|
+
).createServer({
|
|
22
|
+
root,
|
|
23
|
+
logLevel: "error",
|
|
24
|
+
server: {
|
|
25
|
+
watch: {
|
|
26
|
+
usePolling: true,
|
|
27
|
+
interval: 100,
|
|
28
|
+
},
|
|
29
|
+
hmr: {
|
|
30
|
+
clientPort: 5174,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
appType: "custom",
|
|
34
|
+
resolve: {
|
|
35
|
+
alias: {
|
|
36
|
+
"@/app": appDir,
|
|
37
|
+
"@/framework": frameworkDir,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { app } = await vite.ssrLoadModule(
|
|
43
|
+
path.join(rootDir, "app/bootstrap.ts"),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
vite.watcher.on("change", async (filePath) => {
|
|
47
|
+
if (filePath.includes("/app/views")) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const m = await vite.moduleGraph.getModuleByUrl(filePath, true);
|
|
51
|
+
if (m) {
|
|
52
|
+
await vite.reloadModule(m);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
server.use(
|
|
57
|
+
"*",
|
|
58
|
+
async (req, next) => {
|
|
59
|
+
const { pathname } = new URL(req.url);
|
|
60
|
+
const res = await fetch(`http://localhost:5174/${pathname}`);
|
|
61
|
+
if (res.ok) {
|
|
62
|
+
return res;
|
|
63
|
+
} else {
|
|
64
|
+
return await next();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async (req) => {
|
|
69
|
+
const styles = [];
|
|
70
|
+
for (const [file, modules] of vite.moduleGraph.fileToModulesMap) {
|
|
71
|
+
for (const mod of modules) {
|
|
72
|
+
if (
|
|
73
|
+
mod.file &&
|
|
74
|
+
mod.file.includes(".css") &&
|
|
75
|
+
!mod.file.includes("app.css")
|
|
76
|
+
) {
|
|
77
|
+
const { default: css } = await vite.ssrLoadModule(file);
|
|
78
|
+
const currentFiles: string[] = styles.map((s) => s.file);
|
|
79
|
+
if (currentFiles.includes(mod.file)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
styles.push({
|
|
83
|
+
file: mod.file,
|
|
84
|
+
css,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = await app.handleRequest(req);
|
|
91
|
+
|
|
92
|
+
if (result.kind === "viewError") {
|
|
93
|
+
const { kind, ...payload } = result;
|
|
94
|
+
return new Response(null, {
|
|
95
|
+
...payload,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (result.kind === "apiError") {
|
|
100
|
+
const { kind, data, ...payload } = result;
|
|
101
|
+
return new Response(JSON.stringify(data), {
|
|
102
|
+
...payload,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!result) {
|
|
107
|
+
return new Response("Not found", { status: 404 });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { default: Root } = await vite.ssrLoadModule("framework/Root.tsx");
|
|
111
|
+
|
|
112
|
+
if (result.kind === "viewData") {
|
|
113
|
+
const { data, headers, head } = result;
|
|
114
|
+
return new Response(
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
data,
|
|
117
|
+
head,
|
|
118
|
+
}),
|
|
119
|
+
{
|
|
120
|
+
headers: {
|
|
121
|
+
...headers,
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (result.kind === "view") {
|
|
129
|
+
const { data, headers, head, status = 200 } = result;
|
|
130
|
+
const stream = await renderToReadableStream(
|
|
131
|
+
createElement(Root, { data, styles: [], head }),
|
|
132
|
+
{
|
|
133
|
+
bootstrapScriptContent: `window.__GEMI_DATA__ = ${JSON.stringify(data)};`,
|
|
134
|
+
bootstrapModules: [
|
|
135
|
+
"/framework/refresh.js",
|
|
136
|
+
"/framework/main.tsx",
|
|
137
|
+
"http://localhost:5173/@vite/client",
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return new Response(stream, {
|
|
143
|
+
status,
|
|
144
|
+
headers: { ...headers, "Content-Type": "text/html" },
|
|
145
|
+
});
|
|
146
|
+
} else if (result.kind === "api") {
|
|
147
|
+
const { data, headers, status } = result;
|
|
148
|
+
return new Response(JSON.stringify(data), {
|
|
149
|
+
status,
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
...headers,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
} else if (result.kind === "api_404") {
|
|
156
|
+
return new Response("Not found", { status: 404 });
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
await vite.listen(5174);
|
|
162
|
+
|
|
163
|
+
process.env.ROOT_DIR = rootDir;
|
|
164
|
+
process.env.APP_DIR = appDir;
|
|
165
|
+
|
|
166
|
+
Bun.serve({
|
|
167
|
+
fetch: (req, s) => {
|
|
168
|
+
if (s.upgrade(req)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
return server.fetch.call(server, req);
|
|
172
|
+
},
|
|
173
|
+
port: process.env.PORT || 5173,
|
|
174
|
+
websocket: app.websocket,
|
|
175
|
+
});
|
|
176
|
+
}
|
package/server/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { startDevServer } from "./dev";
|
package/tsconfig.json
ADDED