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
c35b08d
— Peter John
2023-05-22T11:10:26+05:30
rename app
Generated files not shown: package-lock.json, pnpm-lock.yaml
- packages/cli/index.js → edge-city/cli.js +18 -11
- {packages/runtime → edge-city}/index.js +0 -0
- {packages/cli → edge-city}/package.json +12 -5
- {packages/example → example}/.gitignore +0 -0
- {packages/example → example}/.vscode/extensions.json +0 -0
- {packages/example → example}/.vscode/settings.json +0 -0
- {packages/example → example}/components/Counter/Counter.jsx +0 -0
- {packages/example → example}/components/Layout/Layout.css +0 -0
- {packages/example → example}/components/Layout/Layout.jsx +1 -1
- {packages/example → example}/components/Timer/Timer.jsx +0 -0
- {packages/example → example}/components/Todo/Todo.css +0 -0
- {packages/example → example}/components/Todo/Todo.jsx +0 -0
- {packages/example → example}/db/index.js +0 -0
- {packages/example → example}/db/migrations/0000_empty_shatterstar.sql +0 -0
- {packages/example → example}/db/migrations/meta/0000_snapshot.json +0 -0
- {packages/example → example}/db/migrations/meta/_journal.json +0 -0
- {packages/example → example}/drizzle.config.json +0 -0
- {packages/example → example}/jsconfig.json +0 -0
- {packages/example → example}/package.json +5 -9
- {packages/example → example}/pages/_404/page.css +0 -0
- {packages/example → example}/pages/_404/page.jsx +0 -0
- {packages/example → example}/pages/_500/page.css +0 -0
- {packages/example → example}/pages/_500/page.jsx +0 -0
- {packages/example → example}/pages/about/page.css +0 -0
- {packages/example → example}/pages/about/page.jsx +8 -5
- {packages/example → example}/pages/page.css +0 -0
- {packages/example → example}/pages/page.jsx +2 -2
- {packages/example → example}/pages/page.spec.js +1 -1
- {packages/example → example}/playwright.config.js +3 -4
- example/readme.md +22 -0
- {packages/example → example}/services/auth.service.js +0 -0
- {packages/example → example}/services/todos.service.js +0 -0
- {packages/example → example}/services/todos.service.test.js +0 -0
- {packages/example → example}/static/favicon.ico +0 -0
- {packages/example → example}/static/logo192.png +0 -0
- {packages/example → example}/static/logo512.png +0 -0
- {packages/example → example}/static/manifest.json +0 -0
- {packages/example → example}/static/robots.txt +0 -0
- {packages/cli → example/static}/todos/page.css +0 -0
- {packages/cli → example/static}/todos/page.jsx +1 -1
- packages/example/readme.md +0 -18
- packages/runtime/package.json +0 -16
- pnpm-workspace.yaml +2 -1
- readme.md +16 -9
packages/cli/index.js → edge-city/cli.js
RENAMED
|
@@ -14,13 +14,14 @@ import pc from 'picocolors';
|
|
|
14
14
|
import ms from 'ms';
|
|
15
15
|
|
|
16
16
|
const __dirname = path.dirname(import.meta.url.replace("file://", ""));
|
|
17
|
+
const appName = "edge-city";
|
|
17
18
|
const version = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))).version;
|
|
18
19
|
const cli = meow(`
|
|
19
|
-
|
|
20
|
+
${appName} v${version}
|
|
20
21
|
|
|
21
22
|
Usage
|
|
22
|
-
$
|
|
23
|
+
$ ${appName} build cloudflare
|
|
23
|
-
$
|
|
24
|
+
$ ${appName} build vercel
|
|
24
25
|
`, {
|
|
25
26
|
importMeta: import.meta,
|
|
26
27
|
autoVersion: true,
|
|
@@ -29,7 +30,7 @@ if (cli.input.length != 2) {
|
|
|
29
30
|
cli.showHelp();
|
|
30
31
|
process.exit(0);
|
|
31
32
|
}
|
|
32
|
-
console.log(`
|
|
33
|
+
console.log(`${appName} v${version}`)
|
|
33
34
|
console.log(`running with NODE_ENV=${process.env.NODE_ENV}`);
|
|
34
35
|
|
|
35
36
|
const ensureDir = (d) => {
|
|
@@ -38,11 +39,17 @@ const ensureDir = (d) => {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
const cleanDir = (d) => {
|
|
43
|
+
if (fs.existsSync(d)) {
|
|
44
|
+
fs.rmSync(d, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
const isProd = process.env.NODE_ENV === "production";
|
|
42
49
|
const buildDir = path.join(process.cwd(), "build");
|
|
43
50
|
const staticDir = path.join(buildDir, "static");
|
|
44
51
|
const createDirs = () => {
|
|
45
|
-
|
|
52
|
+
cleanDir(buildDir);
|
|
46
53
|
ensureDir(buildDir);
|
|
47
54
|
ensureDir(staticDir);
|
|
48
55
|
}
|
|
@@ -133,12 +140,12 @@ const bundlePages = async () => {
|
|
|
133
140
|
const buildStart = Date.now();
|
|
134
141
|
const outfile = `build/functions${r.out}`;
|
|
135
142
|
await bundleJs({ entryPoints: [r.in], outfile }, {
|
|
136
|
-
name: "
|
|
143
|
+
name: "page-plugin",
|
|
137
144
|
setup(build) {
|
|
138
145
|
build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
|
|
139
146
|
const data = fs.readFileSync(args.path);
|
|
140
147
|
const newSrc = `
|
|
141
|
-
import { renderPage } from "
|
|
148
|
+
import { renderPage } from "edge-city";
|
|
142
149
|
${data.toString()}
|
|
143
150
|
|
|
144
151
|
export function onRequest(context) {
|
|
@@ -175,12 +182,12 @@ const bundlePages = async () => {
|
|
|
175
182
|
entryNames: '[dir]/[name]',
|
|
176
183
|
chunkNames: 'chunks/[name]-[hash]'
|
|
177
184
|
}, {
|
|
178
|
-
name: "
|
|
185
|
+
name: "page-js-plugin",
|
|
179
186
|
setup(build) {
|
|
180
187
|
build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
|
|
181
188
|
const data = fs.readFileSync(args.path);
|
|
182
189
|
const newSrc = `
|
|
183
|
-
import { hydrateApp } from "
|
|
190
|
+
import { hydrateApp } from "edge-city";
|
|
184
191
|
${data.toString()}
|
|
185
192
|
|
|
186
193
|
const searchParams = new URL(import.meta.url).searchParams;
|
|
@@ -216,7 +223,7 @@ const bundleServices = async () => {
|
|
|
216
223
|
for (const p of Object.keys(pkg)) {
|
|
217
224
|
const buildStart = Date.now();
|
|
218
225
|
const result = await bundleJs({ write: false }, s, `build/functions/_rpc${dest}.js`, {
|
|
219
|
-
name: "
|
|
226
|
+
name: "service-plugin",
|
|
220
227
|
setup(build) {
|
|
221
228
|
build.onLoad({ filter: /\\*.service.js/, namespace: undefined }, async (args) => {
|
|
222
229
|
const src = fs.readFileSync(args.path);
|
|
@@ -301,7 +308,7 @@ main();
|
|
|
301
308
|
// }
|
|
302
309
|
// })
|
|
303
310
|
// if (addRpcImport) {
|
|
304
|
-
// lines.unshift(`import { rpc } from "
|
|
311
|
+
// lines.unshift(`import { rpc } from "edge-city";`);
|
|
305
312
|
// }
|
|
306
313
|
// // remove .css and .service imports
|
|
307
314
|
// const filteredJsx = lines.filter((ln) => !ln.includes(`.css"`) && !ln.includes(`.service"`)).join("\n");
|
{packages/runtime → edge-city}/index.js
RENAMED
|
File without changes
|
{packages/cli → edge-city}/package.json
RENAMED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "edge-city",
|
|
3
3
|
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
5
6
|
"dependencies": {
|
|
7
|
+
"history": "^5.3.0",
|
|
8
|
+
"radix3": "^1.0.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
6
11
|
"autoprefixer": "^10.4.14",
|
|
7
12
|
"bytes": "3.1.2",
|
|
8
13
|
"esbuild": "0.17.19",
|
|
9
14
|
"meow": "12.0.1",
|
|
10
15
|
"mime-types": "2.1.35",
|
|
11
16
|
"ms": "2.1.3",
|
|
12
|
-
"parotta-runtime": "workspace:*",
|
|
13
17
|
"picocolors": "1.0.0",
|
|
14
18
|
"postcss": "^8.4.21",
|
|
15
19
|
"postcss-custom-media": "^9.1.2",
|
|
@@ -19,9 +23,12 @@
|
|
|
19
23
|
},
|
|
20
24
|
"peerDependencies": {
|
|
21
25
|
"react": "*",
|
|
22
|
-
"react-dom": "*"
|
|
26
|
+
"react-dom": "*",
|
|
27
|
+
"nprogress": "*",
|
|
28
|
+
"react-error-boundary": "*",
|
|
29
|
+
"react-helmet-async": "*"
|
|
23
30
|
},
|
|
24
31
|
"bin": {
|
|
25
|
-
"
|
|
32
|
+
"edge-city": "cli.js"
|
|
26
33
|
}
|
|
27
|
-
}
|
|
34
|
+
}
|
{packages/example → example}/.gitignore
RENAMED
|
File without changes
|
{packages/example → example}/.vscode/extensions.json
RENAMED
|
File without changes
|
{packages/example → example}/.vscode/settings.json
RENAMED
|
File without changes
|
{packages/example → example}/components/Counter/Counter.jsx
RENAMED
|
File without changes
|
{packages/example → example}/components/Layout/Layout.css
RENAMED
|
File without changes
|
{packages/example → example}/components/Layout/Layout.jsx
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Link } from "
|
|
2
|
+
import { Link } from "edge-city";
|
|
3
3
|
import "modern-normalize";
|
|
4
4
|
import "nprogress";
|
|
5
5
|
import "./Layout.css";
|
{packages/example → example}/components/Timer/Timer.jsx
RENAMED
|
File without changes
|
{packages/example → example}/components/Todo/Todo.css
RENAMED
|
File without changes
|
{packages/example → example}/components/Todo/Todo.jsx
RENAMED
|
File without changes
|
{packages/example → example}/db/index.js
RENAMED
|
File without changes
|
{packages/example → example}/db/migrations/0000_empty_shatterstar.sql
RENAMED
|
File without changes
|
{packages/example → example}/db/migrations/meta/0000_snapshot.json
RENAMED
|
File without changes
|
{packages/example → example}/db/migrations/meta/_journal.json
RENAMED
|
File without changes
|
{packages/example → example}/drizzle.config.json
RENAMED
|
File without changes
|
{packages/example → example}/jsconfig.json
RENAMED
|
File without changes
|
{packages/example → example}/package.json
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "edge-city-example",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"dev": "
|
|
5
|
+
"dev": "edge-city build cloudflare",
|
|
6
6
|
"dev-server": "wrangler pages dev static --local --live-reload",
|
|
7
|
-
"build": "NODE_ENV=production
|
|
7
|
+
"build": "NODE_ENV=production edge-city build cloudflare",
|
|
8
8
|
"test": "bun test",
|
|
9
9
|
"test-e2e": "playwright test"
|
|
10
10
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"modern-normalize": "^2.0.0",
|
|
15
15
|
"normalize.css": "^8.0.1",
|
|
16
16
|
"nprogress": "0.2.0",
|
|
17
|
-
"
|
|
17
|
+
"edge-city": "workspace:*",
|
|
18
18
|
"react": "18.2.0",
|
|
19
19
|
"react-aria-components": "1.0.0-alpha.3",
|
|
20
20
|
"react-dom": "18.2.0",
|
|
@@ -27,11 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@playwright/test": "^1.31.2",
|
|
29
29
|
"eslint": "^8.35.0",
|
|
30
|
-
"eslint-config-react-app": "^7.0.1"
|
|
30
|
+
"eslint-config-react-app": "^7.0.1"
|
|
31
|
-
"parotta-cli": "workspace:*"
|
|
32
|
-
},
|
|
33
|
-
"parotta": {
|
|
34
|
-
"hydrate": true
|
|
35
31
|
},
|
|
36
32
|
"prettier": {
|
|
37
33
|
"printWidth": 120
|
{packages/example → example}/pages/_404/page.css
RENAMED
|
File without changes
|
{packages/example → example}/pages/_404/page.jsx
RENAMED
|
File without changes
|
{packages/example → example}/pages/_500/page.css
RENAMED
|
File without changes
|
{packages/example → example}/pages/_500/page.jsx
RENAMED
|
File without changes
|
{packages/example → example}/pages/about/page.css
RENAMED
|
File without changes
|
{packages/example → example}/pages/about/page.jsx
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Link, useRouter } from "
|
|
2
|
+
import { Link, useRouter } from "edge-city";
|
|
3
3
|
import { Helmet } from 'react-helmet-async';
|
|
4
4
|
import Layout from '@/components/Layout/Layout';
|
|
5
5
|
import "./page.css";
|
|
@@ -10,12 +10,15 @@ export const Page = () => {
|
|
|
10
10
|
<Layout>
|
|
11
11
|
<div className="about-page">
|
|
12
12
|
<Helmet>
|
|
13
|
-
<title>About
|
|
13
|
+
<title>About | Edge City</title>
|
|
14
|
-
<meta name="description" content="Showcase of using
|
|
14
|
+
<meta name="description" content="Showcase of using edge-city meta-framework." />
|
|
15
15
|
</Helmet>
|
|
16
16
|
<div>
|
|
17
|
-
<h1>About Page
|
|
17
|
+
<h1>About Page</h1>
|
|
18
|
+
<p>
|
|
19
|
+
Path: {router.pathname}
|
|
20
|
+
</p>
|
|
18
|
-
<p>Showcase of using
|
|
21
|
+
<p>Showcase of using edge-city meta-framework.</p>
|
|
19
22
|
</div>
|
|
20
23
|
<footer>
|
|
21
24
|
<Link href="/">Back</Link>
|
{packages/example → example}/pages/page.css
RENAMED
|
File without changes
|
{packages/example → example}/pages/page.jsx
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { useRouter } from "
|
|
2
|
+
import { useRouter } from "edge-city";
|
|
3
3
|
import Layout from '@/components/Layout/Layout';
|
|
4
4
|
import Counter from "@/components/Counter/Counter";
|
|
5
5
|
import { Helmet } from 'react-helmet-async';
|
|
@@ -13,7 +13,7 @@ const Page = () => {
|
|
|
13
13
|
return (
|
|
14
14
|
<Layout>
|
|
15
15
|
<Helmet>
|
|
16
|
-
<title>
|
|
16
|
+
<title>Edge City</title>
|
|
17
17
|
</Helmet>
|
|
18
18
|
<div className="home-page">
|
|
19
19
|
<h1>Home Page</h1>
|
{packages/example → example}/pages/page.spec.js
RENAMED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// })
|
|
7
7
|
|
|
8
8
|
// test('has title', async ({ page }) => {
|
|
9
|
-
// await expect(page).toHaveTitle(/
|
|
9
|
+
// await expect(page).toHaveTitle(/Edge City/);
|
|
10
10
|
// });
|
|
11
11
|
|
|
12
12
|
// test('has links', async ({ page }) => {
|
{packages/example → example}/playwright.config.js
RENAMED
|
@@ -58,8 +58,7 @@ export default defineConfig({
|
|
|
58
58
|
],
|
|
59
59
|
outputDir: 'test-results/',
|
|
60
60
|
webServer: {
|
|
61
|
-
command: '
|
|
61
|
+
command: 'npm run dev',
|
|
62
|
-
port:
|
|
62
|
+
port: 8788,
|
|
63
63
|
},
|
|
64
|
-
});
|
|
64
|
+
});
|
|
65
|
-
|
example/readme.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Sample edge-city app
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
1. node >= v20
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
1. `npm i`
|
|
10
|
+
2. `npx playright install`
|
|
11
|
+
|
|
12
|
+
## Running
|
|
13
|
+
|
|
14
|
+
`npm run dev`
|
|
15
|
+
|
|
16
|
+
## Testing
|
|
17
|
+
|
|
18
|
+
`npm test`
|
|
19
|
+
|
|
20
|
+
## Build
|
|
21
|
+
|
|
22
|
+
`npm run build`
|
{packages/example → example}/services/auth.service.js
RENAMED
|
File without changes
|
{packages/example → example}/services/todos.service.js
RENAMED
|
File without changes
|
{packages/example → example}/services/todos.service.test.js
RENAMED
|
File without changes
|
{packages/example → example}/static/favicon.ico
RENAMED
|
File without changes
|
{packages/example → example}/static/logo192.png
RENAMED
|
File without changes
|
{packages/example → example}/static/logo512.png
RENAMED
|
File without changes
|
{packages/example → example}/static/manifest.json
RENAMED
|
File without changes
|
{packages/example → example}/static/robots.txt
RENAMED
|
File without changes
|
{packages/cli → example/static}/todos/page.css
RENAMED
|
File without changes
|
{packages/cli → example/static}/todos/page.jsx
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { Suspense } from 'react';
|
|
2
2
|
import { Helmet } from 'react-helmet-async';
|
|
3
|
-
import { useQuery, useMutation } from "
|
|
3
|
+
import { useQuery, useMutation } from "edge-city";
|
|
4
4
|
import { useForm } from 'react-hook-form';
|
|
5
5
|
import Todo from "@/components/Todo/Todo";
|
|
6
6
|
import { TextField, Label, Input } from 'react-aria-components';
|
packages/example/readme.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Sample Parotta Application
|
|
2
|
-
|
|
3
|
-
## Requirements
|
|
4
|
-
|
|
5
|
-
1. bun >= v0.5.8
|
|
6
|
-
|
|
7
|
-
## Setup
|
|
8
|
-
|
|
9
|
-
1. `bun i`
|
|
10
|
-
2. `bunx playright install`
|
|
11
|
-
|
|
12
|
-
## Running
|
|
13
|
-
|
|
14
|
-
`bun run dev`
|
|
15
|
-
|
|
16
|
-
## Testing
|
|
17
|
-
|
|
18
|
-
`bun test`
|
packages/runtime/package.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "parotta-runtime",
|
|
3
|
-
"version": "0.5.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"dependencies": {
|
|
6
|
-
"history": "^5.3.0",
|
|
7
|
-
"radix3": "^1.0.0"
|
|
8
|
-
},
|
|
9
|
-
"peerDependencies": {
|
|
10
|
-
"react": "*",
|
|
11
|
-
"react-dom": "*",
|
|
12
|
-
"nprogress": "*",
|
|
13
|
-
"react-error-boundary": "*",
|
|
14
|
-
"react-helmet-async": "*"
|
|
15
|
-
}
|
|
16
|
-
}
|
pnpm-workspace.yaml
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
packages:
|
|
2
|
-
- '
|
|
2
|
+
- 'edge-city'
|
|
3
|
+
- 'example'
|
readme.md
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
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
|
|
4
|
+
It uses esbuild as its bundler/transpiler.
|
|
5
|
-
It uses
|
|
5
|
+
It uses file system routing (similar to next app router) with streaming SSR + CSR render pages.
|
|
6
6
|
It is very opionated and has set of idiomatic ways of doing things.
|
|
7
7
|
It has an inbuilt rpc mechanism to access server resources instead of a typical REST API.
|
|
8
|
+
It aims to have almost the same router api as nextjs router for ease of use.
|
|
8
9
|
|
|
9
|
-
During development each request for a page is executed in a separate
|
|
10
|
+
During development each request for a page is executed in a separate edge-runtime (miniflare/vercel) vm.
|
|
10
11
|
During production each page is packaged to an esm function adapted to the platform of your choice.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Why?
|
|
13
|
-
1. Hydrate rpc cache
|
|
14
|
-
|
|
14
|
+
Because its really hard to have a streaming SSR + CSR setup in nextjs currently.
|
|
15
|
+
The only other framework is rakkasjs but it doesn't maitaing as smooth transition from nextjs.
|
|
15
16
|
|
|
16
17
|
### Supported platforms
|
|
17
18
|
1. [Cloudflare page functions](https://developers.cloudflare.com/pages/platform/functions/routing/)
|
|
18
19
|
2. [Vercel edge functions](https://vercel.com/docs/concepts/functions/edge-functions)
|
|
19
20
|
3. [Netlify edge functions](https://docs.netlify.com/edge-functions/overview/)
|
|
20
|
-
4. [Deno Deploy](https://deno.com/deploy)
|
|
21
|
+
4. [Deno Deploy](https://deno.com/deploy)
|
|
22
|
+
|
|
23
|
+
### Todo
|
|
24
|
+
1. Hydrate rpc cache
|
|
25
|
+
2. Build a docs website
|
|
26
|
+
3. Fix 404/500 pages not routing
|
|
27
|
+
3. Add Env variables `PUBLIC_` for client
|