~repos /edge-city

#react#js#ssr

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


0754c43a Peter John

2 years ago
add router
.gitignore CHANGED
@@ -1 +1,2 @@
1
- node_modules
1
+ node_modules
2
+ .vercel
package.json CHANGED
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "name": "my-project",
3
3
  "version": "1.0.0",
4
+ "workspaces": [
4
- "workspaces": ["packages/example", "packages/muffinjs"]
5
+ "packages/example",
6
+ "packages/muffinjs"
7
+ ]
5
8
  }
packages/example/Dockerfile ADDED
@@ -0,0 +1,6 @@
1
+ FROM oven/bun
2
+
3
+ WORKDIR /app
4
+ COPY . /app
5
+
6
+ CMD ["bun", "start"]
packages/example/package.json CHANGED
@@ -3,7 +3,9 @@
3
3
  "type": "module",
4
4
  "scripts": {
5
5
  "dev": "bun --hot main.js",
6
- "start": "NODE_ENV=production bun main.js"
6
+ "start": "NODE_ENV=production bun main.js",
7
+ "build": "docker build . -t example",
8
+ "run": "docker run -p 3000:3000 example"
7
9
  },
8
10
  "dependencies": {
9
11
  "muffinjs": "0.1.0",
packages/example/routes/{health/api.js → api.js} RENAMED
File without changes
packages/example/routes/{index/page.css → page.css} RENAMED
File without changes
packages/example/routes/{index/page.jsx → page.jsx} RENAMED
File without changes
packages/example/routes/todos/{[id]/server.js → :id/api.js} RENAMED
@@ -1,4 +1,4 @@
1
- import tigrisDB from "./db.js.js.js";
1
+ import tigrisDB from "./db.js.js.js.js.js.js.js.js";
2
2
 
3
3
  export const todosCollection = tigrisDB.getCollection("todoItems");
4
4
 
packages/muffinjs/bin/muffin CHANGED
File without changes
packages/muffinjs/bun.lockb ADDED
Binary file
packages/muffinjs/index.js CHANGED
@@ -1,18 +1,18 @@
1
- // import './jsxPlugin.js';
2
- import React from 'react';
3
1
  import path from 'path';
4
- import { rmSync, readFileSync } from "fs";
2
+ import walkdir from 'walkdir';
5
3
  import postcss from "postcss"
6
4
  import autoprefixer from "autoprefixer";
7
5
  import postcssCustomMedia from "postcss-custom-media";
8
6
  // import postcssNormalize from 'postcss-normalize';
9
7
  import postcssNesting from "postcss-nesting";
10
8
  import { renderToReadableStream } from 'react-dom/server';
9
+ import { createRouter } from 'radix3';
10
+ import mimeTypes from "mime-types";
11
11
  import { routerAtom } from './router.js';
12
12
 
13
13
  console.log("running in folder:", path.basename(process.cwd()), "env:", process.env.NODE_ENV);
14
- console.log("deleting cache");
14
+ // console.log("deleting cache");
15
- rmSync(path.join(process.cwd(), ".cache"), { force: true, recursive: true })
15
+ // rmSync(path.join(process.cwd(), ".cache"), { force: true, recursive: true })
16
16
 
17
17
  const isProd = process.env.NODE_ENV === "production";
18
18
 
@@ -22,7 +22,48 @@ const transpiler = new Bun.Transpiler({
22
22
  jsxOptimizationInline: true,
23
23
  });
24
24
 
25
+
26
+ const mapFiles = () => {
27
+ const routes = {};
28
+ const dirs = walkdir.sync(path.join(process.cwd(), "routes"))
29
+ .map((s) => s.replace(process.cwd(), "")
30
+ .replace("/routes", "")
31
+ // .replaceAll("[", ":")
32
+ // .replaceAll("]", "")
33
+ );
34
+ dirs.filter((p) => p.includes('page.jsx'))
35
+ .map((s) => ({ path: s, route: s.replace("/page.jsx", "") }))
36
+ .forEach((page) => {
37
+ const key = page.route || "/";
38
+ routes[key] = { key: key, page: page.path };
39
+ });
40
+ dirs.filter((p) => p.includes('api.js'))
41
+ .map((s) => s.replace(process.cwd(), ""))
42
+ .map((s) => ({ path: s, route: s.replace("/api.js", "") }))
43
+ .forEach((api) => {
44
+ const key = api.route || "/";
45
+ routes[key] = routes[key] || { key };
46
+ routes[key].api = api.path;
47
+ });
48
+ walkdir.sync(path.join(process.cwd(), "static"))
49
+ .map((s) => s.replace(process.cwd(), "").replace("/static", ""))
50
+ .forEach((route) => {
51
+ routes[route] = { key: route, file: route }
52
+ });
53
+ return routes;
54
+ }
55
+
56
+ const radixRouter = createRouter({
57
+ strictTrailingSlash: true,
58
+ routes: mapFiles(),
59
+ });
60
+ console.log(radixRouter.lookup('/'));
61
+ console.log(radixRouter.lookup('/about'));
62
+ console.log(radixRouter.lookup('/todos'));
63
+ console.log(radixRouter.lookup('/todos/123'));
64
+ console.log(radixRouter.lookup('/robots.txt'));
65
+
25
- const renderApi = async (route, req) => {
66
+ const renderApi = async (filePath, req) => {
26
67
  const routeImport = await import(route.filePath);
27
68
  console.log('routeImport', routeImport);
28
69
  }
@@ -57,6 +98,7 @@ const renderPage = async (filePath, url, params) => {
57
98
  {
58
99
  "imports": {
59
100
  ...imports,
101
+ "radix3": `https://esm.sh/radix3`,
60
102
  "react-dom/client": `https://esm.sh/react-dom@18.2.0/client${devTag}`,
61
103
  "react/jsx-dev-runtime": `https://esm.sh/react@18.2.0/jsx-dev-runtime${devTag}`,
62
104
  "muffinjs": "https://esm.sh/muffinjs",
@@ -93,27 +135,57 @@ const renderPage = async (filePath, url, params) => {
93
135
  }
94
136
 
95
137
  const renderCss = async (url) => {
138
+ try {
96
- const cssText = readFileSync(path.join(process.cwd(), url.pathname), "utf-8");
139
+ const cssText = await Bun.file(path.join(process.cwd(), url.pathname)).text();
97
- const result = await postcss([
140
+ const result = await postcss([
98
- autoprefixer(),
141
+ autoprefixer(),
99
- postcssCustomMedia(),
142
+ postcssCustomMedia(),
100
- // postcssNormalize({ browsers: 'last 2 versions' }),
143
+ // postcssNormalize({ browsers: 'last 2 versions' }),
101
- postcssNesting,
144
+ postcssNesting,
102
- ]).process(cssText);
145
+ ]).process(cssText, { from: url.pathname, to: url.pathname });
103
- return new Response(result.css, {
146
+ return new Response(result.css, {
104
- headers: { 'Content-Type': 'text/css' },
147
+ headers: { 'Content-Type': 'text/css' },
105
- status: 200,
148
+ status: 200,
106
- });
149
+ });
150
+ } catch (err) {
151
+ return new Response(`Not Found`, {
152
+ headers: { 'Content-Type': 'text/html' },
153
+ status: 404,
154
+ });
155
+ }
107
156
  }
108
157
 
109
158
  const renderJs = async (url) => {
159
+ try {
110
- const jsText = readFileSync(path.join(process.cwd(), url.pathname), "utf-8");
160
+ const jsText = await Bun.file(path.join(process.cwd(), url.pathname)).text();
111
- const result = await transpiler.transform(jsText);
161
+ const result = await transpiler.transform(jsText);
112
- const js = result.replaceAll(`import"./page.css";`, "");
162
+ const js = result.replaceAll(`import"./page.css";`, "");
113
- return new Response(js, {
163
+ return new Response(js, {
114
- headers: { 'Content-Type': 'application/javascript' },
164
+ headers: { 'Content-Type': 'application/javascript' },
115
- status: 200,
165
+ status: 200,
116
- });
166
+ });
167
+ } catch (err) {
168
+ return new Response(`Not Found`, {
169
+ headers: { 'Content-Type': 'text/html' },
170
+ status: 404,
171
+ });
172
+ }
173
+ }
174
+
175
+ const sendFile = async (file) => {
176
+ try {
177
+ const contentType = mimeTypes.lookup(file) || "application/octet-stream";
178
+ const stream = await Bun.file(path.join(process.cwd(), file)).stream();
179
+ return new Response(stream, {
180
+ headers: { 'Content-Type': contentType },
181
+ status: 200,
182
+ });
183
+ } catch (err) {
184
+ return new Response(`Not Found`, {
185
+ headers: { 'Content-Type': 'text/html' },
186
+ status: 404,
187
+ });
188
+ }
117
189
  }
118
190
 
119
191
  export default {
@@ -126,14 +198,17 @@ export default {
126
198
  if (url.pathname.endsWith(".js") || url.pathname.endsWith(".jsx")) {
127
199
  return renderJs(url);
128
200
  }
129
- if (url.pathname.includes("/favicon")) {
201
+ const match = radixRouter.lookup(url.pathname);
202
+ if (match) {
203
+ if (match.file) {
130
- return new Response(`Not Found`, {
204
+ return sendFile(`/static${match.file}`);
131
- headers: { 'Content-Type': 'text/html' },
132
- status: 404,
133
- });
134
- }
205
+ }
135
- if (url.pathname.includes("/")) {
206
+ if (match.page) {
136
- return renderPage("/routes/index/page.jsx", url, {});
207
+ return renderPage(`/routes${match.page}`, url, match.params);
208
+ }
209
+ if (match.api) {
210
+ return renderApi(`/routes${match.api}`, req);
211
+ }
137
212
  }
138
213
  return new Response(`Not Found`, {
139
214
  headers: { 'Content-Type': 'text/html' },
packages/muffinjs/package.json CHANGED
@@ -15,7 +15,10 @@
15
15
  "postcss": "^8.4.21",
16
16
  "postcss-custom-media": "^9.1.2",
17
17
  "postcss-nesting": "^11.2.1",
18
- "postcss-normalize": "^10.0.1"
18
+ "postcss-normalize": "^10.0.1",
19
+ "radix3": "^1.0.0",
20
+ "walkdir": "0.4.1",
21
+ "mime-types": "2.1.35"
19
22
  },
20
23
  "bin": {
21
24
  "muffin": "./bin/muffin"
packages/muffinjs/router.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // import { signal, useSignal } from "@preact/signals-react";
2
+ import { createRouter } from 'radix3';
2
3
  import { atom, useAtom } from "./atom.js";
3
4
 
4
5
  export const routerAtom = atom({