~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


a0d404df Peter John

2 years ago
remove layout
bun.lockb CHANGED
Binary file
pages/layout.css → components/Layout/Layout.css RENAMED
File without changes
components/Layout/Layout.jsx ADDED
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { Link } from "parotta/runtime";
3
+ import "./Layout.css";
4
+
5
+ const Layout = ({ children }) => {
6
+ return (
7
+ <div>
8
+ <header className="layout-header">
9
+ <Link href="/about">About us</Link>
10
+ <Link href="/todos">Todos</Link>
11
+ </header>
12
+ <div>
13
+ {children}
14
+ </div>
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export default Layout;
containers/TodoList/TodoList.jsx DELETED
@@ -1,19 +0,0 @@
1
- import React from 'react';
2
- import Todo from "@/components/Todo/Todo";
3
- import { getTodos } from "@/services/todos.service";
4
- import { useRpc } from "parotta/runtime";
5
-
6
- const TodoList = () => {
7
- const { data } = useRpc(getTodos, {});
8
- return (
9
- <div className="todo-list">
10
- <ul>
11
- {data.map((item) => (
12
- <Todo key={item.id} todo={item} />
13
- ))}
14
- </ul>
15
- </div>
16
- );
17
- }
18
-
19
- export default TodoList;
main.js CHANGED
@@ -1,5 +1,3 @@
1
- import React from "react";
2
- import ReactDom from "react-dom/server";
3
1
  import server from "parotta/server.js";
4
2
 
5
3
  export default {
package.json CHANGED
@@ -17,6 +17,7 @@
17
17
  "normalize.css": "^8.0.1",
18
18
  "react": "18.2.0",
19
19
  "react-dom": "^18.2.0",
20
+ "react-error-boundary": "^4.0.4",
20
21
  "react-helmet-async": "^1.3.0",
21
22
  "sql-highlight": "^4.3.2"
22
23
  },
pages/{404 → _404}/page.css RENAMED
File without changes
pages/{404 → _404}/page.jsx RENAMED
@@ -1,10 +1,14 @@
1
1
  import React from 'react';
2
+ import { Helmet } from 'react-helmet-async';
2
3
  import "./page.css";
3
4
 
4
5
  const Page = () => {
5
6
  return (
6
7
  <div>
8
+ <Helmet>
9
+ <title>Page not found</title>
10
+ </Helmet>
7
- <h1>404</h1>
11
+ <h1>404 - Page not found</h1>
8
12
  <div className="content">
9
13
  <h2>This page could not be found</h2>
10
14
  </div>
pages/_500/page.css ADDED
@@ -0,0 +1,38 @@
1
+ body {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ color: #000;
7
+ background: #fff;
8
+ font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif;
9
+ height: 100vh;
10
+ text-align: center;
11
+
12
+ & h1 {
13
+ display: inline-block;
14
+ border-right: 1px solid rgba(0, 0, 0, .3);
15
+ margin: 0;
16
+ margin-right: 20px;
17
+ padding: 10px 23px 10px 0;
18
+ font-size: 24px;
19
+ font-weight: 500;
20
+ vertical-align: top;
21
+ }
22
+
23
+ & .content {
24
+ display: inline-block;
25
+ text-align: left;
26
+ line-height: 49px;
27
+ height: 49px;
28
+ vertical-align: middle;
29
+ }
30
+
31
+ & h2 {
32
+ font-size: 14px;
33
+ font-weight: normal;
34
+ line-height: inherit;
35
+ margin: 0;
36
+ padding: 0;
37
+ }
38
+ }
pages/_500/page.jsx ADDED
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { Helmet } from 'react-helmet-async';
3
+ import "./page.css";
4
+
5
+ const Page = () => {
6
+ return (
7
+ <div>
8
+ <Helmet>
9
+ <title>Oop's Something went wrong</title>
10
+ </Helmet>
11
+ <h1>Oop's Something went wrong</h1>
12
+ <div className="content">
13
+ <h2>Internal Server Error</h2>
14
+ </div>
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export default Page;
pages/about/page.jsx CHANGED
@@ -1,24 +1,27 @@
1
1
  import React from 'react';
2
2
  import { Link, useRouter } from "parotta/runtime";
3
3
  import { Helmet } from 'react-helmet-async';
4
+ import Layout from '@/components/Layout/Layout';
4
5
  import "./page.css";
5
6
 
6
7
  export const Page = () => {
7
8
  const router = useRouter();
8
9
  return (
10
+ <Layout>
9
- <div className="about-page">
11
+ <div className="about-page">
10
- <Helmet>
12
+ <Helmet>
11
- <title>About Page @ {router.pathname}</title>
13
+ <title>About Page @ {router.pathname}</title>
12
- <meta name="description" content="Showcase of using parotta meta-framework." />
14
+ <meta name="description" content="Showcase of using parotta meta-framework." />
13
- </Helmet>
15
+ </Helmet>
14
- <div>
16
+ <div>
15
- <h1>About Page @ {router.pathname}</h1>
17
+ <h1>About Page @ {router.pathname}</h1>
16
- <p>Showcase of using parotta meta-framework.</p>
18
+ <p>Showcase of using parotta meta-framework.</p>
19
+ </div>
20
+ <footer>
21
+ <Link href="/">Back</Link>
22
+ </footer>
17
23
  </div>
18
- <footer>
19
- <Link href="/">Back</Link>
20
- </footer>
24
+ </Layout>
21
- </div>
22
25
  )
23
26
  }
24
27
 
pages/layout.jsx DELETED
@@ -1,20 +0,0 @@
1
- import React, { Suspense } from 'react';
2
- import { Link } from "parotta/runtime";
3
- import { ErrorBoundary } from "parotta/runtime";
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;
pages/page.jsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { useRouter } from "parotta/runtime";
3
+ import Layout from '@/components/Layout/Layout';
3
4
  import Counter from "@/components/Counter/Counter";
4
5
  import { Helmet } from 'react-helmet-async';
5
6
  import "./page.css";
@@ -10,7 +11,7 @@ const Page = () => {
10
11
 
11
12
  }, []);
12
13
  return (
13
- <div>
14
+ <Layout>
14
15
  <Helmet>
15
16
  <title>Parotta App</title>
16
17
  </Helmet>
@@ -21,7 +22,7 @@ const Page = () => {
21
22
  </p>
22
23
  <Counter />
23
24
  </div>
24
- </div>
25
+ </Layout>
25
26
  )
26
27
  }
27
28
 
pages/todos/page.css CHANGED
@@ -1,4 +1,4 @@
1
- body{
1
+ body {
2
2
  padding: 10px;
3
3
  background-color: turquoise;
4
4
  }
pages/todos/page.jsx CHANGED
@@ -1,19 +1,35 @@
1
1
  import React, { Suspense } from 'react';
2
- import TodoList from "@/containers/TodoList/TodoList";
3
2
  import { Helmet } from 'react-helmet-async';
3
+ import { useRpc } from "parotta/runtime";
4
+ import Todo from "@/components/Todo/Todo";
5
+ import { getTodos } from "@/services/todos.service";
6
+ import Layout from '@/components/Layout/Layout';
4
7
  import "./page.css";
5
8
 
9
+ const TodoList = () => {
10
+ const { data } = useRpc(getTodos, {});
11
+ return (
12
+ <ul>
13
+ {data.map((item) => (
14
+ <Todo key={item.id} todo={item} />
15
+ ))}
16
+ </ul>
17
+ )
18
+ }
19
+
6
20
  const Page = () => {
7
21
  return (
8
- <div>
22
+ <Layout>
9
23
  <h1>Todos</h1>
10
24
  <Helmet>
11
25
  <title>Todos Page</title>
12
26
  </Helmet>
27
+ <div>
13
- <Suspense fallback={<p>Loading...</p>}>
28
+ <Suspense fallback="Loading...">
14
- <TodoList />
29
+ <TodoList />
15
- </Suspense>
30
+ </Suspense>
16
- </div>
31
+ </div>
32
+ </Layout>
17
33
  )
18
34
  }
19
35
 
parotta/runtime.js CHANGED
@@ -1,13 +1,11 @@
1
1
  import React, {
2
- createElement, createContext, useContext, useState, useEffect, useTransition
2
+ Suspense, createElement, createContext, useContext, useState, useEffect, useTransition
3
3
  } from "react";
4
4
  import { HelmetProvider } from 'react-helmet-async';
5
+ import { ErrorBoundary } from "react-error-boundary";
5
6
 
6
7
  export const domain = () => typeof window !== 'undefined' ? window.origin : "http://0.0.0.0:3000";
7
8
 
8
- const changedArray = (a = [], b = []) =>
9
- a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]))
10
-
11
9
  export const rpc = (serviceName) => async (params = {}) => {
12
10
  const res = await fetch(`${domain()}/services/${serviceName}`, {
13
11
  method: "POST",
@@ -64,7 +62,7 @@ export const RouterContext = createContext(undefined);
64
62
  const getMatch = (radixRouter, pathname) => {
65
63
  const matchedPage = radixRouter.lookup(pathname);
66
64
  if (!matchedPage) {
67
- return radixRouter.lookup("/404");
65
+ return React.lazy(() => import("/pages/_404/page.jsx"));
68
66
  }
69
67
  return matchedPage;
70
68
  }
@@ -118,8 +116,13 @@ export const App = ({ nProgress, history, radixRouter, rpcCache, helmetContext }
118
116
  history: history,
119
117
  params: match.params || {},
120
118
  },
119
+ children: createElement(ErrorBoundary, {
120
+ onError: (err) => console.log(err),
121
+ fallback: createElement("p", {}, "Oops something went wrong"),
121
- children: createElement(match.Layout, {
122
+ children: createElement(Suspense, {
123
+ fallback: createElement("p", {}, "Loading..."),
122
- children: createElement(match.Page, {}),
124
+ children: createElement(match, {}),
125
+ }),
123
126
  }),
124
127
  }),
125
128
  }),
@@ -167,42 +170,4 @@ export const NavLink = ({ children, className, activeClassName, ...props }) => {
167
170
  className: classNames,
168
171
  ...props,
169
172
  })
170
- }
171
-
172
- export class ErrorBoundary extends React.Component {
173
- // static propTypes = {
174
- // resetKeys: PropTypes.arrayOf(PropTypes.any),
175
- // }
176
- static getDerivedStateFromError(error) {
177
- return { error }
178
- }
179
-
180
- state = {}
181
-
182
- componentDidCatch(error, info) {
183
- this.props.onError?.(error, info)
184
- }
185
-
186
- componentDidUpdate(prevProps, prevState) {
187
- const { error } = this.state
188
- const { resetKeys } = this.props
189
- if (
190
- error !== null &&
191
- prevState.error !== null &&
192
- changedArray(prevProps.resetKeys, resetKeys)
193
- ) {
194
- this.setState({});
195
- }
196
- }
197
-
198
- render() {
199
- const { error } = this.state;
200
- const { children, fallback } = this.props;
201
- if (error) {
202
- if (React.isValidElement(fallback)) {
203
- return fallback;
204
- }
205
- }
206
- return children;
207
- }
208
173
  }
parotta/server.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
  import { renderToReadableStream } from "react-dom/server";
3
3
  import path from 'path';
4
- import fs from 'fs';
5
4
  import walkdir from 'walkdir';
6
5
  import postcss from "postcss"
7
6
  import autoprefixer from "autoprefixer";
@@ -29,7 +28,8 @@ const createServerRouter = async () => {
29
28
  .replace("/pages", "")
30
29
  // .replaceAll("[", ":")
31
30
  // .replaceAll("]", "")
32
- );
31
+ )
32
+
33
33
  walkdir.sync(path.join(process.cwd(), "services"))
34
34
  .map((s) => s.replace(process.cwd(), ""))
35
35
  .filter((s) => s.includes(".service.js"))
@@ -43,12 +43,6 @@ const createServerRouter = async () => {
43
43
  const key = page.route || "/";
44
44
  routes[key] = { key: key, page: page.path };
45
45
  });
