~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


3d1bca19 Peter John

2 years ago
get cf SSR working
package-lock.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "parotta",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }
packages/cli/index.js CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import meow from 'meow';
3
- import React from "react";
4
3
  import esbuild from 'esbuild';
5
4
  import resolve from 'esbuild-plugin-resolve';
6
- import { renderToReadableStream } from "react-dom/server";
7
- import fs, { mkdir } from "fs";
5
+ import fs from "fs";
8
6
  import path from 'path';
9
7
  import walkdir from 'walkdir';
10
8
  import postcss from "postcss"
@@ -87,14 +85,16 @@ const buildImportMap = async () => {
87
85
  }, {})
88
86
  const components = mapDeps("components");
89
87
  const importmap = {
88
+ "imports": {
90
- "radix3": `https://esm.sh/radix3@1.0.1`,
89
+ "radix3": `https://esm.sh/radix3@1.0.1`,
91
- "history": "https://esm.sh/history@5.3.0",
90
+ "history": "https://esm.sh/history@5.3.0",
92
- "react": `https://esm.sh/react@18.2.0${devQueryParam}`,
91
+ "react": `https://esm.sh/react@18.2.0${devQueryParam}`,
93
- [`react/jsx${devTag}runtime`]: `https://esm.sh/react@18.2.0${devQueryParam}/jsx${devTag}runtime`,
92
+ [`react/jsx${devTag}runtime`]: `https://esm.sh/react@18.2.0${devQueryParam}/jsx${devTag}runtime`,
94
- "react-dom/client": `https://esm.sh/react-dom@18.2.0${devQueryParam}/client`,
93
+ "react-dom/client": `https://esm.sh/react-dom@18.2.0${devQueryParam}/client`,
95
- "nprogress": "https://esm.sh/nprogress@0.2.0",
94
+ "nprogress": "https://esm.sh/nprogress@0.2.0",
96
- ...nodeDeps,
95
+ ...nodeDeps,
97
- ...components,
96
+ ...components,
97
+ }
98
98
  }
99
99
  const outfile = path.join(staticDir, "importmap.json");
100
100
  fs.writeFileSync(outfile, JSON.stringify(importmap, null, 2));
@@ -111,6 +111,16 @@ const buildRouteMap = () => {
111
111
  fs.writeFileSync(outfile, JSON.stringify(routemap, null, 2));
112
112
  }
113
113
 
114
+ // const EsbuildPluginResolve = (options) => ({
115
+ // name: 'esbuild-resolve',
116
+ // setup: (build) => {
117
+ // for (const moduleName of Object.keys(options)) {
118
+ // intercept(build, moduleName, options[moduleName]);
119
+ // }
120
+ // }
121
+ // });
122
+
123
+
114
124
  const buildServer = async (r) => {
115
125
  const buildStart = Date.now();
116
126
  const shortName = r.replace(process.cwd(), "").replace("/pages", "");
@@ -124,16 +134,19 @@ const buildServer = async (r) => {
124
134
  keepNames: true,
125
135
  external: ["node:*"],
126
136
  color: true,
127
- treeShaking: true,
137
+ treeShaking: false,
128
138
  // metafile: true,
129
139
  jsxDev: !isProd,
130
140
  jsx: 'automatic',
131
141
  define: {
132
142
  'process.env.NODE_ENV': `"${process.env.NODE_ENV}"`,
133
143
  },
134
- plugins: [resolve({
144
+ plugins: [
145
+ resolve({
135
- "/static/routemap.json": `${staticDir}/routemap.json`
146
+ "/static/routemap.json": `${staticDir}/routemap.json`,
147
+ "/static/importmap.json": `${staticDir}/importmap.json`
136
- })]
148
+ }),
149
+ ]
137
150
  });
138
151
  // console.log(await analyzeMetafile(result.metafile))
139
152
  const outLength = fs.statSync(outfile).size;
packages/example/pages/about/page.jsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Link, useRouter } from "parotta-runtime";
2
+ import { Link, useRouter, renderPage } from "parotta-runtime";
3
3
  import { Helmet } from 'react-helmet-async';
4
4
  import Layout from '@/components/Layout/Layout';
5
5
  import "./page.css";
@@ -25,4 +25,8 @@ export const Page = () => {
25
25
  )
26
26
  }
27
27
 
28
- export default Page;
28
+ export default Page;
29
+
30
+ export function onRequest(context) {
31
+ return renderPage(Page, context.request);
32
+ }
packages/example/pages/page.jsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect } from 'react';
2
- import { useRouter } from "parotta-runtime";
2
+ import { useRouter, renderPage } from "parotta-runtime";
3
3
  import Layout from '@/components/Layout/Layout';
4
4
  import Counter from "@/components/Counter/Counter";
5
5
  import { Helmet } from 'react-helmet-async';
@@ -26,4 +26,8 @@ const Page = () => {
26
26
  )
27
27
  }
28
28
 
