edge-city

#react#js#ssr

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import meow from 'meow';
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 main = async () => {
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
- main();
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 build cloudflare",
5
+ "dev": "edge-city dev -p cloudflare",
6
- "dev-server": "wrangler pages dev static --local --live-reload",
7
- "build": "NODE_ENV=production edge-city build cloudflare",
6
+ "build:prod": " edge-city build -p cloudflare",
8
- "test": "bun 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": ">= v20"
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
- "meow": "12.0.1",
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 (similar to nextjs app router) with streaming SSR + CSR to render pages.
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 and targets only edge for production.
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