~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


164eba9f pyrossh

2 years ago
Fix hydration issue
cli.js CHANGED
@@ -21,6 +21,7 @@ dotenv.config();
21
21
 
22
22
  let isProd = false;
23
23
  const srcDir = path.join(process.cwd(), "src");
24
+ const pagesDir = path.join(srcDir, "pages");
24
25
  const inputStaticDir = path.join(srcDir, "static");
25
26
  const buildDir = path.join(process.cwd(), "build");
26
27
  const staticDir = path.join(buildDir, "static");
@@ -36,13 +37,13 @@ const recordSize = (buildStart, dest) => {
36
37
  let generatedCss = ``;
37
38
  const cssCache = [];
38
39
  const serverEnvs = Object.keys(process.env)
39
- .filter((k) => k.startsWith("EC_") || k === "NODE_ENV")
40
+ .filter((k) => k.startsWith("EDGE_") || k === "NODE_ENV")
40
41
  .reduce((acc, k) => {
41
42
  acc[`process.env.${k}`] = JSON.stringify(process.env[k]);
42
43
  return acc
43
44
  }, {});
44
45
  const clientEnvs = Object.keys(process.env)
45
- .filter((k) => k.startsWith("EC_PUBLIC") || k === "NODE_ENV")
46
+ .filter((k) => k.startsWith("EDGE_PUBLIC") || k === "NODE_ENV")
46
47
  .reduce((acc, k) => {
47
48
  acc[`process.env.${k}`] = JSON.stringify(process.env[k]);
48
49
  return acc
@@ -94,7 +95,9 @@ const buildRouteMap = (routes) => {
94
95
  }
95
96
 
96
97
  const bundlePages = async () => {
97
- const routes = walkdir.sync(path.join(srcDir, "pages"))
98
+ const appExists = fs.existsSync(path.join(pagesDir, "app.jsx"));
99
+ const importAppComp = appExists ? `import App from "@/pages/app";` : ""
100
+ const routes = walkdir.sync(pagesDir)
98
101
  .filter((p) => p.includes("page.jsx"))
99
102
  .map((r) => ({
100
103
  in: r,
@@ -111,11 +114,12 @@ const bundlePages = async () => {
111
114
  const data = fs.readFileSync(args.path);
112
115
  const newSrc = `
113
116
  import renderPage from "edge-city/renderPage";
117
+ ${importAppComp}
114
118
 
115
119
  ${data.toString()}
116
120
 
117
121
  export function onRequest(context) {
118
- return renderPage(Page, context.request);
122
+ return renderPage(Page, App, context.request);
119
123
  }
120
124
  `
121
125
  return {
@@ -155,11 +159,13 @@ const bundlePages = async () => {
155
159
  const data = fs.readFileSync(args.path);
156
160
  const newSrc = `
157
161
  import { hydrateApp } from "edge-city";
162
+ ${importAppComp}
163
+
158
164
  ${data.toString()}
159
165
 
160
166
  const searchParams = new URL(import.meta.url).searchParams;
161
167
  if (searchParams.get("hydrate") === "true") {
162
- hydrateApp()
168
+ hydrateApp(App)
163
169
  }
164
170
  `
165
171
  return {
example/package.json CHANGED
@@ -3,6 +3,8 @@
3
3
  "type": "module",
4
4
  "scripts": {
5
5
  "dev": "edge-city dev -p cloudflare",
6
+ "dev:wrangler": "cd build && wrangler pages dev static",
7
+ "deploy:wrangler": "cd build && wrangler pages deploy static",
6
8
  "build": " edge-city build -p cloudflare",
7
9
  "generate": "drizzle-kit generate:pg --out migrations --schema migrations/schema.js",
8
10
  "migrate": "node scripts/migrate.js",
@@ -29,6 +31,7 @@
29
31
  "postgres": "3.3.4",
30
32
  "@playwright/test": "^1.31.2",
31
33
  "eslint": "^8.35.0",
34
+ "wrangler": "3.0.1",
32
35
  "eslint-config-react-app": "^7.0.1"
33
36
  },
34
37
  "prettier": {
example/scripts/migrate.js CHANGED
@@ -6,7 +6,7 @@ dotenv.config();
6
6
 
7
7
  const main = async () => {
8
8
  console.log("migration started");
9
- const client = postgres(process.env.EC_PG_CONN_URL + "?sslmode=require", { max: 1 });
9
+ const client = postgres(process.env.EDGE_PG_CONN_URL + "?sslmode=require", { max: 1 });
10
10
  await migrate(drizzle(client), { migrationsFolder: './migrations' });
11
11
  console.log("migration complete");
12
12
  }
example/src/pages/app.jsx ADDED
@@ -0,0 +1,11 @@
1
+ import { SSRProvider } from 'react-aria';
2
+
3
+ const App = ({ children }) => {
4
+ return (
5
+ <SSRProvider>
6
+ {children}
7
+ </SSRProvider>
8
+ )
9
+ }
10
+
11
+ export default App;
example/src/pages/todos/page.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Suspense, useEffect } from 'react';
1
+ import React, { Suspense } from 'react';
2
2
  import { Helmet } from 'react-helmet-async';
3
3
  import { useQuery, useMutation } from "edge-city";
4
4
  import { useForm } from 'react-hook-form';
@@ -29,7 +29,6 @@ export default Page;
29
29
 
30
30
  const TodoList = () => {
31
31
  const { data, refetch } = useQuery("todos", () => getTodos());
32
- console.log("data", data);
33
32
  const { mutate, isMutating, err } = useMutation(async ({ text }) => {
34
33
  await createTodo({
35
34
  text,
@@ -38,12 +37,11 @@ const TodoList = () => {
38
37
  await refetch();
39
38
  });
40
39
  const { register, handleSubmit, formState: { errors } } = useForm();
41
- console.log('err', err, errors);
42
- useEffect(() => {
40
+ // useEffect(() => {
43
- setTimeout(() => {
41
+ // setTimeout(() => {
44
- refetch();
42
+ // refetch();
45
- }, 3000)
43
+ // }, 3000)
46
- }, [])
44
+ // }, [])
47
45
  return (
48
46
  <div>
49
47
  <ul>
example/src/services/db.js CHANGED
@@ -2,7 +2,7 @@ import { drizzle } from 'drizzle-orm/neon-serverless';
2
2
  import { Pool } from '@neondatabase/serverless';
3
3
  import { highlight } from 'sql-highlight';
4
4
 
5
- export const pool = new Pool({ connectionString: process.env.EC_PG_CONN_URL });
5
+ export const pool = new Pool({ connectionString: process.env.EDGE_PG_CONN_URL });
6
6
  const db = drizzle(pool, {
7
7
  logger: {
8
8
  logQuery: (query, params) => {
index.js CHANGED
@@ -8,6 +8,7 @@ import { createBrowserHistory } from "history";
8
8
  import { createRouter } from "radix3";
9
9
  import routemap from '/routemap.json' assert {type: 'json'};
10
10
 
11
+ export const isProd = () => process.env.NODE_ENV === "production";
11
12
  export const isClient = () => typeof window !== 'undefined';
12
13
  export const domain = () => isClient() ? window.origin : "http://0.0.0.0:3000";
13
14
 
@@ -114,7 +115,7 @@ export const useMutation = (fn) => {
114
115
  }
115
116
 
116
117
  export const RouterContext = createContext(undefined);
117
- export const RouterProvider = ({ router, history, rpcContext, helmetContext }) => {
118
+ export const RouterProvider = ({ router, history, rpcContext, helmetContext, App }) => {
118
119
  const [_, startTransition] = useTransition();
119
120
  const [pathname, setPathname] = useState(history.location.pathname);
120
121
  const page = router.lookup(pathname) || router.lookup("/_404");
@@ -140,12 +141,14 @@ export const RouterProvider = ({ router, history, rpcContext, helmetContext }) =
140
141
  fallback: _jsx("p", {}, "Oops something went wrong"),
141
142
  children: _jsx(Suspense, {
142
143
  fallback: _jsx("p", {}, "Loading..."),
144
+ children: _jsx(App, {
143
- children: _jsx(page, {}),
145
+ children: _jsx(page, {}),
146
+ })
144
147
  }),
145
148
  }),
146
149
  }),
147
150
  }),
148
- });
151
+ })
149
152
  }
150
153
 
151
154
  export const useRouter = () => {
@@ -191,7 +194,10 @@ export const NavLink = ({ children, className, activeClassName, ...props }) => {
191
194
  })
192
195
  }
193
196
 
194
- export const hydrateApp = async () => {
197
+ export const hydrateApp = async (App) => {
198
+ if (!isProd()) {
199
+ console.log("hydrating with", window._EDGE_DATA_);
200
+ }
195
201
  const module = await import("react-dom/client");
196
202
  const history = createBrowserHistory();
197
203
  const router = createRouter({
@@ -205,7 +211,8 @@ export const hydrateApp = async () => {
205
211
  module.default.hydrateRoot(root, _jsx(RouterProvider, {
206
212
  history,
207
213
  router,
208
- rpcContext: window.__EC_RPC_DATA__ || {},
214
+ rpcContext: window._EDGE_DATA_ || {},
209
215
  helmetContext: {},
216
+ App,
210
217
  }));
211
218
  }
pnpm-lock.yaml CHANGED
@@ -141,6 +141,9 @@ importers:
141
141
  postgres:
142
142
  specifier: 3.3.4
143
143
  version: 3.3.4
144
+ wrangler:
145
+ specifier: 3.0.1
146
+ version: 3.0.1
144
147
 
145
148
  packages:
146
149
 
readme.md CHANGED
@@ -45,7 +45,6 @@ which are compatible with these API's. Here is a list of some of them,
45
45
  3. Add tests for runtime
46
46
  4. Add E2E tests for example
47
47
  5. Maybe move to vite for HMR goodness
48
- 6. Fix todos service not loading
49
48
 
50
49
  ### Todo[Cloudflare]
51
50
  1. Fix 404/500 pages not routing on server
renderPage.js CHANGED
@@ -101,7 +101,7 @@ const render = async (children, {
101
101
  }
102
102
  }
103
103
 
104
- const renderPage = async (Page, req) => {
104
+ const renderPage = async (Page, App, req) => {
105
105
  const url = new URL(req.url);
106
106
  const history = createMemoryHistory({
107
107
  initialEntries: [url.pathname + url.search],
@@ -137,6 +137,7 @@ const renderPage = async (Page, req) => {
137
137
  router,
138
138
  rpcContext,
139
139
  helmetContext,
140
+ App,
140
141
  }),
141
142
  ]
142
143
  })
@@ -147,9 +148,11 @@ const renderPage = async (Page, req) => {
147
148
  Object.keys(helmetContext.helmet)
148
149
  .map((k) => helmetContext.helmet[k].toString())
149
150
  .join(''),
150
- injectBeforeBodyClose: () => ''
151
+ injectOnEnd: () => {
152
+ return ''
151
- + `<script>window.__EC_RPC_DATA__ = ${JSON.stringify(rpcContext)};</script>`
153
+ + `<script>window._EDGE_DATA_ = ${JSON.stringify(rpcContext)};</script>`
152
- + `<script type="module" src="/js${jsScript}.js?hydrate=true"></script>`
154
+ + `<script type="module" src="/js${jsScript}.js?hydrate=true" defer></script>`
155
+ }
153
156
  });
154
157
  return new Response(stream, {
155
158
  headers: { 'Content-Type': 'text/html' },