edge-city
git clone https://git.pyrossh.dev/edge-city
edge-city is a next level meta-framework for react that runs only on edge runtimes
4d1436e
— pyrossh
2023-05-22T22:34:00+05:30
improve cli
Generated files not shown: pnpm-lock.yaml
- cli.js +45 -63
- example/package.json +3 -4
- example/services/todos.service.js +1 -1
- example/services/todos.service.test.js +0 -1
- index.test.js +15 -0
- package.json +10 -3
- readme.md +6 -9
cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import yargs from 'yargs'
|
|
3
|
+
import { hideBin } from 'yargs/helpers'
|
|
3
4
|
import esbuild from 'esbuild';
|
|
4
5
|
import resolve from 'esbuild-plugin-resolve';
|
|
5
6
|
import fs from "fs";
|
|
@@ -13,46 +14,9 @@ import bytes from 'bytes';
|
|
|
13
14
|
import pc from 'picocolors';
|
|
14
15
|
import ms from 'ms';
|
|
15
16
|
|
|
16
|
-
const __dirname = path.dirname(import.meta.url.replace("file://", ""));
|
|
17
|
-
const appName = "edge-city";
|
|
18
|
-
const version = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))).version;
|
|
19
|
-
const cli = meow(`
|
|
20
|
-
${appName} v${version}
|
|
21
|
-
|
|
22
|
-
Usage
|
|
23
|
-
$ ${appName} build cloudflare
|
|
24
|
-
$ ${appName} build vercel
|
|
25
|
-
`, {
|
|
26
|
-
importMeta: import.meta,
|
|
27
|
-
autoVersion: true,
|
|
28
|
-
});
|
|
29
|
-
if (cli.input.length != 2) {
|
|
30
|
-
cli.showHelp();
|
|
31
|
-
process.exit(0);
|
|
32
|
-
}
|
|
33
|
-
console.log(`${appName} v${version}`)
|
|
34
|
-
console.log(`running with NODE_ENV=${process.env.NODE_ENV}`);
|
|
35
|
-
|
|
36
|
-
const ensureDir = (d) => {
|
|
37
|
-
if (!fs.existsSync(d)) {
|
|
38
|
-
fs.mkdirSync(d, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const cleanDir = (d) => {
|
|
43
|
-
if (fs.existsSync(d)) {
|
|
44
|
-
fs.rmSync(d, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
17
|
const isProd = process.env.NODE_ENV === "production";
|
|
49
18
|
const buildDir = path.join(process.cwd(), "build");
|
|
50
19
|
const staticDir = path.join(buildDir, "static");
|
|
51
|
-
const createDirs = () => {
|
|
52
|
-
cleanDir(buildDir);
|
|
53
|
-
ensureDir(buildDir);
|
|
54
|
-
ensureDir(staticDir);
|
|
55
|
-
}
|
|
56
20
|
|
|
57
21
|
const recordSize = (buildStart, dest) => {
|
|
58
22
|
const outLength = fs.statSync(dest).size;
|
|
@@ -93,29 +57,6 @@ const bundleJs = async ({ entryPoints, outfile, ...options }, plg) => {
|
|
|
93
57
|
return result;
|
|
94
58
|
}
|
|
95
59
|
|
|
96
|
-
// const bundleBun = async (r, type) => {
|
|
97
|
-
// const buildStart = Date.now();
|
|
98
|
-
// const shortName = r.replace(process.cwd(), "").replace("/page.jsx", "");
|
|
99
|
-
// const result = await Bun.build({
|
|
100
|
-
// entrypoints: [r],
|
|
101
|
-
// outdir: `${process.cwd()}/bb/functions/${shortName}`,
|
|
102
|
-
// });
|
|
103
|
-
// if (!result.success) {
|
|
104
|
-
// console.error("Build failed");
|
|
105
|
-
// for (const message of result.logs) {
|
|
106
|
-
// // Bun will pretty print the message object
|
|
107
|
-
// console.error(message);
|
|
108
|
-
// }
|
|
109
|
-
// }
|
|
110
|
-
// for (const o of result.outputs) {
|
|
111
|
-
// const outLength = (await o.arrayBuffer()).byteLength;
|
|
112
|
-
// const builtTime = ms(Date.now() - buildStart);
|
|
113
|
-
// console.log(
|
|
114
|
-
// `✓ Bundled ${o.kind} ${o.path.replace(process.cwd() + "/bb", "")} ${pc.cyan(`(${bytes(outLength)})`)} ${pc.gray(`[${builtTime}]`)}`
|
|
115
|
-
// );
|
|
116
|
-
// }
|
|
117
|
-
// }
|
|
118
|
-
|
|
119
60
|
const buildRouteMap = (routes) => {
|
|
120
61
|
const buildStart = new Date();
|
|
121
62
|
const routemap = routes.reduce((acc, r) => {
|
|
@@ -277,14 +218,55 @@ const bundleCss = async () => {
|
|
|
277
218
|
fs.writeFileSync(`${process.cwd()}/build/static/css/app.css`, result.toString());
|
|
278
219
|
}
|
|
279
220
|
|
|
221
|
+
const ensureDir = (d) => {
|
|
222
|
+
if (!fs.existsSync(d)) {
|
|
223
|
+
fs.mkdirSync(d, { recursive: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const cleanDir = (d) => {
|
|
228
|
+
if (fs.existsSync(d)) {
|
|
229
|
+
fs.rmSync(d, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const createDirs = () => {
|
|
233
|
+
cleanDir(buildDir);
|
|
234
|
+
ensureDir(buildDir);
|
|
235
|
+
ensureDir(staticDir);
|
|
236
|
+
}
|
|
237
|
+
|
|
280
|
-
const
|
|
238
|
+
const build = async () => {
|
|
281
239
|
createDirs();
|
|
282
240
|
await bundlePages();
|
|
283
241
|
// await bundleServices();
|
|
284
242
|
await bundleCss();
|
|
285
243
|
}
|
|
286
244
|
|
|
245
|
+
yargs(hideBin(process.argv))
|
|
246
|
+
.scriptName("edge-city")
|
|
247
|
+
.usage('$0 <cmd> [args]')
|
|
248
|
+
.command('build', 'build the project', (y) => {
|
|
249
|
+
y.option('platform', {
|
|
250
|
+
alias: 'p',
|
|
251
|
+
description: 'The edge platform',
|
|
252
|
+
choices: ['cloudflare', 'vercel'],
|
|
253
|
+
})
|
|
254
|
+
.demandOption("p")
|
|
255
|
+
}, ({ platform }) => {
|
|
256
|
+
build(platform);
|
|
257
|
+
})
|
|
258
|
+
.command('dev', 'run the dev server', (y) => {
|
|
259
|
+
y.option('platform', {
|
|
260
|
+
alias: 'p',
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'cloudflare or vercel',
|
|
263
|
+
choices: ['cloudflare', 'vercel']
|
|
264
|
+
})
|
|
265
|
+
}, ({ platform }) => {
|
|
266
|
+
build(platform);
|
|
267
|
+
})
|
|
268
|
+
.demandCommand(1)
|
|
287
|
-
|
|
269
|
+
.parse()
|
|
288
270
|
|
|
289
271
|
// const renderJs = async (srcFile) => {
|
|
290
272
|
// try {
|
example/package.json
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
"name": "edge-city-example",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"dev": "edge-city
|
|
5
|
+
"dev": "edge-city dev -p cloudflare",
|
|
6
|
-
"dev-server": "wrangler pages dev static --local --live-reload",
|
|
7
|
-
"build": "
|
|
6
|
+
"build:prod": " edge-city build -p cloudflare",
|
|
8
|
-
"test": "
|
|
7
|
+
"test": "jest",
|
|
9
8
|
"test-e2e": "playwright test"
|
|
10
9
|
},
|
|
11
10
|
"dependencies": {
|
example/services/todos.service.js
CHANGED
|
@@ -11,7 +11,7 @@ const todos = pgTable('todos', {
|
|
|
11
11
|
updatedAt: date('updatedAt'),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
const createSchema = z.object({
|
|
14
|
+
export const createSchema = z.object({
|
|
15
15
|
text: z.string().nonempty("please enter some text"),
|
|
16
16
|
completed: z.boolean(),
|
|
17
17
|
});
|
example/services/todos.service.test.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { test, expect } from "bun:test";
|
|
2
1
|
import { createSchema } from "./todos.service";
|
|
3
2
|
|
|
4
3
|
test("validate createSchema", () => {
|
index.test.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
describe('A thing', () => {
|
|
2
|
+
it('should work', () => {
|
|
3
|
+
expect(1).toEqual(1);
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
it('should be ok', () => {
|
|
7
|
+
expect(1).toEqual(1);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('a nested thing', () => {
|
|
11
|
+
it('should work', () => {
|
|
12
|
+
expect(1).toEqual(1);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
package.json
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">= 20"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest index.test.js"
|
|
8
11
|
},
|
|
9
12
|
"dependencies": {
|
|
10
13
|
"history": "^5.3.0",
|
|
@@ -15,7 +18,7 @@
|
|
|
15
18
|
"autoprefixer": "^10.4.14",
|
|
16
19
|
"bytes": "3.1.2",
|
|
17
20
|
"esbuild": "0.17.19",
|
|
18
|
-
"
|
|
21
|
+
"yargs": "17.7.2",
|
|
19
22
|
"mime-types": "2.1.35",
|
|
20
23
|
"ms": "2.1.3",
|
|
21
24
|
"picocolors": "1.0.0",
|
|
@@ -23,7 +26,8 @@
|
|
|
23
26
|
"postcss-custom-media": "^9.1.2",
|
|
24
27
|
"postcss-nesting": "^11.2.1",
|
|
25
28
|
"walkdir": "0.4.1",
|
|
26
|
-
"esbuild-plugin-resolve": "2.0.0"
|
|
29
|
+
"esbuild-plugin-resolve": "2.0.0",
|
|
30
|
+
"jest": "29.5.0"
|
|
27
31
|
},
|
|
28
32
|
"peerDependencies": {
|
|
29
33
|
"react": "*",
|
|
@@ -34,5 +38,8 @@
|
|
|
34
38
|
},
|
|
35
39
|
"bin": {
|
|
36
40
|
"edge-city": "cli.js"
|
|
41
|
+
},
|
|
42
|
+
"jest": {
|
|
43
|
+
"verbose": true
|
|
37
44
|
}
|
|
38
45
|
}
|
readme.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# Edge City
|
|
2
2
|
|
|
3
3
|
edge-city is a next level meta-framework for react that runs only on edge runtimes.
|
|
4
|
-
It uses esbuild as its bundler/transpiler.
|
|
5
|
-
It uses file system routing
|
|
4
|
+
It uses file system routing with streaming SSR + CSR to render pages.
|
|
6
5
|
It is very opionated and has set of idiomatic ways of doing things.
|
|
7
6
|
It has an inbuilt rpc mechanism to access server resources instead of a typical REST API.
|
|
8
7
|
It aims to have almost the same router api as nextjs router for ease of use.
|
|
9
8
|
|
|
10
9
|
During development each request for a page is executed in a separate edge-runtime (miniflare/vercel) vm.
|
|
11
|
-
During production each page is packaged to an esm function adapted to the platform of your choice.
|
|
10
|
+
During production each page is packaged to an esm function adapted to the platform of your choice.
|
|
12
11
|
|
|
13
12
|
## Why?
|
|
14
|
-
Beacause,
|
|
15
|
-
* Its really hard to have a streaming SSR + CSR setup in nextjs currently.
|
|
13
|
+
* Its really hard to have a streaming SSR + CSR with automatic data rehydration setup in nextjs currently.
|
|
16
|
-
* There is no framework which runs your code in an edge simulated environment during development
|
|
14
|
+
* There is no meta-framework which runs your code in an edge simulated environment during development.
|
|
17
15
|
|
|
18
16
|
## Requirements
|
|
19
17
|
1. `node >= v20`
|
|
@@ -22,9 +20,8 @@ Beacause,
|
|
|
22
20
|
|
|
23
21
|
### Supported platforms
|
|
24
22
|
1. [Cloudflare page functions](https://developers.cloudflare.com/pages/platform/functions/routing/)
|
|
25
|
-
2. [Vercel edge functions](https://vercel.com/docs/concepts/functions/edge-functions)
|
|
23
|
+
2. [TODO][Vercel edge functions](https://vercel.com/docs/concepts/functions/edge-functions)
|
|
26
|
-
3. [Netlify edge functions](https://docs.netlify.com/edge-functions/overview/)
|
|
24
|
+
3. [TODO][Netlify edge functions](https://docs.netlify.com/edge-functions/overview/)
|
|
27
|
-
4. [Deno Deploy](https://deno.com/deploy)
|
|
28
25
|
|
|
29
26
|
## Developing
|
|
30
27
|
|