~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
f90ac9a4
—
Peter John 2 years ago
fix hydrate and add prod build
- packages/cli/index.js +65 -100
- packages/example/Dockerfile +0 -8
- packages/example/main.js +0 -6
- packages/example/package.json +3 -3
- packages/example/pages/page.jsx +1 -1
- packages/runtime/index.js +3 -11
packages/cli/index.js
CHANGED
|
@@ -28,35 +28,9 @@ if (cli.input.length != 2) {
|
|
|
28
28
|
cli.showHelp();
|
|
29
29
|
process.exit(0);
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!globalThis.firstRun) {
|
|
35
|
-
globalThis.firstRun = true
|
|
36
|
-
|
|
31
|
+
const version = (await import(path.join(import.meta.dir, "package.json"))).default.version;
|
|
37
|
-
|
|
32
|
+
console.log(`parotta v${version}`)
|
|
38
|
-
|
|
33
|
+
console.log(`running with NODE_ENV=${process.env.NODE_ENV}`);
|
|
39
|
-
} else {
|
|
40
|
-
console.log(`server reloading`);
|
|
41
|
-
}
|
|
42
|
-
const isProd = process.env.NODE_ENV === "production";
|
|
43
|
-
|
|
44
|
-
const mapDeps = (dir) => {
|
|
45
|
-
return walkdir.sync(path.join(process.cwd(), dir))
|
|
46
|
-
.map((s) => s.replace(process.cwd(), ""))
|
|
47
|
-
.filter((s) => s.includes(".jsx") || s.includes(".js"))
|
|
48
|
-
.reduce((acc, s) => {
|
|
49
|
-
if (s.includes(".jsx")) {
|
|
50
|
-
acc['@' + s.replace(".jsx", "")] = s
|
|
51
|
-
}
|
|
52
|
-
if (s.includes(".js")) {
|
|
53
|
-
acc['@' + s.replace(".js", "")] = s
|
|
54
|
-
}
|
|
55
|
-
return acc;
|
|
56
|
-
}, {});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const staticDir = path.join(process.cwd(), "build", "static");
|
|
60
34
|
|
|
61
35
|
const ensureDir = (d) => {
|
|
62
36
|
if (!fs.existsSync(d)) {
|
|
@@ -64,8 +38,11 @@ const ensureDir = (d) => {
|
|
|
64
38
|
}
|
|
65
39
|
}
|
|
66
40
|
|
|
41
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
42
|
+
const buildDir = path.join(process.cwd(), "build");
|
|
43
|
+
const staticDir = path.join(buildDir, "static");
|
|
67
44
|
const createDirs = () => {
|
|
68
|
-
|
|
45
|
+
fs.rmSync(buildDir, { recursive: true });
|
|
69
46
|
ensureDir(buildDir);
|
|
70
47
|
ensureDir(staticDir);
|
|
71
48
|
}
|
|
@@ -78,69 +55,30 @@ const recordSize = (buildStart, dest) => {
|
|
|
78
55
|
);
|
|
79
56
|
}
|
|
80
57
|
|
|
81
|
-
const buildImportMap = async () => {
|
|
82
|
-
const packageJson = await import(path.join(process.cwd(), "package.json"));
|
|
83
|
-
const config = packageJson.default.parotta || { hydrate: true };
|
|
84
|
-
const devTag = !isProd ? "-dev-" : "";
|
|
85
|
-
const devQueryParam = !isProd ? `?dev` : "";
|
|
86
|
-
const nodeDeps = Object.keys(packageJson.default.dependencies).reduce((acc, dep) => {
|
|
87
|
-
acc[dep] = `https://esm.sh/${dep}@${packageJson.default.dependencies[dep]}`;
|
|
88
|
-
return acc;
|
|
89
|
-
}, {})
|
|
90
|
-
const components = mapDeps("components");
|
|
91
|
-
const importmap = {
|
|
92
|
-
"imports": {
|
|
93
|
-
"radix3": `https://esm.sh/radix3@1.0.1`,
|
|
94
|
-
"history": "https://esm.sh/history@5.3.0",
|
|
95
|
-
"react": `https://esm.sh/react@18.2.0${devQueryParam}`,
|
|
96
|
-
[`react/jsx${devTag}runtime`]: `https://esm.sh/react@18.2.0${devQueryParam}/jsx${devTag}runtime`,
|
|
97
|
-
"react-dom/client": `https://esm.sh/react-dom@18.2.0${devQueryParam}/client`,
|
|
98
|
-
"nprogress": "https://esm.sh/nprogress@0.2.0",
|
|
99
|
-
...nodeDeps,
|
|
100
|
-
...components,
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const outfile = path.join(staticDir, "importmap.json");
|
|
104
|
-
fs.writeFileSync(outfile, JSON.stringify(importmap, null, 2));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const buildRouteMap = (routes) => {
|
|
108
|
-
const routemap = routes.reduce((acc, p) => {
|
|
109
|
-
const r = p.replace(process.cwd(), "");
|
|
110
|
-
const key = r.replace("/pages", "").replace("/page.jsx", "")
|
|
111
|
-
acc[key === "" ? "/" : key] = "/js" + (r.replace("/pages", "").replace("/page.jsx", "") || "/index") + ".js";
|
|
112
|
-
return acc
|
|
113
|
-
}, {});
|
|
114
|
-
const outfile = path.join(staticDir, "routemap.json");
|
|
115
|
-
fs.writeFileSync(outfile, JSON.stringify(routemap, null, 2));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
58
|
let generatedCss = ``;
|
|
119
59
|
const cssCache = [];
|
|
120
|
-
const bundleJs = async (
|
|
60
|
+
const bundleJs = async ({ entryPoints, outfile, ...options }, plg) => {
|
|
121
61
|
const result = await esbuild.build({
|
|
122
62
|
bundle: true,
|
|
123
63
|
target: ['es2022'],
|
|
124
|
-
entryPoints
|
|
64
|
+
entryPoints,
|
|
125
|
-
outfile
|
|
65
|
+
outfile,
|
|
126
66
|
format: 'esm',
|
|
127
|
-
keepNames: true,
|
|
128
67
|
external: ["node:*"],
|
|
129
68
|
color: true,
|
|
69
|
+
keepNames: !isProd,
|
|
70
|
+
minify: isProd,
|
|
130
71
|
treeShaking: true,
|
|
131
|
-
// loader: { '.json': 'copy' },
|
|
132
|
-
// metafile: true,
|
|
133
72
|
jsxDev: !isProd,
|
|
134
73
|
jsx: 'automatic',
|
|
135
74
|
...options,
|
|
136
75
|
define: {
|
|
137
|
-
'process.env.NODE_ENV':
|
|
76
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || "development"),
|
|
138
|
-
'process.env.PG_CONN_URL':
|
|
77
|
+
'process.env.PG_CONN_URL': JSON.stringify(process.env.PG_CONN_URL),
|
|
139
78
|
},
|
|
140
79
|
plugins: [
|
|
141
80
|
resolve({
|
|
142
81
|
"/routemap.json": `${staticDir}/routemap.json`,
|
|
143
|
-
"/importmap.json": `${staticDir}/importmap.json`
|
|
144
82
|
}),
|
|
145
83
|
plg,
|
|
146
84
|
]
|
|
@@ -171,15 +109,30 @@ const bundleJs = async (options, src, dest, plg) => {
|
|
|
171
109
|
// }
|
|
172
110
|
// }
|
|
173
111
|
|
|
112
|
+
const buildRouteMap = (routes) => {
|
|
113
|
+
const buildStart = new Date();
|
|
114
|
+
const routemap = routes.reduce((acc, r) => {
|
|
115
|
+
const key = r.out.replace("index", "").replace(".js", "");
|
|
116
|
+
acc[key === "" ? "/" : key] = "/js" + r.out;
|
|
117
|
+
return acc
|
|
118
|
+
}, {});
|
|
119
|
+
const outfile = path.join(staticDir, "routemap.json");
|
|
120
|
+
fs.writeFileSync(outfile, JSON.stringify(routemap, null, 2));
|
|
121
|
+
recordSize(buildStart, outfile);
|
|
122
|
+
}
|
|
123
|
+
|
|
174
124
|
const bundlePages = async () => {
|
|
175
125
|
const routes = walkdir.sync(path.join(process.cwd(), "pages"))
|
|
176
|
-
.filter((p) => p.includes("page.jsx"))
|
|
126
|
+
.filter((p) => p.includes("page.jsx"))
|
|
127
|
+
.map((r) => ({
|
|
128
|
+
in: r,
|
|
129
|
+
out: (r.replace(process.cwd(), "").replace("/pages", "").replace("/page.jsx", "") || "/index") + ".js",
|
|
130
|
+
}));
|
|
177
131
|
buildRouteMap(routes);
|
|
178
132
|
for (const r of routes) {
|
|
179
133
|
const buildStart = Date.now();
|
|
180
|
-
const dest = r.replace(process.cwd(), "").replace("/pages", "").replace("/page.jsx", "") || "/index";
|
|
181
|
-
const outfile = `build/functions${
|
|
134
|
+
const outfile = `build/functions${r.out}`;
|
|
182
|
-
await bundleJs({
|
|
135
|
+
await bundleJs({ entryPoints: [r.in], outfile }, {
|
|
183
136
|
name: "parotta-page-plugin",
|
|
184
137
|
setup(build) {
|
|
185
138
|
build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
|
|
@@ -212,32 +165,45 @@ const bundlePages = async () => {
|
|
|
212
165
|
});
|
|
213
166
|
recordSize(buildStart, outfile);
|
|
214
167
|
}
|
|
168
|
+
await bundleJs({
|
|
215
|
-
|
|
169
|
+
entryPoints: routes.map((r) => ({
|
|
216
|
-
|
|
170
|
+
in: r.in,
|
|
217
|
-
|
|
171
|
+
out: "." + r.out.replace(".js", ""),
|
|
172
|
+
})),
|
|
218
|
-
|
|
173
|
+
outdir: "build/static/js",
|
|
219
|
-
|
|
174
|
+
splitting: true,
|
|
175
|
+
entryNames: '[dir]/[name]',
|
|
176
|
+
chunkNames: 'chunks/[name]-[hash]'
|
|
177
|
+
}, {
|
|
220
|
-
|
|
178
|
+
name: "parotta-page-js-plugin",
|
|
221
|
-
|
|
179
|
+
setup(build) {
|
|
222
|
-
|
|
180
|
+
build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
|
|
223
|
-
|
|
181
|
+
const data = fs.readFileSync(args.path);
|
|
224
|
-
|
|
182
|
+
const newSrc = `
|
|
225
183
|
import { hydrateApp } from "parotta-runtime";
|
|
226
184
|
${data.toString()}
|
|
227
|
-
|
|
185
|
+
|
|
228
186
|
const searchParams = new URL(import.meta.url).searchParams;
|
|
229
187
|
if (searchParams.get("hydrate") === "true") {
|
|
230
188
|
hydrateApp(Page)
|
|
231
189
|
}
|
|
232
190
|
`
|
|
233
|
-
|
|
191
|
+
return {
|
|
234
|
-
|
|
192
|
+
contents: newSrc,
|
|
235
|
-
|
|
193
|
+
loader: "jsx",
|
|
236
|
-
|
|
194
|
+
}
|
|
237
|
-
|
|
195
|
+
});
|
|
196
|
+
build.onLoad({ filter: /\\*.css/, namespace: undefined }, (args) => {
|
|
197
|
+
return {
|
|
198
|
+
contents: "",
|
|
199
|
+
loader: "file",
|
|
238
|
-
|
|
200
|
+
}
|
|
239
|
-
|
|
201
|
+
});
|
|
202
|
+
build.on
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
for (const r of routes) {
|
|
240
|
-
recordSize(
|
|
206
|
+
recordSize(Date.now(), `build/static/js${r.out}`);
|
|
241
207
|
}
|
|
242
208
|
}
|
|
243
209
|
|
|
@@ -306,7 +272,6 @@ const bundleCss = async () => {
|
|
|
306
272
|
|
|
307
273
|
const main = async () => {
|
|
308
274
|
createDirs();
|
|
309
|
-
buildImportMap();
|
|
310
275
|
await bundlePages();
|
|
311
276
|
// await bundleServices();
|
|
312
277
|
await bundleCss();
|
packages/example/Dockerfile
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
FROM oven/bun:0.5.8
|
|
2
|
-
|
|
3
|
-
ENV NODE_ENV production
|
|
4
|
-
|
|
5
|
-
WORKDIR /app
|
|
6
|
-
COPY . /app
|
|
7
|
-
|
|
8
|
-
CMD ["bun", "start"]
|
packages/example/main.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import server from "parotta/server.js";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
port: 3000,
|
|
5
|
-
fetch: server,
|
|
6
|
-
}
|
packages/example/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"name": "parotta-example",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"scripts": {
|
|
5
|
+
"dev": "parotta build cloudflare",
|
|
5
|
-
"dev": "wrangler pages dev static --local --live-reload",
|
|
6
|
+
"dev-server": "wrangler pages dev static --local --live-reload",
|
|
6
|
-
"build": "parotta build cloudflare",
|
|
7
|
+
"build": "NODE_ENV=production parotta build cloudflare",
|
|
7
|
-
"run": "docker run -p 3000:3000 example",
|
|
8
8
|
"test": "bun test",
|
|
9
9
|
"test-e2e": "playwright test"
|
|
10
10
|
},
|
packages/example/pages/page.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
import { useRouter } from "parotta-runtime";
|
|
3
3
|
import Layout from '@/components/Layout/Layout';
|
|
4
4
|
import Counter from "@/components/Counter/Counter";
|
packages/runtime/index.js
CHANGED
|
@@ -9,7 +9,6 @@ import { ErrorBoundary } from "react-error-boundary";
|
|
|
9
9
|
import { createMemoryHistory, createBrowserHistory } from "history";
|
|
10
10
|
import { createRouter } from "radix3";
|
|
11
11
|
import nProgress from "nprogress";
|
|
12
|
-
import importmap from '/importmap.json' assert {type: 'json'};
|
|
13
12
|
import routemap from '/routemap.json' assert {type: 'json'};
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -171,7 +170,6 @@ export const App = ({ nProgress, history, router, rpcCache, helmetContext, PageC
|
|
|
171
170
|
// }
|
|
172
171
|
});
|
|
173
172
|
}, []);
|
|
174
|
-
console.log("match", match)
|
|
175
173
|
useEffect(() => {
|
|
176
174
|
if (!isPending) {
|
|
177
175
|
nProgress.done();
|
|
@@ -262,12 +260,7 @@ export const renderPage = async (PageComponent, req) => {
|
|
|
262
260
|
rel: "stylesheet",
|
|
263
261
|
href: "/css/app.css"
|
|
264
262
|
}),
|
|
265
|
-
_jsx("script", {
|
|
266
|
-
type: "importmap",
|
|
267
|
-
dangerouslySetInnerHTML: {
|
|
268
|
-
__html: JSON.stringify(importmap),
|
|
269
|
-
|
|
263
|
+
]
|
|
270
|
-
})]
|
|
271
264
|
}), _jsx("body", {
|
|
272
265
|
children: _jsxs("div", {
|
|
273
266
|
id: "root",
|
|
@@ -300,8 +293,7 @@ export const renderPage = async (PageComponent, req) => {
|
|
|
300
293
|
}
|
|
301
294
|
|
|
302
295
|
export const hydrateApp = async (Page) => {
|
|
303
|
-
console.log("hydrating");
|
|
304
|
-
const
|
|
296
|
+
const module = await import("react-dom/client");
|
|
305
297
|
const history = createBrowserHistory();
|
|
306
298
|
const router = createRouter({
|
|
307
299
|
strictTrailingSlash: true,
|
|
@@ -311,7 +303,7 @@ export const hydrateApp = async (Page) => {
|
|
|
311
303
|
}, {}),
|
|
312
304
|
});
|
|
313
305
|
const root = document.getElementById("root");
|
|
314
|
-
hydrateRoot(root, React.createElement(App, {
|
|
306
|
+
module.default.hydrateRoot(root, React.createElement(App, {
|
|
315
307
|
nProgress,
|
|
316
308
|
history,
|
|
317
309
|
router,
|