29
- export default Page;
29
+ export default Page;
30
+
31
+ export function onRequest(context) {
32
+ return renderPage(Page, context.request);
33
+ }
packages/runtime/index.js CHANGED
@@ -1,11 +1,16 @@
1
1
  import React, {
2
- Suspense, createElement, createContext, useContext, useState, useEffect, useTransition, useCallback
2
+ Suspense, createContext, useContext, useState, useEffect, useTransition, useCallback
3
3
  } from "react";
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ import { jsxs as _jsxs } from "react/jsx-runtime";
6
+ import { Fragment as _Fragment } from "react/jsx-runtime";
7
+ import { renderToReadableStream } from "react-dom/server";
4
8
  import { HelmetProvider } from 'react-helmet-async';
5
9
  import { ErrorBoundary } from "react-error-boundary";
6
10
  import { createMemoryHistory } from "history";
7
11
  import { createRouter } from "radix3";
12
+ import importmap from '/static/importmap.json' assert {type: 'json'};
8
- import routes from '/static/routemap.json' assert {type: 'json'};
13
+ import routemap from '/static/routemap.json' assert {type: 'json'};
9
14
 
10
15
  /**
11
16
  * CSR related functions
@@ -127,9 +132,14 @@ const getMatch = (radixRouter, pathname) => {
127
132
 
128
133
  const getCssUrl = (pathname) => `/pages${pathname === "/" ? "" : pathname}/page.css`;
129
134
 
130
- export const App = ({ nProgress, history, radixRouter, rpcCache, helmetContext }) => {
135
+ export const App = ({ nProgress, history, radixRouter, rpcCache, helmetContext, PageComponent }) => {
131
136
  const [isPending, startTransition] = useTransition();
137
+ const [match, setMatch] = useState(() => {
138
+ if (PageComponent) {
139
+ return PageComponent;
140
+ }
132
- const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
141
+ return getMatch(radixRouter, history.location.pathname)
142
+ });
133
143
  useEffect(() => {
134
144
  return history.listen(({ location }) => {
135
145
  const href = getCssUrl(location.pathname);
@@ -165,21 +175,21 @@ export const App = ({ nProgress, history, radixRouter, rpcCache, helmetContext }
165
175
  nProgress.done();
166
176
  }
167
177
  }, [isPending]);
168
- return createElement(HelmetProvider, {
178
+ return _jsx(HelmetProvider, {
169
179
  context: helmetContext,
170
- children: createElement(RpcContext.Provider, {
180
+ children: _jsx(RpcContext.Provider, {
171
181
  value: rpcCache,
172
- children: createElement(RouterContext.Provider, {
182
+ children: _jsx(RouterContext.Provider, {
173
183
  value: {
174
184
  history: history,
175
185
  params: match.params || {},
176
186
  },
177
- children: createElement(ErrorBoundary, {
187
+ children: _jsx(ErrorBoundary, {
178
188
  onError: (err) => console.log(err),
179
- fallback: createElement("p", {}, "Oops something went wrong"),
189
+ fallback: _jsx("p", {}, "Oops something went wrong"),
180
- children: createElement(Suspense, {
190
+ children: _jsx(Suspense, {
181
- fallback: createElement("p", {}, "Loading..."),
191
+ fallback: _jsx("p", {}, "Loading..."),
182
- children: createElement(match, {}),
192
+ children: _jsx(match, {}),
183
193
  }),
184
194
  }),
185
195
  }),
@@ -203,7 +213,7 @@ export const useRouter = () => {
203
213
 
204
214
  export const Link = (props) => {
205
215
  const router = useRouter();
206
- return createElement("a", {
216
+ return _jsx("a", {
207
217
  ...props,
208
218
  onMouseOver: (e) => {
209
219
  // Simple prefetching for now will work only with cache headers
@@ -223,7 +233,7 @@ export const Link = (props) => {
223
233
  export const NavLink = ({ children, className, activeClassName, ...props }) => {
224
234
  const { pathname } = useRouter();
225
235
  const classNames = pathname === props.href ? [activeClassName, className] : [className];
226
- return createElement(Link, {
236
+ return _jsx(Link, {
227
237
  children,
228
238
  className: classNames,
229
239
  ...props,
@@ -233,10 +243,11 @@ export const NavLink = ({ children, className, activeClassName, ...props }) => {
233
243
  /**
234
244
  * SSR related functions
235
245
  */
