~repos /edge-city
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 +6 -0
- packages/cli/index.js +28 -15
- packages/example/pages/about/page.jsx +6 -2
- packages/example/pages/page.jsx +6 -2
- packages/runtime/index.js +89 -71
- packages/runtime/package.json +1 -0
- pnpm-lock.yaml +3 -0
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
|
|
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
|
-
|
|
89
|
+
"radix3": `https://esm.sh/radix3@1.0.1`,
|
|
91
|
-
|
|
90
|
+
"history": "https://esm.sh/history@5.3.0",
|
|
92
|
-
|
|
91
|
+
"react": `https://esm.sh/react@18.2.0${devQueryParam}`,
|
|
93
|
-
|
|
92
|
+
[`react/jsx${devTag}runtime`]: `https://esm.sh/react@18.2.0${devQueryParam}/jsx${devTag}runtime`,
|
|
94
|
-
|
|
93
|
+
"react-dom/client": `https://esm.sh/react-dom@18.2.0${devQueryParam}/client`,
|
|
95
|
-
|
|
94
|
+
"nprogress": "https://esm.sh/nprogress@0.2.0",
|
|
96
|
-
|
|
95
|
+
...nodeDeps,
|
|
97
|
-
|
|
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:
|
|
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: [
|
|
144
|
+
plugins: [
|
|
145
|
+
resolve({
|
|
135
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
178
|
+
return _jsx(HelmetProvider, {
|
|
169
179
|
context: helmetContext,
|
|
170
|
-
children:
|
|
180
|
+
children: _jsx(RpcContext.Provider, {
|
|
171
181
|
value: rpcCache,
|
|
172
|
-
children:
|
|
182
|
+
children: _jsx(RouterContext.Provider, {
|
|
173
183
|
value: {
|
|
174
184
|
history: history,
|
|
175
185
|
params: match.params || {},
|
|
176
186
|
},
|
|
177
|
-
children:
|
|
187
|
+
children: _jsx(ErrorBoundary, {
|
|
178
188
|
onError: (err) => console.log(err),
|
|
179
|
-
fallback:
|
|
189
|
+
fallback: _jsx("p", {}, "Oops something went wrong"),
|
|
180
|
-
children:
|
|
190
|
+
children: _jsx(Suspense, {
|
|
181
|
-
fallback:
|
|
191
|
+
fallback: _jsx("p", {}, "Loading..."),
|
|
182
|
-
children:
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
260
|
+
const stream = await renderToReadableStream(
|
|
261
|
+
_jsxs("html", {
|
|
262
|
+
lang: "en",
|
|
263
|
+
children: [_jsxs("head", {
|
|
264
|
+
children: [
|
|
250
|
-
|
|
265
|
+
// _jsx("link", {
|
|
251
|
-
|
|
266
|
+
// rel: "stylesheet",
|
|
252
|
-
|
|
267
|
+
// href: "https://unpkg.com/nprogress@0.2.0/nprogress.css"
|
|
268
|
+
// }),
|
|
253
|
-
|
|
269
|
+
// _jsx("link", {
|
|
254
|
-
|
|
270
|
+
// id: "pageCss",
|
|
255
|
-
|
|
271
|
+
// rel: "stylesheet",
|
|
256
|
-
|
|
272
|
+
// href: `/pages${url.pathname}/page.css`
|
|
257
|
-
|
|
273
|
+
// }),
|
|
258
|
-
|
|
274
|
+
_jsx("script", {
|
|
259
|
-
|
|
275
|
+
type: "importmap",
|
|
276
|
+
dangerouslySetInnerHTML: {
|
|
277
|
+
__html: JSON.stringify(importmap),
|
|
278
|
+
}
|
|
279
|
+
})]
|
|
280
|
+
}), _jsxs("body", {
|
|
281
|
+
children: [_jsx(App, {
|
|
282
|
+
nProgress,
|
|
283
|
+
history,
|
|
260
|
-
|
|
284
|
+
radixRouter: clientRouter,
|
|
285
|
+
rpcCache: {},
|
|
286
|
+
helmetContext,
|
|
287
|
+
PageComponent,
|
|
261
|
-
|
|
288
|
+
}), false && _jsx(_Fragment, {
|
|
262
|
-
|
|
289
|
+
children: _jsx("script", {
|
|
263
|
-
|
|
290
|
+
type: "module",
|
|
264
|
-
|
|
291
|
+
defer: true,
|
|
265
|
-
|
|
292
|
+
dangerouslySetInnerHTML: {
|
|
266
|
-
// <script type="module" defer={true} dangerouslySetInnerHTML={{
|
|
267
|
-
|
|
293
|
+
__html: `
|
|
268
|
-
|
|
294
|
+
import React from "react";
|
|
269
|
-
|
|
295
|
+
import { hydrateRoot } from "react-dom/client";
|
|
270
|
-
|
|
296
|
+
import { createBrowserHistory } from "history";
|
|
271
|
-
|
|
297
|
+
import nProgress from "nprogress";
|
|
272
|
-
|
|
298
|
+
import { createRouter } from "radix3";
|
|
273
|
-
|
|
299
|
+
import { App } from "parotta/runtime";
|
|
274
|
-
|
|
300
|
+
import routemap from '/static/routemap.json' assert {type: 'json'};
|
|
275
|
-
|
|
301
|
+
// import sheet from './styles.css' assert { type: 'css' };
|
|
276
302
|
|
|
277
|
-
|
|
303
|
+
const history = createBrowserHistory();
|
|
278
|
-
|
|
304
|
+
const radixRouter = createRouter({
|
|
279
|
-
|
|
305
|
+
strictTrailingSlash: true,
|
|
280
|
-
|
|
306
|
+
routes: {
|
|
281
|
-
|
|
307
|
+
${Object.keys(routemap).map(r => `"${r}": React.lazy(() => import("/pages${r}/page.jsx"))`).join(',\n ')}
|
|
282
|
-
|
|
308
|
+
},
|
|
283
|
-
|
|
309
|
+
});
|
|
284
310
|
|
|
285
|
-
|
|
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
|
-
|
|
312
|
+
nProgress,
|
|
301
|
-
|
|
313
|
+
history,
|
|
302
|
-
|
|
314
|
+
radixRouter,
|
|
303
|
-
|
|
315
|
+
rpcCache: {},
|
|
304
|
-
|
|
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)
|