~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


3acb86f1 Peter John

2 years ago
use head and body
packages/create-parotta-app/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import os from 'os';
7
+ import chalk from 'chalk';
8
+ import gunzip from 'gunzip-file';
9
+ import packageJson from './package.json'
10
+
11
+ process.on('SIGINT', () => process.exit(0))
12
+ process.on('SIGTERM', () => process.exit(0))
13
+
14
+ let projectPath = "";
15
+ const program = new Command(packageJson.name)
16
+ .version(packageJson.version)
17
+ .arguments('<project-directory>')
18
+ .usage(`${chalk.green('<project-directory>')} [options]`)
19
+ .action((name) => {
20
+ projectPath = name
21
+ })
22
+
23
+ // program.parse();
24
+
25
+
26
+ const tmpDir = os.tmpdir();
27
+ console.log('tmpDir', tmpDir);
28
+ const tmpFile = path.join(tmpDir, "main.zip");
29
+ const res = await fetch("https://github.com/pyrossh/parotta/archive/refs/heads/main.zip");
30
+ await Bun.write(tmpFile, res);
31
+ await gunzip(tmpFile, os.tmpdir());
packages/create-parotta-app/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "create-parotta-app",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "chalk": "^5.2.0",
7
+ "commander": "10.0.0",
8
+ "extract-files": "^13.0.0",
9
+ "gunzip-file": "^0.1.1",
10
+ "tar": "^6.1.13",
11
+ "unzip": "^0.1.11"
12
+ },
13
+ "bin": {
14
+ "parotta": "./index.js"
15
+ }
16
+ }
packages/example/containers/TodoList/TodoList.jsx CHANGED
@@ -7,7 +7,7 @@ const todos = [
7
7
  ];
8
8
 
9
9
  const TodoList = () => {
10
- // const { data: todos } = usePromise("/todos");
10
+ // const { data: todos } = useFetch("/todos");
11
11
  return (
12
12
  <div className="todo-list">
13
13
  <h1>Todos</h1>
packages/example/jsconfig.json CHANGED
@@ -7,7 +7,6 @@
7
7
  },
8
8
  // TODO these options are not supported
9
9
  "jsx": "react",
10
- "jsxFactory": "React.createElement",
10
+ "jsxFactory": "React.createElement"
11
- "jsxImportSource": "react"
12
11
  }
13
12
  }
