~repos /edge-city

#react#js#ssr

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 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 { Link, useRouter } from "parotta/router";
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((acc, r) => {
87
+ const clientRoutes = await clientSideRoutes.reduce(async (accp, r) => {
88
+ const acc = await accp;
81
- const Head = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
89
+ const src = await import(`${process.cwd()}/routes${r}/page.jsx`);
82
- const Body = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
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
- for (const k of Object.keys(clientRoutes)) {
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
- ${clientSideRoutes.map((r) => `"${r === "" ? "/" : r}": {
195
+ ${Object.keys(clientRoutes).map((r) => `"${r === "" ? "/" : r}": {
186
- Head: React.lazy(() => import("/routes${r === "" ? "" : r}/page.jsx").then((js) => ({ default: js.Head }))),
196
+ Head: React.lazy(() => import("/routes${r}/page.jsx").then((js) => ({ default: js.Head }))),
187
- Body: React.lazy(() => import("/routes${r === "" ? "" : r}/page.jsx").then((js) => ({ default: js.Body }))),
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
- import React, { createContext, useContext, useState, useEffect, useMemo, useSyncExternalStore } from "react";
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 === "/" ? "/page.css" : pathname + "/page.css"}`;
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 React.createElement(React.Fragment, {
23
+ return createElement(Fragment, {
21
24
  children: [
22
- React.createElement("link", {
25
+ createElement("link", {
23
26
  rel: "stylesheet",
24
27
  href: "https://unpkg.com/nprogress@0.2.0/nprogress.css",
25
28
  }),
26
- React.createElement("link", {
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
- React.createElement(React.Suspense, {
39
+ createElement(Suspense, {
32
- children: React.createElement(match.Head, {}),
40
+ children: createElement(match.Head, {}),
33
41
  }),
34
- React.createElement("script", {
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 = ({ App, history, radixRouter }) => {
53
+ export const Body = ({ history, radixRouter }) => {
46
- const [isPending, startTransition] = React.useTransition();
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
- nProgress.start();
74
+ nProgress.start();
67
- startTransition(() => {
75
+ startTransition(() => {
68
- setMatch(getMatch(radixRouter, location.pathname));
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 React.createElement(RouterContext.Provider, {
87
+ return createElement(RouterContext.Provider, {
80
88
  value: {
81
89
  history: history,
82
90
  params: match.params || {},
83
91
  },
84
- children: React.createElement(App, {
92
+ children: createElement(match.Layout, {
85
- children: React.createElement(match.Body, {}),
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 React.createElement("a", {
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();