~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
461357aa
—
Peter John 2 years ago
move fetch
- packages/example/routes/page.jsx +7 -6
- packages/example/routes/todos/:id/page.jsx +2 -1
- packages/parotta/cli.js +3 -1
- packages/parotta/fetch.js +55 -0
- packages/parotta/router.js +13 -73
- readme.md +5 -6
packages/example/routes/page.jsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import { Link, useRouter
|
|
2
|
+
import { Link, useRouter } from "parotta/router";
|
|
3
|
+
import { useFetch } from "parotta/fetch";
|
|
3
4
|
import Counter from "@/components/Counter/Counter";
|
|
4
5
|
import "./page.css";
|
|
5
6
|
|
|
@@ -14,11 +15,11 @@ export default function Page() {
|
|
|
14
15
|
const { data, cache } = useFetch("/todos");
|
|
15
16
|
console.log('page');
|
|
16
17
|
console.log('data', data);
|
|
17
|
-
|
|
18
|
+
useEffect(() => {
|
|
18
|
-
|
|
19
|
+
setTimeout(() => {
|
|
19
|
-
|
|
20
|
+
cache.invalidate(/todos/);
|
|
20
|
-
|
|
21
|
+
}, 3000)
|
|
21
|
-
|
|
22
|
+
}, [])
|
|
22
23
|
const router = useRouter();
|
|
23
24
|
return (
|
|
24
25
|
<div className="home-page">
|
packages/example/routes/todos/:id/page.jsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Suspense } from "react";
|
|
2
|
-
import { Link, useRouter
|
|
2
|
+
import { Link, useRouter } from "parotta/router";
|
|
3
|
+
import { useFetch } from "parotta/fetch";
|
|
3
4
|
// import "./index.css";
|
|
4
5
|
|
|
5
6
|
export default function Page() {
|
packages/parotta/cli.js
CHANGED
|
@@ -77,7 +77,7 @@ const serverRouter = createRouter({
|
|
|
77
77
|
routes: serverSideRoutes,
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
const clientRoutes =
|
|
80
|
+
const clientRoutes = clientSideRoutes.reduce((acc, r) => {
|
|
81
81
|
const Head = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
|
|
82
82
|
const Page = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
|
|
83
83
|
acc[r === "" ? "/" : r] = {
|
|
@@ -142,8 +142,10 @@ const renderPage = async (url) => {
|
|
|
142
142
|
"nprogress": "https://esm.sh/nprogress@0.2.0",
|
|
143
143
|
// "parotta/router": `https://esm.sh/parotta@${version}/router.js`,
|
|
144
144
|
// "parotta/error": `https://esm.sh/parotta@${version}/error.js`,
|
|
145
|
+
// "parotta/fetch": `https://esm.sh/parotta@${version}/fetch.js`,
|
|
145
146
|
"parotta/router": `/parotta/router.js`,
|
|
146
147
|
"parotta/error": `/parotta/error.js`,
|
|
148
|
+
"parotta/fetch": `/parotta/fetch.js`,
|
|
147
149
|
...nodeDeps,
|
|
148
150
|
...components,
|
|
149
151
|
...containers,
|
packages/parotta/fetch.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState, useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
export const domain = () => typeof window !== 'undefined' ? window.origin : "http://0.0.0.0:3000";
|
|
4
|
+
export const globalCache = new Map();
|
|
5
|
+
|
|
6
|
+
export const useCache = () => {
|
|
7
|
+
const [_, rerender] = useState(false);
|
|
8
|
+
const cache = useMemo(() => globalCache, []);
|
|
9
|
+
const get = (k) => cache.get(k)
|
|
10
|
+
const set = (k, v) => {
|
|
11
|
+
cache.set(k, v);
|
|
12
|
+
rerender((c) => !c);
|
|
13
|
+
}
|
|
14
|
+
const invalidate = (regex) => {
|
|
15
|
+
Array.from(cache.keys())
|
|
16
|
+
.filter((k) => regex.test(k))
|
|
17
|
+
.forEach((k) => {
|
|
18
|
+
fetchData(k).then((v) => set(k, v));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
get,
|
|
23
|
+
set,
|
|
24
|
+
invalidate,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fetchData = async (route) => {
|
|
29
|
+
const url = `${domain()}${route}`;
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
headers: {
|
|
32
|
+
"Accept": "application/json",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (res.ok) {
|
|
36
|
+
return await res.json();
|
|
37
|
+
} else {
|
|
38
|
+
return new Error(await res.text());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const useFetch = (url) => {
|
|
43
|
+
const cache = useCache();
|
|
44
|
+
const value = cache.get(url);
|
|
45
|
+
if (value) {
|
|
46
|
+
if (value instanceof Promise) {
|
|
47
|
+
throw value;
|
|
48
|
+
} else if (value instanceof Error) {
|
|
49
|
+
throw value;
|
|
50
|
+
}
|
|
51
|
+
return { data: value, cache };
|
|
52
|
+
}
|
|
53
|
+
cache.set(url, fetchData(url).then((v) => cache.set(url, v)));
|
|
54
|
+
throw cache.get(url);
|
|
55
|
+
}
|
packages/parotta/router.js
CHANGED
|
@@ -1,62 +1,6 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
2
2
|
import nProgress from "nprogress";
|
|
3
3
|
|
|
4
|
-
export const isClient = () => typeof window !== 'undefined';
|
|
5
|
-
export const domain = () => isClient() ? window.origin : "http://0.0.0.0:3000";
|
|
6
|
-
export const basePath = () => isClient() ? "" : process.cwd();
|
|
7
|
-
export const globalCache = new Map();
|
|
8
|
-
export const useFetchCache = () => {
|
|
9
|
-
const [_, rerender] = useState(false);
|
|
10
|
-
const cache = useMemo(() => globalCache, []);
|
|
11
|
-
const get = (k) => cache.get(k)
|
|
12
|
-
const set = (k, v) => {
|
|
13
|
-
cache.set(k, v);
|
|
14
|
-
rerender((c) => !c);
|
|
15
|
-
}
|
|
16
|
-
const invalidate = (regex) => {
|
|
17
|
-
Array.from(cache.keys())
|
|
18
|
-
.filter((k) => regex.test(k))
|
|
19
|
-
.forEach((k) => {
|
|
20
|
-
fetchData(k).then((v) => set(k, v));
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
get,
|
|
25
|
-
set,
|
|
26
|
-
invalidate,
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const fetchData = async (route) => {
|
|
31
|
-
const url = `${domain()}${route}`;
|
|
32
|
-
console.log('url', url);
|
|
33
|
-
const res = await fetch(url, {
|
|
34
|
-
headers: {
|
|
35
|
-
"Accept": "application/json",
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
if (res.ok) {
|
|
39
|
-
return await res.json();
|
|
40
|
-
} else {
|
|
41
|
-
return new Error(await res.text());
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const useFetch = (url) => {
|
|
46
|
-
const cache = useFetchCache();
|
|
47
|
-
const value = cache.get(url);
|
|
48
|
-
if (value) {
|
|
49
|
-
if (value instanceof Promise) {
|
|
50
|
-
throw value;
|
|
51
|
-
} else if (value instanceof Error) {
|
|
52
|
-
throw value;
|
|
53
|
-
}
|
|
54
|
-
return { data: value, cache };
|
|
55
|
-
}
|
|
56
|
-
cache.set(url, fetchData(url).then((v) => cache.set(url, v)));
|
|
57
|
-
throw cache.get(url);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
4
|
export const RouterContext = createContext(undefined);
|
|
61
5
|
|
|
62
6
|
const getMatch = (radixRouter, pathname) => {
|
|
@@ -97,18 +41,8 @@ export const Header = ({ history, radixRouter, importMap }) => {
|
|
|
97
41
|
});
|
|
98
42
|
}
|
|
99
43
|
|
|
100
|
-
// export const PP = ({ children }) => {
|
|
101
|
-
// React.useEffect(() => {
|
|
102
|
-
// nProgress.done()
|
|
103
|
-
// return () => {
|
|
104
|
-
// nProgress.start();
|
|
105
|
-
// }
|
|
106
|
-
// }, []);
|
|
107
|
-
// return children
|
|
108
|
-
// }
|
|
109
|
-
|
|
110
44
|
export const Router = ({ App, history, radixRouter }) => {
|
|
111
|
-
const [, startTransition] = React.useTransition();
|
|
45
|
+
const [isPending, startTransition] = React.useTransition();
|
|
112
46
|
const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
|
|
113
47
|
useEffect(() => {
|
|
114
48
|
return history.listen(({ location }) => {
|
|
@@ -134,8 +68,13 @@ export const Router = ({ App, history, radixRouter }) => {
|
|
|
134
68
|
})
|
|
135
69
|
}
|
|
136
70
|
});
|
|
137
|
-
}, [])
|
|
71
|
+
}, []);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isPending) {
|
|
74
|
+
nProgress.done();
|
|
75
|
+
}
|
|
76
|
+
}, [isPending])
|
|
138
|
-
console.log('Router');
|
|
77
|
+
console.log('Router', isPending);
|
|
139
78
|
return React.createElement(RouterContext.Provider, {
|
|
140
79
|
value: {
|
|
141
80
|
history: history,
|
|
@@ -165,10 +104,11 @@ export const Link = (props) => {
|
|
|
165
104
|
const router = useRouter();
|
|
166
105
|
return React.createElement("a", {
|
|
167
106
|
...props,
|
|
168
|
-
|
|
107
|
+
onMouseOver: (e) => {
|
|
169
|
-
|
|
108
|
+
// Simple prefetching for now will work only with cache headers
|
|
109
|
+
fetch(getCssUrl(props.href));
|
|
170
|
-
|
|
110
|
+
fetch(getCssUrl(props.href).replace("css", "jsx"));
|
|
171
|
-
|
|
111
|
+
},
|
|
172
112
|
onClick: (e) => {
|
|
173
113
|
e.preventDefault();
|
|
174
114
|
if (props && props.onClick) {
|
readme.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# parotta
|
|
2
2
|
|
|
3
|
-
parotta is a next level meta-framework for react that runs only on
|
|
3
|
+
parotta is a next level meta-framework for react that runs only on edge runtimes mainly bun.
|
|
4
|
-
It uses File
|
|
4
|
+
It uses File System routing with streaming SSR + CSR as the method to render pages. Basically a MPA + SPA Transitional App.
|
|
5
5
|
It is very opionated and has set of idiomatic ways of doing things.
|
|
6
6
|
|
|
7
7
|
### Todo
|
|
8
|
-
1. Prefetching assets
|
|
9
|
-
|
|
8
|
+
1. Add build step
|
|
10
|
-
|
|
9
|
+
2. Deploy to Docker, Deno deploy, Vercel edge functions, Cloudflare workers, Bun edge (whenever it releases)
|
|
11
|
-
|
|
10
|
+
3. Hydrate fetch cache
|