236
- export const renderPage = async () => {
246
+ export const renderPage = async (PageComponent, req) => {
247
+ const url = new URL(req.url);
237
248
  const clientRouter = createRouter({
238
249
  strictTrailingSlash: true,
239
- routes: Object.keys(routes).reduce((acc, r) => {
250
+ routes: Object.keys(routemap).reduce((acc, r) => {
240
251
  acc[r] = React.lazy(() => import(`/pages${r}/page.jsx`));
241
252
  return acc;
242
253
  }, {}),
@@ -246,63 +257,70 @@ export const renderPage = async () => {
246
257
  });
247
258
  const helmetContext = {}
248
259
  const nProgress = { start: () => { }, done: () => { } }
249
- // const stream = await renderToReadableStream(
260
+ const stream = await renderToReadableStream(
261
+ _jsxs("html", {
262
+ lang: "en",
263
+ children: [_jsxs("head", {
264
+ children: [
250
- // <html lang="en">
265
+ // _jsx("link", {
251
- // <head>
266
+ // rel: "stylesheet",
252
- // <link rel="stylesheet" href="https://unpkg.com/nprogress@0.2.0/nprogress.css" />
267
+ // href: "https://unpkg.com/nprogress@0.2.0/nprogress.css"
268
+ // }),
253
- // <link id="pageCss" rel="stylesheet" href={`/pages${url.pathname}/page.css`} />
269
+ // _jsx("link", {
254
- // <script type="importmap" src="/static/importmap.json" />
270
+ // id: "pageCss",
255
- // </head>
271
+ // rel: "stylesheet",
256
- // <body>
272
+ // href: `/pages${url.pathname}/page.css`
257
- // <App
273
+ // }),
258
- // nProgress={nProgress}
274
+ _jsx("script", {
259
- // history={history}
275
+ type: "importmap",
276
+ dangerouslySetInnerHTML: {
277
+ __html: JSON.stringify(importmap),
278
+ }
279
+ })]
280
+ }), _jsxs("body", {
281
+ children: [_jsx(App, {
282
+ nProgress,
283
+ history,
260
- // radixRouter={clientRouter}
284
+ radixRouter: clientRouter,
285
+ rpcCache: {},
286
+ helmetContext,
287
+ PageComponent,
261
- // rpcCache={{}}
288
+ }), false && _jsx(_Fragment, {
262
- // helmetContext={helmetContext}
289
+ children: _jsx("script", {
263
- // />
290
+ type: "module",
264
- // {false &&
291
+ defer: true,
265
- // <>
292
+ dangerouslySetInnerHTML: {
266
- // <script type="module" defer={true} dangerouslySetInnerHTML={{
267
- // __html: `
293
+ __html: `
268
- // import React from "react";
294
+ import React from "react";
269
- // import { hydrateRoot } from "react-dom/client";
295
+ import { hydrateRoot } from "react-dom/client";
270
- // import { createBrowserHistory } from "history";
296
+ import { createBrowserHistory } from "history";
271
- // import nProgress from "nprogress";
297
+ import nProgress from "nprogress";
272
- // import { createRouter } from "radix3";
298
+ import { createRouter } from "radix3";
273
- // import { App } from "parotta/runtime";
299
+ import { App } from "parotta/runtime";
274
- // import routes from '/static/routemap.json' assert {type: 'json'};
300
+ import routemap from '/static/routemap.json' assert {type: 'json'};
275
- // // import sheet from './styles.css' assert { type: 'css' };
301
+ // import sheet from './styles.css' assert { type: 'css' };
276
302
 
277
- // const history = createBrowserHistory();
303
+ const history = createBrowserHistory();
278
- // const radixRouter = createRouter({
304
+ const radixRouter = createRouter({
279
- // strictTrailingSlash: true,
305
+ strictTrailingSlash: true,
280
- // routes: {
306
+ routes: {
281
- // ${Object.keys(routes).map((r) => `"${r}": React.lazy(() => import("/pages${r}/page.jsx"))`).join(',\n ')}
307
+ ${Object.keys(routemap).map(r => `"${r}": React.lazy(() => import("/pages${r}/page.jsx"))`).join(',\n ')}
282
- // },
308
+ },
283
- // });
309
+ });
284
310
 
285
- // hydrateRoot(document.body, React.createElement(App, {
311
+ hydrateRoot(document.body, React.createElement(App, {
286
- // nProgress,
287
- // history,
288
- // radixRouter,
289
- // rpcCache: {},
290
- // helmetContext: {},
291
- // }));`
292
- // }}>
293
- // </script>
294
- // </>
295
- // }
296
- // </body>
297
- // </html>
298
- // );
299
- const stream = await renderToReadableStream(React.createElement(App, {
300
- nProgress,
312
+ nProgress,
301
- history,
313
+ history,
302
- radixRouter: clientRouter,
314
+ radixRouter,
303
- rpcCache: {},
315
+ rpcCache: {},
304
- helmetContext: helmetContext,
316
+ helmetContext: {},
317
+ PageComponent: null,
318
+ }));`
319
+ }
320
+ })
321
+ })]
322
+ })]
305
- }));
323
+ }));
306
324
  // TODO:
307
325
  // if (bot || isCrawler) {
308
326
  // await stream.allReady
packages/runtime/package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "peerDependencies": {
10
10
  "react": "*",
11
+ "react-dom": "*",
11
12
  "react-error-boundary": "*",
12
13
  "react-helmet-async": "*"
13
14
  }
pnpm-lock.yaml CHANGED
@@ -113,6 +113,9 @@ importers:
113
113
  react:
114
114
  specifier: '*'
115
115
  version: 18.2.0
116
+ react-dom:
117
+ specifier: '*'
118
+ version: 18.2.0(react@18.2.0)
116
119
  react-error-boundary:
117
120
  specifier: '*'
118
121
  version: 4.0.4(react@18.2.0)