packages/example/routes/404/page.css CHANGED
@@ -1,4 +1,4 @@
1
- .notfound-page {
1
+ body {
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
  align-items: center;
packages/example/routes/404/page.jsx CHANGED
@@ -1,14 +1,18 @@
1
1
  import React from 'react';
2
2
  import "./page.css";
3
3
 
4
- export default function Page() {
4
+ export const Head = () => {
5
5
  return (
6
- <div className="notfound-page">
6
+ <title>Page not found</title>
7
+ )
8
+ }
9
+
10
+ export const Body = () => {
11
+ return (
7
- <div>
12
+ <div>
8
- <h1>404</h1>
13
+ <h1>404</h1>
9
- <div className="content">
14
+ <div className="content">
10
- <h2>This page could not be found</h2>
15
+ <h2>This page could not be found</h2>
11
- </div>
12
16
  </div>
13
17
  </div>
14
18
  )
packages/example/routes/about/page.css CHANGED
@@ -1,5 +1,10 @@
1
- .about-page {
1
+ body {
2
+ margin: 0;
2
3
  padding: 20px;
3
4
  padding-bottom: 130px;
4
5
  background-color: violet;
6
+
7
+ & footer {
8
+ margin-top: 100px;
9
+ }
5
10
  }
packages/example/routes/about/page.jsx CHANGED
@@ -2,13 +2,13 @@ import React from 'react';
2
2
  import { Link, useRouter } from "parotta/router";
3
3
  import "./page.css";
4
4
 
5
- export function Head() {
5
+ export const Head = () => {
6
6
  return (
7
7
  <title>About us</title>
8
8
  )
9
9
  }
10
10
 
11
- export default function Page() {
11
+ export const Body = () => {
12
12
  const router = useRouter();
13
13
  return (
14
14
  <div className="about-page">
packages/example/routes/page.css CHANGED
@@ -1,8 +1,5 @@
1
1
  body {
2
2
  margin: 0;
3
- }
4
-
5
- .home-page {
6
3
  padding: 20px;
7
4
  margin: 0;
8
5
  background-color: turquoise;
packages/example/routes/page.jsx CHANGED
@@ -4,14 +4,14 @@ import { useFetch } from "parotta/fetch";
4
4
  import Counter from "@/components/Counter/Counter";
5
5
  import "./page.css";
6
6
 
7
- export function Head() {
7
+ export const Head = () => {
8
8
  const { data } = useFetch("/todos");
9
9
  return (
10
10
  <title>Parotta</title>
11
11
  )
12
12
  }
13
13
 
14
- export default function Page() {
14
+ export const Body = () => {
15
15
  const { data, cache } = useFetch("/todos");
16
16
  console.log('page');
17
17
  console.log('data', data);
@@ -22,7 +22,7 @@ export default function Page() {
22
22
  }, [])
23
23
  const router = useRouter();
24
24
  return (
25
- <div className="home-page">
25
+ <div>
26
26
  <div>
27
27
  <h1>Home Page</h1>
28
28
  <p>
packages/example/routes/todos/:id/page.css CHANGED
@@ -1,11 +1,4 @@
1
- .home-page {
1
+ .body{
2
2
  padding: 10px;
3
3
  background-color: turquoise;
4
-
5
- & .count {
6
- color: black;
7
- padding: 40px;
8
- font-size: 30px;
9
- font-weight: 600;
10
- }
11
4
  }
packages/example/routes/todos/:id/page.jsx CHANGED
@@ -1,14 +1,20 @@
1
1
  import { Suspense } from "react";
2
2
  import { Link, useRouter } from "parotta/router";
3
3
  import { useFetch } from "parotta/fetch";
4
- // import "./index.css";
5
4
 
6
- export default function Page() {
5
+ export const Head = () => {
6
+ const { params } = useRouter();
7
+ return (
8
+ <title>Todo: {params.id}</title>
9
+ )
10
+ }
11
+
12
+ export const Body = () => {
7
13
  const { params } = useRouter();
8
14
  const data = useFetch(`/todos/${params.id}`);
9
15
  console.log('data', data);
10
16
  return (
11
- <div className="todos-page">
17
+ <div>
12
18
  <h1>Todo no: {params.id}</h1>
13
19
  </div>
14
20
  )
packages/example/routes/todos/page.css CHANGED
@@ -1,11 +1,4 @@
1
- .home-page {
1
+ body{
2
2
  padding: 10px;
3
3
  background-color: turquoise;
4
-
5
- & .count {
6
- color: black;
7
- padding: 40px;
8
- font-size: 30px;
9
- font-weight: 600;
10
- }
11
4
  }
packages/example/routes/todos/page.jsx CHANGED
@@ -1,10 +1,15 @@
1
1
  import { Suspense } from "react";
2
2
  import TodoList from "@/containers/TodoList/TodoList";
3
- // import "./index.css";
4
3
 
5
- export default function Page() {
4
+ export const Head = () => {
6
5
  return (
7
- <div className="todos-page">
6
+ <title>Todos</title>
7
+ )
8
+ }
9
+
10
+ export const Body = () => {
11
+ return (
12
+ <div>
8
13
  <h1>Todos</h1>
9
14
  {/* <Suspense>
10
15
  <TodoList todos={todos} />
packages/parotta/cli.js CHANGED
@@ -11,7 +11,7 @@ import postcssNesting from "postcss-nesting";
11
11
  import { createMemoryHistory } from "history";
12
12
  import { createRouter } from 'radix3';
13
13
  import mimeTypes from "mime-types";
14
- import { Header, Router } from "./router";
14
+ import { Head, Body } from "./router";
15
15
  import { renderToReadableStream } from 'react-dom/server';
16
16
  // import { renderToStream } from './render';
17
17
 
@@ -79,17 +79,17 @@ const serverRouter = createRouter({
79
79
 
80
80
  const clientRoutes = clientSideRoutes.reduce((acc, r) => {
81
81
  const Head = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
82
- const Page = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
82
+ const Body = import(`${process.cwd()}/routes${r === "" ? "" : r}/page.jsx`);
83
83
  acc[r === "" ? "/" : r] = {
84
84
  Head,
85
- Page,
85
+ Body,
86
86
  }
87
87
  return acc
88
88
  }, {});
89
89
 
90
90
  for (const k of Object.keys(clientRoutes)) {
91
91
  clientRoutes[k].Head = (await clientRoutes[k].Head).Head;
92
- clientRoutes[k].Page = (await clientRoutes[k].Page).default;
92
+ clientRoutes[k].Body = (await clientRoutes[k].Body).Body;
93
93
  }
94
94
 
95
95
  const clientRouter = createRouter({
@@ -156,20 +156,18 @@ const renderPage = async (url) => {
156
156
  const stream = await renderToReadableStream(
157
157
  <html lang="en">
158
158
  <head>
159
- <Header
159
+ <Head
160
160
  history={history}
161
161
  radixRouter={clientRouter}
162
162
  importMap={importMap}
163
163
  />
164
164
  </head>
165
165
  <body>
166
- <div id="page">
167
- <Router
166
+ <Body
168
- App={React.lazy(() => import(`${process.cwd()}/routes/app.jsx`))}
167
+ App={React.lazy(() => import(`${process.cwd()}/routes/app.jsx`))}
169
- history={history}
168
+ history={history}
170
- radixRouter={clientRouter}
169
+ radixRouter={clientRouter}
171
- />
170
+ />
172
- </div>
173
171
  {config.hydrate &&
174
172
  <>
175
173
  <script type="module" defer={true} dangerouslySetInnerHTML={{
@@ -178,7 +176,7 @@ import React from "react";
178
176
  import { hydrateRoot } from "react-dom/client";
179
177
  import { createBrowserHistory } from "history";
180
178
  import { createRouter } from "radix3";
181
- import { Header, Router } from "parotta/router";
179
+ import { Head, Body } from "parotta/router";
182
180
 
183
181
  const history = createBrowserHistory();
184
182
  const radixRouter = createRouter({
@@ -186,17 +184,17 @@ const radixRouter = createRouter({
186
184
  routes: {
187
185
  ${clientSideRoutes.map((r) => `"${r === "" ? "/" : r}": {
188
186
  Head: React.lazy(() => import("/routes${r === "" ? "" : r}/page.jsx").then((js) => ({ default: js.Head }))),
189
- Page: React.lazy(() => import("/routes${r === "" ? "" : r}/page.jsx")),
187
+ Body: React.lazy(() => import("/routes${r === "" ? "" : r}/page.jsx").then((js) => ({ default: js.Body }))),
190
188
  }`).join(',\n ')}
191
189
  },
192
190
  });
193
191
 
194
- hydrateRoot(document.head, React.createElement(Header, {
192
+ hydrateRoot(document.head, React.createElement(Head, {
195
193
  history,
196
194
  radixRouter,
197
195
  }))
198
196
 
199
- hydrateRoot(document.getElementById("page"), React.createElement(Router, {
197
+ hydrateRoot(document.body, React.createElement(Body, {
200
198
  App: React.lazy(() => import("/routes/app.jsx")),
201
199
  history,
202
200
  radixRouter,
packages/parotta/router.js CHANGED
@@ -13,7 +13,7 @@ const getMatch = (radixRouter, pathname) => {
13
13
 
14
14
  const getCssUrl = (pathname) => `/routes${pathname === "/" ? "/page.css" : pathname + "/page.css"}`;
15
15
 
16
- export const Header = ({ history, radixRouter, importMap }) => {
16
+ export const Head = ({ history, radixRouter, importMap }) => {
17
17
  const pathname = useSyncExternalStore(history.listen, (v) => v ? v.location.pathname : history.location.pathname, () => history.location.pathname);
18
18
  const match = getMatch(radixRouter, pathname);
19
19
  const initialCss = useMemo(() => getCssUrl(history.location.pathname), []);
@@ -24,8 +24,9 @@ export const Header = ({ history, radixRouter, importMap }) => {
24
24
  href: "https://unpkg.com/nprogress@0.2.0/nprogress.css",
25
25
  }),
26
26
  React.createElement("link", {
27
+ id: "pageCss",
27
28
  rel: "stylesheet",
28
- href: initialCss,
29
+ href: getCssUrl(history.location.pathname),
29
30
  }),
30
31
  React.createElement(React.Suspense, {
31
32
  children: React.createElement(match.Head, {}),
@@ -41,32 +42,32 @@ export const Header = ({ history, radixRouter, importMap }) => {
41
42
  });
42
43
  }
43
44
 
44
- export const Router = ({ App, history, radixRouter }) => {
45
+ export const Body = ({ App, history, radixRouter }) => {
45
46
  const [isPending, startTransition] = React.useTransition();
46
47
  const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
47
48
  useEffect(() => {
48
49
  return history.listen(({ location }) => {
49
- const href = getCssUrl(location.pathname);
50
+ // const href = getCssUrl(location.pathname);
50
- const isLoaded = Array.from(document.getElementsByTagName("link"))
51
+ // const isLoaded = Array.from(document.getElementsByTagName("link"))
51
- .map((link) => link.href.replace(window.origin, "")).includes(href);
52
+ // .map((link) => link.href.replace(window.origin, "")).includes(href);
52
- if (!isLoaded) {
53
+ // if (!isLoaded) {
53
- const link = document.createElement('link');
54
+ // const link = document.createElement('link');
54
- link.setAttribute("rel", "stylesheet");
55
+ // link.setAttribute("rel", "stylesheet");
55
- link.setAttribute("type", "text/css");
56
+ // link.setAttribute("type", "text/css");
56
- link.onload = () => {
57
+ // link.onload = () => {
57
- nProgress.start();
58
+ // nProgress.start();
58
- startTransition(() => {
59
+ // startTransition(() => {
59
- setMatch(getMatch(radixRouter, location.pathname));
60
+ // setMatch(getMatch(radixRouter, location.pathname));
60
- })
61
+ // })
61
- };
62
+ // };
62
- link.setAttribute("href", href);
63
+ // link.setAttribute("href", href);
63
- document.getElementsByTagName("head")[0].appendChild(link);
64
+ // document.getElementsByTagName("head")[0].appendChild(link);
64
- } else {
65
+ // } else {
65
66
  nProgress.start();
66
67
  startTransition(() => {
67
68
  setMatch(getMatch(radixRouter, location.pathname));
68
69
  })
69
- }
70
+ // }
70
71
  });
71
72
  }, []);
72
73
  useEffect(() => {
@@ -81,7 +82,7 @@ export const Router = ({ App, history, radixRouter }) => {
81
82
  params: match.params || {},
82
83
  },
83
84
  children: React.createElement(App, {
84
- children: React.createElement(match.Page, {}),
85
+ children: React.createElement(match.Body, {}),
85
86
  }),
86
87
  });
87
88
  }
readme.md CHANGED
@@ -7,4 +7,6 @@ It is very opionated and has set of idiomatic ways of doing things.
7
7
  ### Todo
8
8
  1. Add build step
9
9
  2. Deploy to Docker, Deno deploy, Vercel edge functions, Cloudflare workers, Bun edge (whenever it releases)
10
- 3. Hydrate fetch cache
10
+ 3. Hydrate fetch cache
11
+ 4. Build a proper example (create-parotta-app)
12
+ 5. Build a Webiste with Docs using parotta