~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


72dacf83 Peter John

2 years ago
use react-helmet-async
bun.lockb CHANGED
Binary file
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-helmet-async": "^1.3.0",
20
21
  "sql-highlight": "^4.3.2"
21
22
  },
22
23
  "devDependencies": {
@@ -32,6 +33,9 @@
32
33
  "node_modules/normalize.css/normalize.css"
33
34
  ]
34
35
  },
36
+ "prettier": {
37
+ "printWidth": 120
38
+ },
35
39
  "eslintConfig": {
36
40
  "root": true,
37
41
  "parserOptions": {
pages/404/page.jsx CHANGED
@@ -1,13 +1,7 @@
1
1
  import React from 'react';
2
2
  import "./page.css";
3
3
 
4
- export const Head = () => {
4
+ const Page = () => {
5
- return (
6
- <title>Page not found</title>
7
- )
8
- }
9
-
10
- export const Body = () => {
11
5
  return (
12
6
  <div>
13
7
  <h1>404</h1>
@@ -16,4 +10,6 @@ export const Body = () => {
16
10
  </div>
17
11
  </div>
18
12
  )
19
- }
13
+ }
14
+
15
+ export default Page;
pages/about/page.jsx CHANGED
@@ -1,19 +1,18 @@
1
1
  import React from 'react';
2
2
  import { Link, useRouter } from "parotta/runtime";
3
+ import { Helmet } from 'react-helmet-async';
3
4
  import "./page.css";
4
5
 
5
- export const Head = () => {
6
+ export const Page = () => {
6
- return (
7
- <title>About us</title>
8
- )
9
- }
10
-
11
- export const Body = () => {
12
7
  const router = useRouter();
13
8
  return (
14
9
  <div className="about-page">
10
+ <Helmet>
11
+ <title>About Page @ {router.pathname}</title>
12
+ <meta name="description" content="Showcase of using parotta meta-framework." />
13
+ </Helmet>
15
14
  <div>
16
- <h1>About Page</h1>
15
+ <h1>About Page @ {router.pathname}</h1>
17
16
  <p>Showcase of using parotta meta-framework.</p>
18
17
  </div>
19
18
  <footer>
@@ -21,4 +20,6 @@ export const Body = () => {
21
20
  </footer>
22
21
  </div>
23
22
  )
24
- }
23
+ }
24
+
25
+ export default Page;
pages/page.jsx CHANGED
@@ -1,21 +1,19 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { useRouter } from "parotta/runtime";
3
3
  import Counter from "@/components/Counter/Counter";
4
+ import { Helmet } from 'react-helmet-async';
4
5
  import "./page.css";
5
6
 
6
- export const Head = () => {
7
+ const Page = () => {
7
- return (
8
- <title>Parotta App</title>
9
- )
10
- }
11
-
12
- export const Body = () => {
13
8
  const router = useRouter();
14
9
  useEffect(() => {
15
10
 
16
11
  }, []);
17
12
  return (
18
13
  <div>
14
+ <Helmet>
15
+ <title>Parotta App</title>
16
+ </Helmet>
19
17
  <div>
20
18
  <h1>Home Page</h1>
21
19
  <p>
@@ -25,4 +23,6 @@ export const Body = () => {
25
23
  </div>
26
24
  </div>
27
25
  )
28
- }
26
+ }
27
+
28
+ export default Page;
pages/todos/page.jsx CHANGED
@@ -1,17 +1,15 @@
1
1
  import React, { Suspense } from 'react';
2
2
  import TodoList from "@/containers/TodoList/TodoList";
3
+ import { Helmet } from 'react-helmet-async';
3
4
  import "./page.css";
4
5
 
5
- export const Head = () => {
6
+ const Page = () => {
6
- return (
7
- <title>Todos</title>
8
- )
9
- }
10
-
11
- export const Body = () => {
12
7
  return (
13
8
  <div>
14
9
  <h1>Todos</h1>
10
+ <Helmet>
11
+ <title>Todos Page</title>
12
+ </Helmet>
15
13
  <Suspense fallback={<p>Loading...</p>}>
16
14
  <TodoList />
17
15
  </Suspense>
@@ -19,3 +17,4 @@ export const Body = () => {
19
17
  )
20
18
  }
21
19
 
20
+ export default Page;
parotta/package.json CHANGED
@@ -11,5 +11,8 @@
11
11
  "postcss-nesting": "^11.2.1",
12
12
  "radix3": "^1.0.0",
13
13
  "walkdir": "0.4.1"
14
+ },
15
+ "prettier": {
16
+ "printWidth": 120
14
17
  }
15
18
  }
parotta/runtime.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, {
2
- Fragment, Suspense, createElement, createContext,
3
- useContext, useState, useEffect, useSyncExternalStore, useTransition
2
+ createElement, createContext, useContext, useState, useEffect, useTransition
4
3
  } from "react";
4
+ import { HelmetProvider } from 'react-helmet-async';
5
5
 
6
6
  export const domain = () => typeof window !== 'undefined' ? window.origin : "http://0.0.0.0:3000";
7
7
 
@@ -69,48 +69,14 @@ const getMatch = (radixRouter, pathname) => {
69
69
  return matchedPage;
70
70
  }
71
71
 
72
- const getCssUrl = (pathname) => `/pages${pathname === "/" ? "" : pathname}`;
72
+ const getCssUrl = (pathname) => `/pages${pathname === "/" ? "" : pathname}/page.css`;
73
-
74
- export const HeadApp = ({ history, radixRouter, importMap }) => {
75
- const pathname = useSyncExternalStore(history.listen, (v) => v ? v.location.pathname : history.location.pathname, () => history.location.pathname);
76
- const match = getMatch(radixRouter, pathname);
77
- // const initialCss = useMemo(() => getCssUrl(history.location.pathname), []);
78
- return createElement(Fragment, {
79
- children: [
80
- createElement("link", {
81
- rel: "stylesheet",
82
- href: "https://unpkg.com/nprogress@0.2.0/nprogress.css",
83
- }),
84
- createElement("link", {
85
- id: "layoutCss",
86
- rel: "stylesheet",
87
- href: match.LayoutPath.replace("jsx", "css"),
88
- }),
89
- createElement("link", {
90
- id: "pageCss",
91
- rel: "stylesheet",
92
- href: getCssUrl(history.location.pathname) + "/page.css",
93
- }),
94
- createElement(Suspense, {
95
- children: createElement(match.Head, {}),
96
- }),
97
- createElement("script", {
98
- id: "importmap",
99
- type: "importmap",
100
- dangerouslySetInnerHTML: {
101
- __html: JSON.stringify({ "imports": importMap }),
102
- }
103
- })
104
- ]
105
- });
106
- }
107
73
 
108
- export const BodyApp = ({ nProgress, history, radixRouter, rpcCache }) => {
74
+ export const App = ({ nProgress, history, radixRouter, rpcCache, helmetContext }) => {
109
75
  const [isPending, startTransition] = useTransition();
110
76
  const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
111
77
  useEffect(() => {
112
78
  return history.listen(({ location }) => {
113
- // const href = getCssUrl(location.pathname);
79
+ const href = getCssUrl(location.pathname);
114
80
  // const isLoaded = Array.from(document.getElementsByTagName("link"))
115
81
  // .map((link) => link.href.replace(window.origin, "")).includes(href);
116
82
  // if (!isLoaded) {
@@ -126,6 +92,11 @@ export const BodyApp = ({ nProgress, history, radixRouter, rpcCache }) => {
126
92
  // link.setAttribute("href", href);
127
93
  // document.getElementsByTagName("head")[0].appendChild(link);
128
94
  // } else {
95
+ const link = document.createElement('link');
96
+ link.setAttribute("rel", "stylesheet");
97
+ link.setAttribute("type", "text/css");
98
+ link.setAttribute("href", href);
99
+ document.getElementsByTagName("head")[0].appendChild(link);
129
100
  nProgress.start();
130
101
  startTransition(() => {
131
102
  setMatch(getMatch(radixRouter, location.pathname));
@@ -138,15 +109,18 @@ export const BodyApp = ({ nProgress, history, radixRouter, rpcCache }) => {
138
109
  nProgress.done();
139
110
  }
140
111
  }, [isPending]);
112
+ return createElement(HelmetProvider, {
113
+ context: helmetContext,
141
- return createElement(RpcContext.Provider, {
114
+ children: createElement(RpcContext.Provider, {
142
- value: rpcCache,
115
+ value: rpcCache,
143
- children: createElement(RouterContext.Provider, {
116
+ children: createElement(RouterContext.Provider, {
144
- value: {
117
+ value: {
145
- history: history,
118
+ history: history,
146
- params: match.params || {},
119
+ params: match.params || {},
147
- },
120
+ },
148
- children: createElement(match.Layout, {
121
+ children: createElement(match.Layout, {
149
- children: createElement(match.Body, {}),
122
+ children: createElement(match.Page, {}),
123
+ }),
150
124
  }),
151
125
  }),
152
126
  });
parotta/server.js CHANGED
@@ -10,7 +10,7 @@ import postcssNesting from "postcss-nesting";
10
10
  import { createMemoryHistory } from "history";
11
11
  import { createRouter } from 'radix3';
12
12
  import mimeTypes from "mime-types";
13
- import { HeadApp, BodyApp } from "./runtime";
13
+ import { App } from "./runtime";
14
14
 
15
15
  if (!globalThis.firstRun) {
16
16
  globalThis.firstRun = true
@@ -74,8 +74,7 @@ const createClientRouter = async () => {
74
74
  const lpath = exists ? `/pages${r}/layout.jsx` : `/pages/layout.jsx`;
75
75
  const lsrc = await import(`${process.cwd()}${lpath}`);
76
76
  acc[r === "" ? "/" : r] = {
77
- Head: src.Head,
77
+ Page: src.default,
78
- Body: src.Body,
79
78
  Layout: lsrc.default,
80
79
  LayoutPath: lpath,
81
80
  }
@@ -88,32 +87,26 @@ const createClientRouter = async () => {
88
87
  import { createBrowserHistory } from "history";
89
88
  import nProgress from "nprogress";
90
89
  import { createRouter } from "radix3";
91
- import { HeadApp, BodyApp } from "parotta/runtime";
90
+ import { App } from "parotta/runtime";
92
-
93
91
 
94
92
  const history = createBrowserHistory();
95
93
  const radixRouter = createRouter({
96
94
  strictTrailingSlash: true,
97
95
  routes: {
98
96
  ${Object.keys(routes).map((r) => `"${r}": {
99
- Head: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Head }))),
97
+ Page: React.lazy(() => import("/pages${r}/page.jsx")),
100
- Body: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Body }))),
101
98
  Layout: React.lazy(() => import("${routes[r].LayoutPath}")),
102
99
  LayoutPath: "${routes[r].LayoutPath}",
103
100
  }`).join(',\n ')}
104
101
  },
105
102
  });
106
103
 
107
- hydrateRoot(document.head, React.createElement(HeadApp, {
108
- history,
109
- radixRouter,
110
- }))
111
-
112
- hydrateRoot(document.body, React.createElement(BodyApp, {
104
+ hydrateRoot(document.body, React.createElement(App, {
113
105
  nProgress,
114
106
  history,
115
107
  radixRouter,
116
108
  rpcCache: {},
109
+ helmetContext: {},
117
110
  }));`
118
111
  const router = createRouter({
119
112
  strictTrailingSlash: true,
@@ -189,18 +182,24 @@ const renderPage = async (url) => {
189
182
  const history = createMemoryHistory({
190
183
  initialEntries: [url.pathname + url.search],
191
184
  });
185
+ const helmetContext = {}
192
186
  const nProgress = { start: () => { }, done: () => { } }
193
187
  const stream = await renderToReadableStream(
194
188
  <html lang="en">
195
189
  <head>
190
+ <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
+ <link id="pageCss" rel="stylesheet" href={`/pages${url.pathname}/page.css`} />
193
+ <script type="importmap" dangerouslySetInnerHTML={{ __html: JSON.stringify({ "imports": importMap }) }} />
194
+ </head>
195
+ <body>
196
- <HeadApp
196
+ <App
197
+ nProgress={nProgress}
197
198
  history={history}
198
199
  radixRouter={clientRouter}
200
+ rpcCache={{}}
199
- importMap={importMap}
201
+ helmetContext={helmetContext}
200
202
  />
201
- </head>
202
- <body>
203
- <BodyApp nProgress={nProgress} history={history} radixRouter={clientRouter} rpcCache={{}} />
204
203
  {config.hydrate &&
205
204
  <>
206
205
  <script type="module" defer={true} dangerouslySetInnerHTML={{
@@ -212,6 +211,8 @@ const renderPage = async (url) => {
212
211
  </body>
213
212
  </html >
214
213
  );
214
+ console.log("helmetContext", helmetContext.helmet.title.toString());
215
+ console.log("helmetContext", helmetContext.helmet.link.toString());
215
216
  return new Response(stream, {
216
217
  headers: { 'Content-Type': 'text/html' },
217
218
  status: 200,
@@ -264,7 +265,7 @@ const renderJs = async (srcFile) => {
264
265
  lines.unshift(`import { rpc } from "parotta/runtime";`);
265
266
  }
266
267
  // remove .css and .service imports
267
- const filteredJsx = lines.filter((ln) => !ln.includes(".css") && !ln.includes(".service")).join("\n");
268
+ const filteredJsx = lines.filter((ln) => !ln.includes(`.css"`) && !ln.includes(`.service"`)).join("\n");
268
269
  //.replaceAll("$jsx", "React.createElement");
269
270
  return new Response(filteredJsx, {
270
271
  headers: {