~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


377409cc Peter John

2 years ago
implement fetch
packages/example/package.json CHANGED
@@ -9,7 +9,6 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "react": "18.2.0",
12
- "react-dom": "^18.2.0",
12
+ "react-dom": "^18.2.0"
13
- "swr": "2.1.0"
14
13
  }
15
14
  }
packages/example/routes/404/page.jsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import "./page.css";
3
3
 
4
- const NotFound = () => {
4
+ export default function Page() {
5
5
  return (
6
6
  <div className="notfound-page">
7
7
  <div>
@@ -12,6 +12,4 @@ const NotFound = () => {
12
12
  </div>
13
13
  </div>
14
14
  )
15
- }
15
+ }
16
-
17
- export default NotFound;
packages/example/routes/about/page.jsx CHANGED
@@ -2,7 +2,15 @@ import React from 'react';
2
2
  import { Link, useRouter } from "parotta/router";
3
3
  import "./page.css";
4
4
 
5
+ export function Head() {
6
+ return (
7
+ <>
8
+ <title>About us</title>
9
+ </>
10
+ )
11
+ }
12
+
5
- const AboutPage = () => {
13
+ export default function Page() {
6
14
  const router = useRouter();
7
15
  return (
8
16
  <div className="about-page">
@@ -17,6 +25,4 @@ const AboutPage = () => {
17
25
  </footer>
18
26
  </div>
19
27
  )
20
- }
28
+ }
21
-
22
- export default AboutPage;
packages/example/routes/app.jsx CHANGED
@@ -1,24 +1,13 @@
1
1
  import React, { Suspense } from "react";
2
- import { SWRConfig } from "swr";
3
2
  import { ErrorBoundary } from "parotta/error";
4
3
 
5
- const App = ({ children }) => {
4
+ export default function App({ children }) {
6
5
  console.log('app');
7
6
  return (
8
- <SWRConfig value={{
9
- fallback: {
10
- 'https://jsonplaceholder.typicode.com/todos/1': { id: '123' },
11
- },
12
- // fallbackData: null,
13
- fetcher: (resource, init) => fetch(resource, init).then(res => res.json()), suspense: true
14
- }}>
15
- <ErrorBoundary onError={(err) => console.log(err)} fallback={<p>Oops something went wrong</p>}>
7
+ <ErrorBoundary onError={(err) => console.log(err)} fallback={<p>Oops something went wrong</p>}>
16
- <Suspense fallback={<p>Loading...</p>}>
8
+ <Suspense fallback={<p>Loading...</p>}>
17
- {children}
9
+ {children}
18
- </Suspense>
10
+ </Suspense>
19
- </ErrorBoundary>
11
+ </ErrorBoundary>
20
- </SWRConfig>
21
12
  )
22
- }
13
+ }
23
-
24
- export default App;
packages/example/routes/page.jsx CHANGED
@@ -1,13 +1,27 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
- import useSWR from "swr";
3
- import { Link, useRouter } from "parotta/router";
2
+ import { Link, useRouter, useFetch } from "parotta/router";
4
3
  import Counter from "@/components/Counter/Counter";
5
4
  import "./page.css";
6
5
 
6
+ export function Head() {
7
+ const { data } = useFetch("/todos");
8
+ return (
9
+ <>
10
+ <title>Parotta</title>
11
+ </>
12
+ )
13
+ }
14
+
7
- const HomePage = () => {
15
+ export default function Page() {
16
+ const { data, cache } = useFetch("/todos");
8
17
  console.log('page');
18
+ console.log('data', data);
19
+ useEffect(() => {
20
+ setTimeout(() => {
21
+ cache.invalidate(/todos/);
22
+ }, 3000)
23
+ }, [])
9
24
  // const todo = useAsync('123', () => getData());
10
- const { data } = useSWR(`https://jsonplaceholder.typicode.com/todos/1`);
11
25
  const router = useRouter();
12
26
  return (
13
27
  <div className="home-page">
@@ -23,6 +37,4 @@ const HomePage = () => {
23
37
  </footer>
24
38
  </div>
25
39
  )
26
- }
40
+ }
27
-
28
- export default HomePage;
packages/example/routes/todos/:id/api.js CHANGED
@@ -11,6 +11,14 @@ export const onGet = async (req) => {
11
11
  const item = await todosCollection.findOne({
12
12
  filter: { id },
13
13
  });
14
+ if (!item) {
15
+ return new Response(`todo '${id}' not found`, {
16
+ headers: {
17
+ "Content-Type": "application/json",
18
+ },
19
+ status: 404,
20
+ });
21
+ }
14
22
  const data = JSON.stringify(item);
15
23
  return new Response(data, {
16
24
  headers: {
packages/example/routes/todos/:id/page.css ADDED
@@ -0,0 +1,11 @@
1
+ .home-page {
2
+ padding: 10px;
3
+ background-color: turquoise;
4
+
5
+ & .count {
6
+ color: black;
7
+ padding: 40px;
8
+ font-size: 30px;
9
+ font-weight: 600;
10
+ }
11
+ }
packages/example/routes/todos/:id/page.jsx ADDED
@@ -0,0 +1,14 @@
1
+ import { Suspense } from "react";
2
+ import { Link, useRouter, useFetch } from "parotta/router";
3
+ // import "./index.css";
4
+
5
+ export default function Page() {
6
+ const { params } = useRouter();
7
+ const data = useFetch(`/todos/${params.id}`);
8
+ console.log('data', data);
9
+ return (
10
+ <div className="todos-page">
11
+ <h1>Todo no: {params.id}</h1>
12
+ </div>
13
+ )
14
+ }
packages/example/routes/todos/page.jsx CHANGED
@@ -1,17 +1,14 @@
1
1
  import { Suspense } from "react";
2
- import { useRouter } from "muffinjs/router.js";
3
- import TodoList from "@/containers/TodoList/TodoList.jsx";
2
+ import TodoList from "@/containers/TodoList/TodoList";
4
3
  // import "./index.css";
5
4
 
6
- const TodosPage = () => {
5
+ export default function Page() {
7
6
  return (
8
7
  <div className="todos-page">
9
8
  <h1>Todos</h1>
10
- <Suspense>
9
+ {/* <Suspense>
11
10
  <TodoList todos={todos} />
12
- </Suspense>
11
+ </Suspense> */}
13
12
  </div>
14
13
  )
15
- }
14
+ }
16
-
17
- export default TodosPage;
packages/parotta/cli.js CHANGED
@@ -112,10 +112,10 @@ const renderPage = async (filePath, url, params) => {
112
112
  const components = mapDeps("components");
113
113
  const containers = mapDeps("containers");
114
114
  const cssFile = `${filePath.replace("jsx", "css")}`;
115
+ const mainPage = await import(path.join(process.cwd(), filePath));
115
116
  const stream = await renderToReadableStream(
116
117
  <html lang="en">
117
118
  <head>
118
- <title>Parotta</title>
119
119
  <link rel="preload" href={cssFile} as="style" />
120
120
  <link rel="stylesheet" href={cssFile} />
121
121
  <script type="importmap" dangerouslySetInnerHTML={{
@@ -140,6 +140,7 @@ const renderPage = async (filePath, url, params) => {
140
140
  )
141
141
  }}>
142
142
  </script>
143
+ <mainPage.Head />
143
144
  <script type="module" defer={true} dangerouslySetInnerHTML={{
144
145
  __html: `
145
146
  import React from "react";
@@ -180,7 +181,6 @@ hydrateRoot(document.getElementById("root"), React.createElement(Router, {
180
181
  </body>
181
182
  </html >
182
183
  );
183
- // injectToStream('<script type="module" src="/main.js"></script>', { flush: true });
184
184
  return new Response(stream, {
185
185
  headers: { 'Content-Type': 'text/html' },
186
186
  status: 200,
packages/parotta/router.js CHANGED
@@ -1,8 +1,63 @@
1
- import React, { createContext, useContext, useState, useEffect } from "react";
1
+ import React, { createContext, useContext, useState, useEffect, useMemo } from "react";
2
+
3
+ export const isClient = () => typeof window !== 'undefined';
4
+ export const domain = () => isClient() ? window.origin : "http://0.0.0.0:3000";
5
+ export const globalCache = new Map();
6
+ export const useFetchCache = () => {
7
+ const [_, rerender] = useState(false);
8
+ const cache = useMemo(() => globalCache, []);
9
+ const get = (k) => cache.get(k)
10
+ const set = (k, v) => {
11
+ cache.set(k, v);
12
+ rerender((c) => !c);
13
+ }
14
+ const invalidate = (regex) => {
15
+ Array.from(cache.keys())
16
+ .filter((k) => regex.test(k))
17
+ .forEach((k) => {
18
+ fetchData(k).then((v) => set(k, v));
19
+ });
20
+ }
21
+ return {
22
+ get,
23
+ set,
24
+ invalidate,
25
+ }
26
+ }
27
+
28
+ const fetchData = async (route) => {
29
+ const url = `${domain()}${route}`;
30
+ console.log('url', url);
31
+ const res = await fetch(url, {
32
+ headers: {
33
+ "Accept": "application/json",
34
+ },
35
+ });
36
+ if (res.ok) {
37
+ return await res.json();
38
+ } else {
39
+ return new Error(await res.text());
40
+ }
41
+ }
42
+
43
+ export const useFetch = (url) => {
44
+ const cache = useFetchCache();
45
+ const value = cache.get(url);
46
+ if (value) {
47
+ if (value instanceof Promise) {
48
+ throw value;
49
+ } else if (value instanceof Error) {
50
+ throw value;
51
+ }
52
+ return { data: value, cache };
53
+ }
54
+ cache.set(url, fetchData(url).then((v) => cache.set(url, v)));
55
+ throw cache.get(url);
56
+ }
2
57
 
3
58
  export const RouterContext = createContext(undefined);
4
59
 
5
- const getRoute = (radixRouter, pathname) => {
60
+ const getMatch = (radixRouter, pathname) => {
6
61
  const matchedPage = radixRouter.lookup(pathname);
7
62
  if (!matchedPage) {
8
63
  return radixRouter.lookup("/404");
@@ -24,40 +79,35 @@ const loadCss = (pathname) => {
24
79
  }
25
80
 
26
81
  export const Router = ({ App, history, radixRouter }) => {
27
- const [Page, setPage] = useState(() => getRoute(radixRouter, history.location.pathname));
82
+ const [MatchedPage, setMatchedPage] = useState(() => getMatch(radixRouter, history.location.pathname));
28
83
  useEffect(() => {
29
84
  return history.listen(({ location }) => {
30
85
  loadCss(location.pathname);
31
- setPage(getRoute(radixRouter, location.pathname));
86
+ setMatchedPage(getMatch(radixRouter, location.pathname));
32
87
  });
33
88
  }, [])
34
89
  console.log('Router');
35
90
  return React.createElement(RouterContext.Provider, {
91
+ value: {
36
- value: history,
92
+ history: history,
93
+ params: MatchedPage.params || {},
94
+ },
37
95
  children: React.createElement(App, {
38
- children: React.createElement(Page, {})
96
+ children: React.createElement(MatchedPage, {})
39
97
  }),
40
98
  });
41
99
  }
42
100
 
43
101
  export const useRouter = () => {
44
- const history = useContext(RouterContext);
102
+ const { history, params } = useContext(RouterContext);
45
103
  return {
46
104
  pathname: history.location.pathname,
47
- query: {},
105
+ query: new URLSearchParams(history.location.search),
48
- params: {},
106
+ params,
49
- push: (path) => {
50
- history.push(path)
107
+ push: history.push,
51
- },
52
- replace: (path) => {
108
+ replace: history.replace,
53
- history.replace(path)
54
- },
55
- forward: () => {
56
- history.forward();
109
+ forward: history.forward,
57
- },
58
- back: () => {
59
- history.back();
110
+ back: history.back,
60
- },
61
111
  reload: () => window.location.reload(),
62
112
  };
63
113
  }
readme.md CHANGED
@@ -5,9 +5,8 @@ It uses File system routing with SSR with streaming + CSR as the method to rende
5
5
  It is very opionated and has set of idiomatic ways of doing things.
6
6
 
7
7
  ### Todo
8
- 1. Add async streaming
8
+ 1. Hydrate fetch cache
9
- 2. Add helmet functionality
10
- 3. Improve css loading
9
+ 2. Improve css loading
11
- 4. Improve react page loading (try to remove flash)
10
+ 3. Improve react page loading (try to remove flash)
12
- 5. Add build step
11
+ 4. Add build step
13
- 6. Deploy to Docker, Deno deploy, Cloudflare workers, Vercel edge functions, Bun edge (whenever it releases)
12
+ 5. Deploy to Docker, Deno deploy, Cloudflare workers, Vercel edge functions, Bun edge (whenever it releases)