~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
32750419
—
Peter John 2 years ago
add layouts
- packages/create-parotta-app/bun.lockb +0 -0
- packages/example/routes/app.jsx +0 -12
- packages/example/routes/layout.css +5 -0
- packages/example/routes/layout.jsx +20 -0
- packages/example/routes/page.jsx +3 -7
- packages/example/services/collections.js +1 -0
- packages/parotta/cli.js +26 -14
- packages/parotta/router.js +29 -21
packages/create-parotta-app/bun.lockb
ADDED
|
Binary file
|
packages/example/routes/app.jsx
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React, { Suspense } from "react";
|
|
2
|
-
import { ErrorBoundary } from "parotta/error";
|
|
3
|
-
|
|
4
|
-
export default function App({ children }) {
|
|
5
|
-
return (
|
|
6
|
-
<ErrorBoundary onError={(err) => console.log(err)} fallback={<p>Oops something went wrong</p>}>
|
|
7
|
-
<Suspense fallback={<p>Loading...</p>}>
|
|
8
|
-
{children}
|
|
9
|
-
</Suspense>
|
|
10
|
-
</ErrorBoundary>
|
|
11
|
-
)
|
|
12
|
-
}
|
packages/example/routes/layout.css
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
.layout-header {
|
|
2
|
+
& a {
|
|
3
|
+
margin-right: 20px;
|
|
4
|
+
}
|
|
5
|
+
}
|
packages/example/routes/layout.jsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { Suspense } from 'react';
|
|
2
|
+
import { Link } from "parotta/router";
|
|
3
|
+
import { ErrorBoundary } from "parotta/error";
|
|
4
|
+
import "./layout.css";
|
|
5
|
+
|
|
6
|
+
const Layout = ({ children }) => {
|
|
7
|
+
return (
|
|
8
|
+
<ErrorBoundary onError={(err) => console.log(err)} fallback={<p>Oops something went wrong</p>}>
|
|
9
|
+
<header className="layout-header">
|
|
10
|
+
<Link href="/about">About us</Link>
|
|
11
|
+
<Link href="/todos">Todos</Link>
|
|
12
|
+
</header>
|
|
13
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
14
|
+
{children}
|
|
15
|
+
</Suspense>
|
|
16
|
+
</ErrorBoundary>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default Layout;
|
packages/example/routes/page.jsx
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useRouter } from "parotta/router";
|
|
3
3
|
import { useFetch } from "parotta/fetch";
|
|
4
4
|
import Counter from "@/components/Counter/Counter";
|
|
5
5
|
import "./page.css";
|
|
6
6
|
|
|
7
7
|
export const Head = () => {
|
|
8
|
-
const { data } = useFetch("/todos");
|
|
9
8
|
return (
|
|
10
9
|
<title>Parotta</title>
|
|
11
10
|
)
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export const Body = () => {
|
|
14
|
+
const router = useRouter();
|
|
15
15
|
const { data, cache } = useFetch("/todos");
|
|
16
16
|
console.log('page');
|
|
17
17
|
console.log('data', data);
|
|
@@ -19,8 +19,7 @@ export const Body = () => {
|
|
|
19
19
|
setTimeout(() => {
|
|
20
20
|
cache.invalidate(/todos/);
|
|
21
21
|
}, 3000)
|
|
22
|
-
}, [])
|
|
22
|
+
}, []);
|
|
23
|
-
const router = useRouter();
|
|
24
23
|
return (
|
|
25
24
|
<div>
|
|
26
25
|
<div>
|
|
@@ -30,9 +29,6 @@ export const Body = () => {
|
|
|
30
29
|
</p>
|
|
31
30
|
<Counter />
|
|
32
31
|
</div>
|
|
33
|
-
<footer>
|
|
34
|
-
<Link href="/about">About us</Link>
|
|
35
|
-
</footer>
|
|
36
32
|
</div>
|
|
37
33
|
)
|
|
38
34
|
}
|
packages/example/services/collections.js
CHANGED
|
@@ -4,6 +4,7 @@ const getCollection = (name) => {
|
|
|
4
4
|
findMany: () => {
|
|
5
5
|
return {
|
|
6
6
|
toArray: async () => {
|
|
7
|
+
await new Promise((res) => setTimeout(res, 500));
|
|
7
8
|
return items;
|
|
8
9
|
}
|
|
9
10
|
}
|
packages/parotta/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
5
6
|
import walkdir from 'walkdir';
|
|
6
7
|
import postcss from "postcss"
|
|
7
8
|
import autoprefixer from "autoprefixer";
|
|
@@ -37,6 +38,12 @@ const mapFiles = () => {
|
|
|
37
38
|
const key = page.route || "/";
|
|
38
39
|
routes[key] = { key: key, page: page.path };
|
|
39
40
|
});
|
|
41
|
+
dirs.filter((p) => p.includes('layout.jsx'))
|
|
42
|
+
.map((s) => ({ path: s, route: s.replace("/layout.jsx", "") }))
|
|
43
|
+
.forEach((item) => {
|
|
44
|
+
const key = item.route || "/";
|
|
45
|
+
routes[key].layout = item.path;
|
|
46
|
+
});
|
|
40
47
|
dirs.filter((p) => p.includes('api.js'))
|
|
41
48
|
.map((s) => s.replace(process.cwd(), ""))
|
|
42
49
|
.map((s) => ({ path: s, route: s.replace("/api.js", "") }))
|
|
@@ -77,20 +84,23 @@ const serverRouter = createRouter({
|
|
|
77
84
|
routes: serverSideRoutes,
|
|
78
85
|
});
|
|
79
86
|
|
|
80
|
-
const clientRoutes = clientSideRoutes.reduce((
|
|
87
|
+
const clientRoutes = await clientSideRoutes.reduce(async (accp, r) => {
|
|
88
|
+
const acc = await accp;
|
|
81
|
-
const
|
|
89
|
+
const src = await import(`${process.cwd()}/routes${r}/page.jsx`);
|
|
82
|
-
const
|
|
90
|
+
const exists = fs.existsSync(`${process.cwd()}/routes${r}/layout.jsx`);
|
|
91
|
+
const lpath = exists ? `/routes${r}/layout.jsx` : `/routes/layout.jsx`;
|
|
92
|
+
const lsrc = await import(`${process.cwd()}${lpath}`);
|
|
83
93
|
acc[r === "" ? "/" : r] = {
|
|
94
|
+
r,
|
|
84
|
-
Head,
|
|
95
|
+
Head: src.Head,
|
|
85
|
-
Body,
|
|
96
|
+
Body: src.Body,
|
|
97
|
+
Layout: lsrc.default,
|
|
98
|
+
LayoutPath: lpath,
|
|
86
99
|
}
|
|
87
100
|
return acc
|
|
88
|
-
}, {});
|
|
101
|
+
}, Promise.resolve({}));
|
|
89
102
|
|
|
90
|
-
|
|
103
|
+
// console.log(clientRoutes);
|
|
91
|
-
clientRoutes[k].Head = (await clientRoutes[k].Head).Head;
|
|
92
|
-
clientRoutes[k].Body = (await clientRoutes[k].Body).Body;
|
|
93
|
-
}
|
|
94
104
|
|
|
95
105
|
const clientRouter = createRouter({
|
|
96
106
|
strictTrailingSlash: true,
|
|
@@ -182,9 +192,11 @@ const history = createBrowserHistory();
|
|
|
182
192
|
const radixRouter = createRouter({
|
|
183
193
|
strictTrailingSlash: true,
|
|
184
194
|
routes: {
|
|
185
|
-
${
|
|
195
|
+
${Object.keys(clientRoutes).map((r) => `"${r === "" ? "/" : r}": {
|
|
186
|
-
Head: React.lazy(() => import("/routes${r
|
|
196
|
+
Head: React.lazy(() => import("/routes${r}/page.jsx").then((js) => ({ default: js.Head }))),
|
|
187
|
-
Body: React.lazy(() => import("/routes${r
|
|
197
|
+
Body: React.lazy(() => import("/routes${r}/page.jsx").then((js) => ({ default: js.Body }))),
|
|
198
|
+
Layout: React.lazy(() => import("${clientRoutes[r].LayoutPath}")),
|
|
199
|
+
LayoutPath: "${clientRoutes[r].LayoutPath}",
|
|
188
200
|
}`).join(',\n ')}
|
|
189
201
|
},
|
|
190
202
|
});
|
|
@@ -246,7 +258,7 @@ const renderJs = async (src) => {
|
|
|
246
258
|
try {
|
|
247
259
|
const jsText = await Bun.file(src).text();
|
|
248
260
|
const result = await transpiler.transform(jsText);
|
|
249
|
-
const js = result.replaceAll(`import"./page.css";`, "");
|
|
261
|
+
const js = result.replaceAll(`import"./page.css";`, "").replaceAll(`import"./layout.css";`, "");;
|
|
250
262
|
// TODO
|
|
251
263
|
//.replaceAll("$jsx", "React.createElement");
|
|
252
264
|
return new Response(js, {
|
packages/parotta/router.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Fragment, Suspense, createElement, createContext,
|
|
1
|
-
|
|
3
|
+
useContext, useState, useEffect, useMemo, useSyncExternalStore, useTransition
|
|
4
|
+
} from "react";
|
|
2
5
|
import nProgress from "nprogress";
|
|
3
6
|
|
|
4
7
|
export const RouterContext = createContext(undefined);
|
|
@@ -11,27 +14,32 @@ const getMatch = (radixRouter, pathname) => {
|
|
|
11
14
|
return matchedPage;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
const getCssUrl = (pathname) => `/routes${pathname === "/" ? "
|
|
17
|
+
const getCssUrl = (pathname) => `/routes${pathname === "/" ? "" : pathname}`;
|
|
15
18
|
|
|
16
19
|
export const Head = ({ history, radixRouter, importMap }) => {
|
|
17
20
|
const pathname = useSyncExternalStore(history.listen, (v) => v ? v.location.pathname : history.location.pathname, () => history.location.pathname);
|
|
18
21
|
const match = getMatch(radixRouter, pathname);
|
|
19
22
|
const initialCss = useMemo(() => getCssUrl(history.location.pathname), []);
|
|
20
|
-
return
|
|
23
|
+
return createElement(Fragment, {
|
|
21
24
|
children: [
|
|
22
|
-
|
|
25
|
+
createElement("link", {
|
|
23
26
|
rel: "stylesheet",
|
|
24
27
|
href: "https://unpkg.com/nprogress@0.2.0/nprogress.css",
|
|
25
28
|
}),
|
|
26
|
-
|
|
29
|
+
createElement("link", {
|
|
30
|
+
id: "layoutCss",
|
|
31
|
+
rel: "stylesheet",
|
|
32
|
+
href: match.LayoutPath.replace("jsx", "css"),
|
|
33
|
+
}),
|
|
34
|
+
createElement("link", {
|
|
27
35
|
id: "pageCss",
|
|
28
36
|
rel: "stylesheet",
|
|
29
|
-
href: getCssUrl(history.location.pathname),
|
|
37
|
+
href: getCssUrl(history.location.pathname) + "/page.css",
|
|
30
38
|
}),
|
|
31
|
-
|
|
39
|
+
createElement(Suspense, {
|
|
32
|
-
children:
|
|
40
|
+
children: createElement(match.Head, {}),
|
|
33
41
|
}),
|
|
34
|
-
|
|
42
|
+
createElement("script", {
|
|
35
43
|
id: "importmap",
|
|
36
44
|
type: "importmap",
|
|
37
45
|
dangerouslySetInnerHTML: {
|
|
@@ -42,8 +50,8 @@ export const Head = ({ history, radixRouter, importMap }) => {
|
|
|
42
50
|
});
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
export const Body = ({
|
|
53
|
+
export const Body = ({ history, radixRouter }) => {
|
|
46
|
-
const [isPending, startTransition] =
|
|
54
|
+
const [isPending, startTransition] = useTransition();
|
|
47
55
|
const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
|
|
48
56
|
useEffect(() => {
|
|
49
57
|
return history.listen(({ location }) => {
|
|
@@ -63,10 +71,10 @@ export const Body = ({ App, history, radixRouter }) => {
|
|
|
63
71
|
// link.setAttribute("href", href);
|
|
64
72
|
// document.getElementsByTagName("head")[0].appendChild(link);
|
|
65
73
|
// } else {
|
|
66
|
-
|
|
74
|
+
nProgress.start();
|
|
67
|
-
|
|
75
|
+
startTransition(() => {
|
|
68
|
-
|
|
76
|
+
setMatch(getMatch(radixRouter, location.pathname));
|
|
69
|
-
|
|
77
|
+
})
|
|
70
78
|
// }
|
|
71
79
|
});
|
|
72
80
|
}, []);
|
|
@@ -76,13 +84,13 @@ export const Body = ({ App, history, radixRouter }) => {
|
|
|
76
84
|
}
|
|
77
85
|
}, [isPending])
|
|
78
86
|
console.log('Router', isPending);
|
|
79
|
-
return
|
|
87
|
+
return createElement(RouterContext.Provider, {
|
|
80
88
|
value: {
|
|
81
89
|
history: history,
|
|
82
90
|
params: match.params || {},
|
|
83
91
|
},
|
|
84
|
-
children:
|
|
92
|
+
children: createElement(match.Layout, {
|
|
85
|
-
children:
|
|
93
|
+
children: createElement(match.Body, {}),
|
|
86
94
|
}),
|
|
87
95
|
});
|
|
88
96
|
}
|
|
@@ -103,12 +111,12 @@ export const useRouter = () => {
|
|
|
103
111
|
|
|
104
112
|
export const Link = (props) => {
|
|
105
113
|
const router = useRouter();
|
|
106
|
-
return
|
|
114
|
+
return createElement("a", {
|
|
107
115
|
...props,
|
|
108
116
|
onMouseOver: (e) => {
|
|
109
117
|
// Simple prefetching for now will work only with cache headers
|
|
110
|
-
fetch(getCssUrl(props.href));
|
|
118
|
+
// fetch(getCssUrl(props.href));
|
|
111
|
-
fetch(getCssUrl(props.href).replace("css", "jsx"));
|
|
119
|
+
// fetch(getCssUrl(props.href).replace("css", "jsx"));
|
|
112
120
|
},
|
|
113
121
|
onClick: (e) => {
|
|
114
122
|
e.preventDefault();
|