~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


f90ac9a4 Peter John

2 years ago
fix hydrate and add prod build
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
- const version = (await import(path.join(import.meta.dir, "package.json"))).default.version;
31
+ const version = (await import(path.join(import.meta.dir, "package.json"))).default.version;
37
- console.log(`parotta v${version}`)
32
+ console.log(`parotta v${version}`)
38
- console.log(`running with cwd=${path.basename(process.cwd())} node_env=${process.env.NODE_ENV}`);
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
- const buildDir = path.join(process.cwd(), "build");
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 (options, src, dest, plg) => {
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: [src],
64
+ entryPoints,
125
- outfile: dest,
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': `"${process.env.NODE_ENV}"`,
76
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || "development"),
138
- 'process.env.PG_CONN_URL': "123",
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${dest}.js`;
134
+ const outfile = `build/functions${r.out}`;
182
- await bundleJs({}, r, outfile, {
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
- for (const r of routes) {
169
+ entryPoints: routes.map((r) => ({
216
- const buildStart = Date.now();
170
+ in: r.in,
217
- const dest = r.replace(process.cwd(), "").replace("/pages", "").replace("/page.jsx", "") || "/index";
171
+ out: "." + r.out.replace(".js", ""),
172
+ })),
218
- const outfile = `build/static/js${dest}.js`;
173
+ outdir: "build/static/js",
219
- await bundleJs({}, r, outfile, {
174
+ splitting: true,
175
+ entryNames: '[dir]/[name]',
176
+ chunkNames: 'chunks/[name]-[hash]'
177
+ }, {
220
- name: "parotta-page-js-plugin",
178
+ name: "parotta-page-js-plugin",
221
- setup(build) {
179
+ setup(build) {
222
- build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
180
+ build.onLoad({ filter: /\\*.page.jsx/, namespace: undefined }, (args) => {
223
- const data = fs.readFileSync(args.path);
181
+ const data = fs.readFileSync(args.path);
224
- const newSrc = `
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
- return {
191
+ return {
234
- contents: newSrc,
192
+ contents: newSrc,
235
- loader: "jsx",
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(buildStart, outfile);
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 React, { useEffect } from 'react';
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 { hydrateRoot } = await import("react-dom/client");
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,