~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


e95ca431 pyrossh

2 years ago
seperate concerns
Files changed (4) hide show
  1. cli.js +2 -19
  2. index.js +2 -105
  3. renderApi.js +19 -0
  4. renderPage.js +106 -0
cli.js CHANGED
@@ -88,7 +88,7 @@ const bundlePages = async () => {
88
88
  build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
89
89
  const data = fs.readFileSync(args.path);
90
90
  const newSrc = `
91
- import { renderPage } from "edge-city";
91
+ import renderPage from "edge-city/renderPage";
92
92
 
93
93
  ${data.toString()}
94
94
 
@@ -172,26 +172,9 @@ const bundleServices = async () => {
172
172
  build.onLoad({ filter: /\\*.service.js/, namespace: undefined }, async (args) => {
173
173
  const src = fs.readFileSync(args.path);
174
174
  const newSrc = `
175
+ import renderApi from "edge-city/renderApi";
175
176
  ${src.toString()}
176
177
 
177
- export const renderApi = async (fn, req) => {
178
- const url = new URL(req.url);
179
- const params = req.method === "POST" ? await req.json() : Object.fromEntries(url.searchParams);
180
- try {
181
- const result = await fn(params);
182
- return new Response(JSON.stringify(result), {
183
- headers: { 'Content-Type': 'application/json' },
184
- status: 200,
185
- });
186
- } catch (err) {
187
- const message = err.format ? err.format() : err;
188
- return new Response(JSON.stringify(message), {
189
- headers: { 'Content-Type': 'application/json' },
190
- status: 400,
191
- });
192
- }
193
- }
194
-
195
178
  export function onRequest(context) {
196
179
  return renderApi(${p}, context.request);
197
180
  }
index.js CHANGED
@@ -4,14 +4,10 @@ import React, {
4
4
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
5
5
  import { HelmetProvider } from 'react-helmet-async';
6
6
  import { ErrorBoundary } from "react-error-boundary";
7
- import { createMemoryHistory, createBrowserHistory } from "history";
7
+ import { createBrowserHistory } from "history";
8
8
  import { createRouter } from "radix3";
9
9
  import routemap from '/routemap.json' assert {type: 'json'};
10
10
 
11
- /**
12
- * CSR related functions
13
- */
14
-
15
11
  export const isClient = () => typeof window !== 'undefined';
16
12
  export const domain = () => isClient() ? window.origin : "http://0.0.0.0:3000";
17
13
 
@@ -212,103 +208,4 @@ export const hydrateApp = async () => {
212
208
  rpcContext: {},
213
209
  helmetContext: {},
214
210
  }));
