~repos /edge-city
git clone https://pyrossh.dev/repos/edge-city.git
edge-city is a next level meta-framework for react that runs only on edge runtimes
b76450ae
—
Peter John 2 years ago
support api rendering
packages/example/routes/todos/:id/api.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { todosCollection } from "@/services/collections";
|
|
2
2
|
|
|
3
|
+
const getId = (req) => {
|
|
4
|
+
const url = new URL(req.url);
|
|
3
|
-
|
|
5
|
+
const res = new RegExp("/todos/(.*?)$").exec(url.pathname)
|
|
6
|
+
return res[1];
|
|
7
|
+
}
|
|
4
8
|
|
|
5
9
|
export const onGet = async (req) => {
|
|
6
|
-
const
|
|
10
|
+
const id = getId(req);
|
|
7
11
|
const item = await todosCollection.findOne({
|
|
8
12
|
filter: { id },
|
|
9
13
|
});
|
|
@@ -19,7 +23,7 @@ export const onGet = async (req) => {
|
|
|
19
23
|
export const onPut = async (req) => {
|
|
20
24
|
const item = await req.body();
|
|
21
25
|
const updated = await todosCollection.insertOrReplaceOne(item);
|
|
22
|
-
const data = JSON.stringify(updated);
|
|
26
|
+
const data = JSON.stringify(updated);
|
|
23
27
|
return new Response(data, {
|
|
24
28
|
headers: {
|
|
25
29
|
"Content-Type": "application/json",
|
|
@@ -29,7 +33,7 @@ export const onPut = async (req) => {
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export const onDelete = async (req) => {
|
|
32
|
-
const
|
|
36
|
+
const id = getId(req);
|
|
33
37
|
const res = await todosCollection.deleteOne({
|
|
34
38
|
filter: { id },
|
|
35
39
|
});
|
packages/example/routes/todos/api.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import tigrisDB from "./db.js.js.js";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { todosCollection } from "@/services/collections";
|
|
4
2
|
|
|
5
3
|
export const onGet = async (req) => {
|
|
6
4
|
const cursor = todosCollection.findMany({});
|
packages/example/services/collections.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const getCollection = (name) => {
|
|
2
|
+
let items = [];
|
|
3
|
+
return {
|
|
4
|
+
findMany: () => {
|
|
5
|
+
return {
|
|
6
|
+
toArray: async () => {
|
|
7
|
+
return items;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
findOne: ({ filter }) => {
|
|
12
|
+
return items.find((item) => item.id === filter.id)
|
|
13
|
+
},
|
|
14
|
+
insertOne: (item) => {
|
|
15
|
+
items.push(item);
|
|
16
|
+
return item;
|
|
17
|
+
},
|
|
18
|
+
insertOrReplaceOne: (item) => {
|
|
19
|
+
const index = items.findIndex((it) => it.id === item.id);
|
|
20
|
+
if (index) {
|
|
21
|
+
items[index] = item;
|
|
22
|
+
} else {
|
|
23
|
+
items.push(item);
|
|
24
|
+
}
|
|
25
|
+
return item;
|
|
26
|
+
},
|
|
27
|
+
deleteOne: ({ filter }) => {
|
|
28
|
+
items = items.filter((item) => item.id !== filter.id);
|
|
29
|
+
return { id: filter.id }
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const todosCollection = getCollection("todos");
|
packages/muffinjs/bun.lockb
CHANGED
|
Binary file
|
packages/muffinjs/index.js
CHANGED
|
@@ -63,14 +63,47 @@ const mapDeps = (dir) => {
|
|
|
63
63
|
}, {});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
const hydrateScript = (filePath, initialRouteValue) => {
|
|
67
|
+
return `
|
|
68
|
+
import React from 'react';
|
|
69
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
70
|
+
// import { routerAtom } from "muffinjs/router";
|
|
71
|
+
import Page from "${filePath}";
|
|
72
|
+
|
|
73
|
+
// routerAtom.update(() => (${JSON.stringify(initialRouteValue)}));
|
|
74
|
+
|
|
75
|
+
hydrateRoot(document.getElementById("root"), React.createElement(Page, {}, undefined, false, undefined, this));
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
const radixRouter = createRouter({
|
|
67
80
|
strictTrailingSlash: true,
|
|
68
81
|
routes: mapFiles(),
|
|
69
82
|
});
|
|
70
83
|
|
|
71
84
|
const renderApi = async (filePath, req) => {
|
|
72
|
-
const routeImport = await import(
|
|
85
|
+
const routeImport = await import(path.join(process.cwd(), filePath));
|
|
86
|
+
switch (req.method) {
|
|
87
|
+
case "HEAD":
|
|
88
|
+
return routeImport.onHead(req);
|
|
89
|
+
case "OPTIONS":
|
|
90
|
+
return routeImport.onOptions(req);
|
|
91
|
+
case "GET":
|
|
92
|
+
return routeImport.onGet(req);
|
|
93
|
+
case "POST":
|
|
94
|
+
return routeImport.onPost(req);
|
|
95
|
+
case "PUT":
|
|
96
|
+
return routeImport.onPut(req);
|
|
97
|
+
case "PATCH":
|
|
98
|
+
return routeImport.onPatch(req);
|
|
99
|
+
case "DELETE":
|
|
73
|
-
|
|
100
|
+
return routeImport.onDelete(req);
|
|
101
|
+
default:
|
|
102
|
+
return new Response(`{"message": "route not found"}`, {
|
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
104
|
+
status: 404,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
const renderPage = async (filePath, url, params) => {
|
|
@@ -86,29 +119,30 @@ const renderPage = async (filePath, url, params) => {
|
|
|
86
119
|
routerAtom.update(() => initialRouteValue);
|
|
87
120
|
const routeImport = await import(path.join(process.cwd(), filePath));
|
|
88
121
|
const packageJson = await import(path.join(process.cwd(), "package.json"));
|
|
89
|
-
const dependencies = packageJson.default.dependencies;
|
|
90
122
|
const devTag = !isProd ? "?dev" : "";
|
|
91
|
-
const
|
|
123
|
+
const nodeDeps = Object.keys(packageJson.default.dependencies).reduce((acc, dep) => {
|
|
92
|
-
acc[dep] = `https://esm.sh/${dep}@${dependencies[dep]}${devTag}`;
|
|
124
|
+
acc[dep] = `https://esm.sh/${dep}@${packageJson.default.dependencies[dep]}${devTag}`;
|
|
93
125
|
return acc;
|
|
94
126
|
}, {})
|
|
95
127
|
const Page = routeImport.default;
|
|
96
128
|
const components = mapDeps("components");
|
|
97
129
|
const containers = mapDeps("containers");
|
|
98
|
-
const parottaVersion = dependencies["parotta"];
|
|
130
|
+
const parottaVersion = packageJson.default.dependencies["parotta"];
|
|
131
|
+
const cssFile = `${filePath.replace("jsx", "css")}`;
|
|
99
132
|
const stream = await renderToReadableStream(
|
|
100
133
|
<html lang="en">
|
|
101
134
|
<head>
|
|
102
|
-
<link rel="
|
|
135
|
+
<link rel="preload" href={cssFile} as="style" />
|
|
136
|
+
<link rel="stylesheet" href={cssFile} />
|
|
103
137
|
<script type="importmap" dangerouslySetInnerHTML={{
|
|
104
138
|
__html: JSON.stringify(
|
|
105
139
|
{
|
|
106
140
|
"imports": {
|
|
107
|
-
...imports,
|
|
108
141
|
"radix3": `https://esm.sh/radix3`,
|
|
109
142
|
"react-dom/client": `https://esm.sh/react-dom@18.2.0/client${devTag}`,
|
|
110
143
|
"react/jsx-dev-runtime": `https://esm.sh/react@18.2.0/jsx-dev-runtime${devTag}`,
|
|
111
144
|
"parotta/router": `https://esm.sh/parotta@${parottaVersion}`,
|
|
145
|
+
...nodeDeps,
|
|
112
146
|
...components,
|
|
113
147
|
...containers,
|
|
114
148
|
}
|
|
@@ -117,16 +151,11 @@ const renderPage = async (filePath, url, params) => {
|
|
|
117
151
|
}}>
|
|
118
152
|
</script>
|
|
119
153
|
<script type="module" defer dangerouslySetInnerHTML={{
|
|
120
|
-
__html: `
|
|
121
|
-
import React from 'react';
|
|
122
|
-
import { hydrateRoot } from 'react-dom/client';
|
|
123
|
-
// import { routerAtom } from "muffinjs/router";
|
|
124
|
-
import Page from "${filePath}";
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
__html: hydrateScript(filePath, initialRouteValue)
|
|
127
|
-
|
|
128
|
-
hydrateRoot(document.getElementById("root"), React.createElement(Page, {}, undefined, false, undefined, this));
|
|
129
|
-
|
|
155
|
+
}}></script>
|
|
156
|
+
<title>
|
|
157
|
+
Parotta
|
|
158
|
+
</title>
|
|
130
159
|
</head>
|
|
131
160
|
<body>
|
|
132
161
|
<div id="root">
|
|
@@ -210,7 +239,7 @@ export default {
|
|
|
210
239
|
if (match.file) {
|
|
211
240
|
return sendFile(`/static${match.file}`);
|
|
212
241
|
}
|
|
213
|
-
if (match.page) {
|
|
242
|
+
if (match.page && req.headers.get("Accept")?.includes('text/html')) {
|
|
214
243
|
return renderPage(`/routes${match.page}`, url, match.params);
|
|
215
244
|
}
|
|
216
245
|
if (match.api) {
|
|
@@ -222,4 +251,11 @@ export default {
|
|
|
222
251
|
status: 404,
|
|
223
252
|
});
|
|
224
253
|
},
|
|
254
|
+
// error(error) {
|
|
255
|
+
// return new Response(`<pre>${error}\n${error.stack}</pre>`, {
|
|
256
|
+
// headers: {
|
|
257
|
+
// "Content-Type": "text/html",
|
|
258
|
+
// },
|
|
259
|
+
// });
|
|
260
|
+
// },
|
|
225
|
-
}
|
|
261
|
+
}
|
packages/muffinjs/package.json
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"autoprefixer": "^10.4.14",
|
|
15
|
+
"mime-types": "2.1.35",
|
|
15
16
|
"postcss": "^8.4.21",
|
|
16
17
|
"postcss-custom-media": "^9.1.2",
|
|
17
18
|
"postcss-nesting": "^11.2.1",
|
|
18
19
|
"postcss-normalize": "^10.0.1",
|
|
19
20
|
"radix3": "^1.0.0",
|
|
20
|
-
"walkdir": "0.4.1"
|
|
21
|
+
"walkdir": "0.4.1"
|
|
21
|
-
"mime-types": "2.1.35"
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
24
|
"muffin": "./bin/muffin"
|