46
- dirs.filter((p) => p.includes('layout.jsx'))
47
- .map((s) => ({ path: s, route: s.replace("/layout.jsx", "") }))
48
- .forEach((item) => {
49
- const key = item.route || "/";
50
- routes[key].layout = item.path;
51
- });
52
46
  walkdir.sync(path.join(process.cwd(), "static"))
53
47
  .map((s) => s.replace(process.cwd(), "").replace("/static", ""))
54
48
  .forEach((route) => {
@@ -63,21 +57,15 @@ const createServerRouter = async () => {
63
57
 
64
58
  const createClientRouter = async () => {
65
59
  const routes = await walkdir.sync(path.join(process.cwd(), "pages"))
66
- .filter((p) => p.includes('page.jsx'))
60
+ .filter((p) => p.includes("page.jsx"))
61
+ .filter((p) => !p.includes("/_"))
67
62
  .map((s) => s.replace(process.cwd(), ""))
68
63
  .map((s) => s.replace("/pages", ""))
69
64
  .map((s) => s.replace("/page.jsx", ""))
70
65
  .reduce(async (accp, r) => {
71
66
  const acc = await accp;
72
67
  const src = await import(`${process.cwd()}/pages${r}/page.jsx`);
73
- const exists = fs.existsSync(`${process.cwd()}/pages${r}/layout.jsx`);
74
- const lpath = exists ? `/pages${r}/layout.jsx` : `/pages/layout.jsx`;
75
- const lsrc = await import(`${process.cwd()}${lpath}`);
76
- acc[r === "" ? "/" : r] = {
68
+ acc[r === "" ? "/" : r] = src.default;
77
- Page: src.default,
78
- Layout: lsrc.default,
79
- LayoutPath: lpath,
80
- }
81
69
  return acc
82
70
  }, Promise.resolve({}));
83
71
  // console.log(clientRoutes);
@@ -93,11 +81,7 @@ const createClientRouter = async () => {
93
81
  const radixRouter = createRouter({
94
82
  strictTrailingSlash: true,
95
83
  routes: {
96
- ${Object.keys(routes).map((r) => `"${r}": {
97
- Page: React.lazy(() => import("/pages${r}/page.jsx")),
84
+ ${Object.keys(routes).map((r) => `"${r}": React.lazy(() => import("/pages${r}/page.jsx"))`).join(',\n ')}
98
- Layout: React.lazy(() => import("${routes[r].LayoutPath}")),
99
- LayoutPath: "${routes[r].LayoutPath}",
100
- }`).join(',\n ')}
101
85
  },
102
86
  });
103
87
 
@@ -164,7 +148,6 @@ const renderPage = async (url) => {
164
148
  return acc;
165
149
  }, {})
166
150
  const components = mapDeps("components");
167
- const containers = mapDeps("containers");
168
151
  const importMap = {
169
152
  "radix3": `https://esm.sh/radix3`,
170
153
  "history": "https://esm.sh/history@5.3.0",
@@ -177,7 +160,6 @@ const renderPage = async (url) => {
177
160
  "parotta/runtime": `/parotta/runtime.js`,
178
161
  ...nodeDeps,
179
162
  ...components,
180
- ...containers,
181
163
  };
182
164
  const history = createMemoryHistory({
183
165
  initialEntries: [url.pathname + url.search],
@@ -188,7 +170,6 @@ const renderPage = async (url) => {
188
170
  <html lang="en">
189
171
  <head>
190
172
  <link rel="stylesheet" href="https://unpkg.com/nprogress@0.2.0/nprogress.css" />
191
- {/* <link id="layoutCss" rel="stylesheet" href={ match.LayoutPath.replace("jsx", "css"),} /> */}
192
173
  <link id="pageCss" rel="stylesheet" href={`/pages${url.pathname}/page.css`} />
193
174
  <script type="importmap" dangerouslySetInnerHTML={{ __html: JSON.stringify({ "imports": importMap }) }} />
194
175
  </head>
@@ -211,8 +192,11 @@ const renderPage = async (url) => {
211
192
  </body>
212
193
  </html >
213
194
  );
195
+ // TODO:
214
- console.log("helmetContext", helmetContext.helmet.title.toString());
196
+ // if (bot || isCrawler) {
197
+ // await stream.allReady
215
- console.log("helmetContext", helmetContext.helmet.link.toString());
198
+ // add helmetContext to head
199
+ // }
216
200
  return new Response(stream, {
217
201
  headers: { 'Content-Type': 'text/html' },
218
202
  status: 200,
@@ -311,7 +295,8 @@ const server = async (req) => {
311
295
  return renderJs(path.join(process.cwd(), url.pathname));
312
296
  }
313
297
  const match = serverRouter.lookup(url.pathname);
298
+ // TODO: maybe remove this as renderPage would handle it in clientRouter
314
- if (match) {
299
+ if (match && !match.key.includes("/_")) {
315
300
  if (match.file) {
316
301
  return sendFile(path.join(process.cwd(), `/static${match.file}`));
317
302
  }
@@ -323,8 +308,10 @@ const server = async (req) => {
323
308
  }
324
309
  }
325
310
  if (req.headers.get("Accept")?.includes('text/html')) {
326
- return renderPage(url);
311
+ // not found html page
312
+ return renderPage(new URL(`${url.protocol}//${url.host}/_404`));
327
313
  }
314
+ // not found generic page
328
315
  return new Response(`{"message": "not found"}`, {
329
316
  headers: { 'Content-Type': 'application/json' },
330
317
  status: 404,
services/todos.service.js CHANGED
@@ -2,8 +2,6 @@ import { eq, asc } from 'drizzle-orm';
2
2
  import db, { todos } from "@/db";
3
3
 
4
4
  export const getTodos = async () => {
5
- // console.log("getTodos");
6
- // return [];
7
5
  return await db.select().from(todos).orderBy(asc(todos.id));
8
6
  }
9
7