215
- }
211
+ }
216
-
217
- // TODO: move this to cli.js or another file
218
- export const renderPage = async (Page, req) => {
219
- const { renderToReadableStream } = await import("react-dom/server");
220
- const { default: isbot } = await import("isbot");
221
- const url = new URL(req.url);
222
- const history = createMemoryHistory({
223
- initialEntries: [url.pathname + url.search],
224
- });
225
- const router = createRouter({
226
- strictTrailingSlash: true,
227
- routes: Object.keys(routemap).reduce((acc, r) => {
228
- acc[r] = React.lazy(() => Promise.resolve({ default: Page }));
229
- return acc;
230
- }, {}),
231
- });
232
- const jsScript = url.pathname === "/" ? "/index" : url.pathname;
233
- const helmetContext = {};
234
- const rpcContext = {};
235
- if (isbot(req.headers.get('User-Agent')) || url.search.includes("ec_is_bot=true")) {
236
- const stream = await renderToReadableStream(_jsx("body", {
237
- children: _jsxs("div", {
238
- id: "root",
239
- children: [_jsx(App, {
240
- history,
241
- router,
242
- rpcCache: {},
243
- helmetContext,
244
- }), _jsx(_Fragment, {
245
- children: _jsx("script", {
246
- type: "module",
247
- defer: true,
248
- src: `/js${jsScript}.js?hydrate=true`,
249
- })
250
- })]
251
- })
252
- }))
253
- await stream.allReady;
254
- let isFirstChunk = true;
255
- // TODO: add rpcContext
256
- const transformStream = new TransformStream({
257
- transform(chunk, controller) {
258
- if (isFirstChunk) {
259
- isFirstChunk = false;
260
- controller.enqueue(new TextEncoder().encode(`
261
- <!DOCTYPE html>
262
- <html lang="en">
263
- <head>
264
- ${helmetContext.helmet.title.toString()}
265
- ${helmetContext.helmet.meta.toString()}
266
- <link rel="stylesheet" href="/css/app.css">
267
- </head>
268
- `));
269
- }
270
- controller.enqueue(chunk);
271
- },
272
- flush(controller) {
273
- controller.enqueue(new TextEncoder().encode(`</html>`));
274
- controller.terminate();
275
- },
276
- });
277
- return new Response(stream.pipeThrough(transformStream), {
278
- headers: { 'Content-Type': 'text/html' },
279
- status: 200,
280
- });
281
- }
282
- const stream = await renderToReadableStream(
283
- _jsxs("html", {
284
- lang: "en",
285
- children: [_jsxs("head", {
286
- children: [
287
- _jsx("link", {
288
- rel: "stylesheet",
289
- href: "/css/app.css"
290
- }),
291
- ]
292
- }), _jsx("body", {
293
- children: _jsxs("div", {
294
- id: "root",
295
- children: [_jsx(App, {
296
- history,
297
- router,
298
- rpcContext,
299
- helmetContext,
300
- }), _jsx(_Fragment, {
301
- children: _jsx("script", {
302
- type: "module",
303
- defer: true,
304
- src: `/js${jsScript}.js?hydrate=true`,
305
- })
306
- })]
307
- })
308
- })]
309
- }));
310
- return new Response(stream, {
311
- headers: { 'Content-Type': 'text/html' },
312
- status: 200,
313
- });
314
- }
renderApi.js ADDED
@@ -0,0 +1,19 @@
1
+ export const renderApi = async (fn, req) => {
2
+ const url = new URL(req.url);
3
+ const params = req.method === "POST" ? await req.json() : Object.fromEntries(url.searchParams);
4
+ try {
5
+ const result = await fn(params);
6
+ return new Response(JSON.stringify(result), {
7
+ headers: { 'Content-Type': 'application/json' },
8
+ status: 200,
9
+ });
10
+ } catch (err) {
11
+ const message = err.format ? err.format() : err;
12
+ return new Response(JSON.stringify(message), {
13
+ headers: { 'Content-Type': 'application/json' },
14
+ status: 400,
15
+ });
16
+ }
17
+ }
18
+
19
+ export default renderApi;
renderPage.js ADDED
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { createMemoryHistory } from "history";
4
+ import { createRouter } from "radix3";
5
+ import { renderToReadableStream } from "react-dom/server";
6
+ import isbot from "isbot";
7
+ import routemap from '/routemap.json' assert {type: 'json'};
8
+ import { App } from "./index";
9
+
10
+ const renderPage = async (Page, req) => {
11
+ const url = new URL(req.url);
12
+ const history = createMemoryHistory({
13
+ initialEntries: [url.pathname + url.search],
14
+ });
15
+ const router = createRouter({
16
+ strictTrailingSlash: true,
17
+ routes: Object.keys(routemap).reduce((acc, r) => {
18
+ acc[r] = React.lazy(() => Promise.resolve({ default: Page }));
19
+ return acc;
20
+ }, {}),
21
+ });
22
+ const jsScript = url.pathname === "/" ? "/index" : url.pathname;
23
+ const helmetContext = {};
24
+ const rpcContext = {};
25
+ if (isbot(req.headers.get('User-Agent')) || url.search.includes("ec_is_bot=true")) {
26
+ const stream = await renderToReadableStream(_jsx("body", {
27
+ children: _jsxs("div", {
28
+ id: "root",
29
+ children: [_jsx(App, {
30
+ history,
31
+ router,
32
+ rpcCache: {},
33
+ helmetContext,
34
+ }), _jsx(_Fragment, {
35
+ children: _jsx("script", {
36
+ type: "module",
37
+ defer: true,
38
+ src: `/js${jsScript}.js?hydrate=true`,
39
+ })
40
+ })]
41
+ })
42
+ }))
43
+ await stream.allReady;
44
+ let isFirstChunk = true;
45
+ // TODO: add rpcContext
46
+ const transformStream = new TransformStream({
47
+ transform(chunk, controller) {
48
+ if (isFirstChunk) {
49
+ isFirstChunk = false;
50
+ controller.enqueue(new TextEncoder().encode(`
51
+ <!DOCTYPE html>
52
+ <html lang="en">
53
+ <head>
54
+ ${helmetContext.helmet.title.toString()}
55
+ ${helmetContext.helmet.meta.toString()}
56
+ <link rel="stylesheet" href="/css/app.css">
57
+ </head>
58
+ `));
59
+ }
60
+ controller.enqueue(chunk);
61
+ },
62
+ flush(controller) {
63
+ controller.enqueue(new TextEncoder().encode(`</html>`));
64
+ controller.terminate();
65
+ },
66
+ });
67
+ return new Response(stream.pipeThrough(transformStream), {
68
+ headers: { 'Content-Type': 'text/html' },
69
+ status: 200,
70
+ });
71
+ }
72
+ const stream = await renderToReadableStream(
73
+ _jsxs("html", {
74
+ lang: "en",
75
+ children: [_jsxs("head", {
76
+ children: [
77
+ _jsx("link", {
78
+ rel: "stylesheet",
79
+ href: "/css/app.css"
80
+ }),
81
+ ]
82
+ }), _jsx("body", {
83
+ children: _jsxs("div", {
84
+ id: "root",
85
+ children: [_jsx(App, {
86
+ history,
87
+ router,
88
+ rpcContext,
89
+ helmetContext,
90
+ }), _jsx(_Fragment, {
91
+ children: _jsx("script", {
92
+ type: "module",
93
+ defer: true,
94
+ src: `/js${jsScript}.js?hydrate=true`,
95
+ })
96
+ })]
97
+ })
98
+ })]
99
+ }));
100
+ return new Response(stream, {
101
+ headers: { 'Content-Type': 'text/html' },
102
+ status: 200,
103
+ });
104
+ }
105
+
106
+ export default renderPage;