edge-city

#react#js#ssr

git clone https://git.pyrossh.dev/edge-city

edge-city is a next level meta-framework for react that runs only on edge runtimes


773fdda — Peter John 2023-05-11T23:14:07+05:30
remove packages

Generated files not shown: bun.lockb, example/bun.lockb, package-lock.json, packages/example/bun.lockb, packages/parotta/bun.lockb

Files changed (46) hide show
  1. .gitignore +1 -2
  2. __render.js +0 -208
  3. {packages/parotta → bin}/cli.js +89 -92
  4. packages/create-parotta/index.js → bin/create-app.js +0 -0
  5. {packages/example → example}/.gitignore +0 -0
  6. {packages/example → example}/.vscode/extensions.json +0 -0
  7. {packages/example → example}/.vscode/settings.json +0 -0
  8. {packages/example → example}/Dockerfile +0 -0
  9. {packages/example → example}/components/Counter/Counter.jsx +0 -0
  10. {packages/example → example}/components/Timer/Timer.jsx +0 -0
  11. {packages/example → example}/components/Todo/Todo.css +0 -0
  12. {packages/example → example}/components/Todo/Todo.jsx +0 -0
  13. {packages/example → example}/containers/TodoList/TodoList.jsx +0 -0
  14. {packages/example → example}/db/index.js +1 -1
  15. {packages/example → example}/db/migrations/0000_empty_shatterstar.sql +0 -0
  16. {packages/example → example}/db/migrations/meta/0000_snapshot.json +0 -0
  17. {packages/example → example}/db/migrations/meta/_journal.json +0 -0
  18. {packages/example → example}/drizzle.config.json +0 -0
  19. {packages/example → example}/jsconfig.json +0 -0
  20. example/main.js +3 -0
  21. {packages/example → example}/package.json +5 -1
  22. {packages/example → example}/pages/404/page.css +0 -0
  23. {packages/example → example}/pages/404/page.jsx +0 -0
  24. {packages/example → example}/pages/about/page.css +0 -0
  25. {packages/example → example}/pages/about/page.jsx +0 -0
  26. {packages/example → example}/pages/layout.css +0 -0
  27. {packages/example → example}/pages/layout.jsx +0 -0
  28. {packages/example → example}/pages/page.css +0 -0
  29. {packages/example → example}/pages/page.jsx +0 -0
  30. {packages/example → example}/pages/page.spec.js +0 -0
  31. {packages/example → example}/pages/todos/page.css +0 -0
  32. {packages/example → example}/pages/todos/page.jsx +0 -0
  33. {packages/example → example}/playwright.config.js +0 -0
  34. {packages/example → example}/readme.md +6 -2
  35. {packages/example → example}/services/auth.service.js +0 -0
  36. {packages/example → example}/services/todos.service.js +0 -0
  37. {packages/example → example}/static/favicon.ico +0 -0
  38. {packages/example → example}/static/logo192.png +0 -0
  39. {packages/example → example}/static/logo512.png +0 -0
  40. {packages/example → example}/static/manifest.json +0 -0
  41. {packages/example → example}/static/robots.txt +0 -0
  42. package.json +26 -4
  43. packages/create-parotta/package.json +0 -16
  44. packages/parotta/package.json +0 -20
  45. readme.md +5 -4
  46. packages/parotta/runtime.js → runtime.js +6 -3
.gitignore CHANGED
@@ -1,2 +1 @@
1
- node_modules
1
+ node_modules
2
- .vercel
__render.js DELETED
@@ -1,208 +0,0 @@
1
- import React from 'react';
2
- import { renderToReadableStream } from 'react-dom/server';
3
- import { StreamProvider, SuspenseData } from './hooks';
4
-
5
- function wrapStreamEnd(streamEnd, didError) {
6
- return (
7
- streamEnd
8
- // Needed because of the `afterReactBugCatch()` hack above, otherwise `onBoundaryError` triggers after `streamEnd` resolved
9
- .then(() => new Promise((r) => setTimeout(r, 0)))
10
- .then(() => !didError)
11
- )
12
- }
13
-
14
- function createBuffer(streamOperations) {
15
- const buffer = []
16
- let state = 'UNSTARTED'
17
- let writePermission = null // Set to `null` because React fails to hydrate if something is injected before the first react write
18
-
19
- return { injectToStream, onBeforeWrite, onBeforeEnd }
20
-
21
- function injectToStream(chunk, options) {
22
- if (state === 'ENDED') {
23
- console.error(`Cannot inject following chunk after stream has ended: \`${chunk}\``)
24
- }
25
- buffer.push({ chunk, flush: options?.flush })
26
- flushBuffer()
27
- }
28
-
29
- function flushBuffer() {
30
- if (!writePermission) {
31
- return
32
- }
33
- if (buffer.length === 0) {
34
- return
35
- }
36
- if (state !== 'STREAMING') {
37
- console.error(state === 'UNSTARTED')
38
- return
39
- }
40
- let flushStream = false
41
- buffer.forEach((bufferEntry) => {
42
- // assert(streamOperations.operations)
43
- const { writeChunk } = streamOperations.operations
44
- writeChunk(bufferEntry.chunk)
45
- if (bufferEntry.flush) {
46
- flushStream = true
47
- }
48
- })
49
- buffer.length = 0
50
- // assert(streamOperations.operations)
51
- if (flushStream && streamOperations.operations.flush !== null) {
52
- streamOperations.operations.flush()
53
- }
54
- }
55
-
56
- function onBeforeWrite(chunk) {
57
- // state === 'UNSTARTED' && debug('>>> START')
58
- state = 'STREAMING'
59
- if (writePermission) {
60
- flushBuffer()
61
- }
62
- if (writePermission == true || writePermission === null) {
63
- writePermission = false
64
- // debug('writePermission =', writePermission)
65
- setTimeout(() => {
66
- // debug('>>> setTimeout()')
67
- writePermission = true
68
- // debug('writePermission =', writePermission)
69
- flushBuffer()
70
- })
71
- }
72
- }
73
-
74
- function onBeforeEnd() {
75
- writePermission = true
76
- // debug('writePermission =', writePermission)
77
- flushBuffer()
78
- // assert(buffer.length === 0)
79
- state = 'ENDED'
80
- // debug('>>> END')
81
- }
82
- }
83
-
84
-
85
- const createReadableWrapper = (readableFromReact) => {
86
- const streamOperations = {
87
- operations: null
88
- }
89
- let controllerOfUserStream;
90
- let onEnded;
91
- const streamEnd = new Promise((r) => {
92
- onEnded = () => r()
93
- })
94
- const readableForUser = new ReadableStream({
95
- start(controller) {
96
- controllerOfUserStream = controller
97
- onReady(onEnded)
98
- }
99
- })
100
- const { injectToStream, onBeforeWrite, onBeforeEnd } = createBuffer(streamOperations)
101
- return { readableForUser, streamEnd, injectToStream }
102
- async function onReady(onEnded) {
103
- streamOperations.operations = {
104
- writeChunk(chunk) {
105
- controllerOfUserStream.enqueue(encodeForWebStream(chunk))
106
- },
107
- flush: null
108
- }
109
-
110
- const reader = readableFromReact.getReader()
111
-
112
- while (true) {
113
- let result;
114
- try {
115
- result = await reader.read()
116
- } catch (err) {
117
- controllerOfUserStream.close()
118
- throw err
119
- }
120
- const { value, done } = result
121
- if (done) {
122
- break
123
- }
124
- onBeforeWrite(value)
125
- streamOperations.operations.writeChunk(value)
126
- }
127
-
128
- // Collect `injectToStream()` calls stuck in an async call
129
- setTimeout(() => {
130
- onBeforeEnd()
131
- controllerOfUserStream.close()
132
- onEnded()
133
- }, 0)
134
- }
135
- }
136
-
137
- let encoder;
138
- function encodeForWebStream(thing) {
139
- if (!encoder) {
140
- encoder = new TextEncoder()
141
- }
142
- if (typeof thing === 'string') {
143
- return encoder.encode(thing)
144
- }
145
- return thing
146
- }
147
-
148
- export const renderToWebStream = async (element, disable) => {
149
- let didError = false
150
- let firstErr = null
151
- let reactBug = null
152
- const onError = (err) => {
153
- didError = true
154
- firstErr = firstErr || err
155
- afterReactBugCatch(() => {
156
- // Is not a React internal error (i.e. a React bug)
157
- if (err !== reactBug) {
158
- options.onBoundaryError?.(err)
159
- }
160
- })
161
- }
162
- const readableOriginal = await renderToReadableStream(element, { onError })
163
- const { allReady } = readableOriginal
164
- let promiseResolved = false
165
- // Upon React internal errors (i.e. React bugs), React rejects `allReady`.
166
- // React doesn't reject `allReady` upon boundary errors.
167
- allReady.catch((err) => {
168
- // debug('react bug')
169
- didError = true
170
- firstErr = firstErr || err
171
- reactBug = err
172
- // Only log if it wasn't used as rejection for `await renderToStream()`
173
- if (reactBug !== firstErr || promiseResolved) {
174
- console.error(reactBug)
175
- }
176
- })
177
- if (didError) throw firstErr
178
- if (disable) await allReady
179
- if (didError) throw firstErr
180
- const { readableForUser, streamEnd, injectToStream } = createReadableWrapper(readableOriginal)
181
- promiseResolved = true
182
- return {
183
- readable: readableForUser,
184
- streamEnd: wrapStreamEnd(streamEnd, didError),
185
- injectToStream
186
- }
187
- }
188
-
189
- export const renderToStream = async (element, options = {}) => {
190
- const buffer = []
191
- let injectToStream = (chunk) => buffer.push(chunk);
192
- const disable = options.disable //?? resolveSeoStrategy(options).disableStream)
193
- const result = await renderToWebStream(React.createElement(
194
- StreamProvider,
195
- {
196
- value: {
197
- injectToStream: (chunk) => {
198
- injectToStream(chunk)
199
- }
200
- }
201
- },
202
- React.createElement(SuspenseData, null, element)
203
- ), disable, options);
204
- injectToStream = result.injectToStream
205
- buffer.forEach((chunk) => injectToStream(chunk));
206
- buffer.length = 0;
207
- return result
208
- }
{packages/parotta → bin}/cli.js RENAMED
@@ -1,30 +1,26 @@
1
1
  #!/usr/bin/env bun --hot
2
2
 
3
- import React from 'react';
4
3
  import path from 'path';
5
4
  import fs from 'fs';
6
5
  import walkdir from 'walkdir';
7
6
  import postcss from "postcss"
8
7
  import autoprefixer from "autoprefixer";
9
8
  import postcssCustomMedia from "postcss-custom-media";
10
- // import postcssNormalize from 'postcss-normalize';
11
9
  import postcssNesting from "postcss-nesting";
12
10
  import { createMemoryHistory } from "history";
13
11
  import { createRouter } from 'radix3';
14
12
  import mimeTypes from "mime-types";
13
+ import React from "react";
14
+ import { renderToReadableStream } from "react-dom/server";
15
- import { HeadApp, BodyApp } from "./runtime";
15
+ import { HeadApp, BodyApp } from "../runtime";
16
- import { renderToReadableStream } from 'react-dom/server';
17
- // import { renderToStream } from './render';
18
16
 
19
- const version = (await import(path.join(import.meta.dir, "package.json"))).default.version;
17
+ const version = (await import(path.join(import.meta.dir, "../package.json"))).default.version;
20
18
  console.log(`parotta v${version}`)
21
19
  console.log(`running with cwd=${path.basename(process.cwd())} node_env=${process.env.NODE_ENV}`);
22
- // console.log("deleting cache");
23
- // rmSync(path.join(process.cwd(), ".cache"), { force: true, recursive: true })
24
20
 
25
21
  const isProd = process.env.NODE_ENV === "production";
26
22
 
27
- const mapServerRoutes = () => {
23
+ const createServerRouter = async () => {
28
24
  const routes = {};
29
25
  const dirs = walkdir.sync(path.join(process.cwd(), "pages"))
30
26
  .map((s) => s.replace(process.cwd(), "")
@@ -56,9 +52,74 @@ const mapServerRoutes = () => {
56
52
  .forEach((route) => {
57
53
  routes[route] = { key: route, file: route }
58
54
  });
55
+
56
+ return createRouter({
57
+ strictTrailingSlash: true,
59
- return routes;
58
+ routes: routes,
59
+ });
60
60
  }
61
61
 
62
+ const createClientRouter = async () => {
63
+ const routes = await walkdir.sync(path.join(process.cwd(), "pages"))
64
+ .filter((p) => p.includes('page.jsx'))
65
+ .map((s) => s.replace(process.cwd(), ""))
66
+ .map((s) => s.replace("/pages", ""))
67
+ .map((s) => s.replace("/page.jsx", ""))
68
+ .reduce(async (accp, r) => {
69
+ const acc = await accp;
70
+ const src = await import(`${process.cwd()}/pages${r}/page.jsx`);
71
+ const exists = fs.existsSync(`${process.cwd()}/pages${r}/layout.jsx`);
72
+ const lpath = exists ? `/pages${r}/layout.jsx` : `/pages/layout.jsx`;
73
+ const lsrc = await import(`${process.cwd()}${lpath}`);
74
+ acc[r === "" ? "/" : r] = {
75
+ Head: src.Head,
76
+ Body: src.Body,
77
+ Layout: lsrc.default,
78
+ LayoutPath: lpath,
79
+ }
80
+ return acc
81
+ }, Promise.resolve({}));
82
+ // console.log(clientRoutes);
83
+ const hydrationScript = `
84
+ import React from "react";
85
+ import { hydrateRoot } from "react-dom/client";
86
+ import { createBrowserHistory } from "history";
87
+ import nProgress from "nprogress";
88
+ import { createRouter } from "radix3";
89
+ import { HeadApp, BodyApp } from "parotta/runtime";
90
+
91
+
92
+ const history = createBrowserHistory();
93
+ const radixRouter = createRouter({
94
+ strictTrailingSlash: true,
95
+ routes: {
96
+ ${Object.keys(routes).map((r) => `"${r}": {
97
+ Head: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Head }))),
98
+ Body: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Body }))),
99
+ Layout: React.lazy(() => import("${routes[r].LayoutPath}")),
100
+ LayoutPath: "${routes[r].LayoutPath}",
101
+ }`).join(',\n ')}
102
+ },
103
+ });
104
+
105
+ hydrateRoot(document.head, React.createElement(HeadApp, {
106
+ history,
107
+ radixRouter,
108
+ }))
109
+
110
+ hydrateRoot(document.body, React.createElement(BodyApp, {
111
+ nProgress,
112
+ history,
113
+ radixRouter,
114
+ }));`
115
+ const router = createRouter({
116
+ strictTrailingSlash: true,
117
+ routes: routes,
118
+ });
119
+ router.hydrationScript = hydrationScript;
120
+ return router;
121
+ };
122
+
62
123
  const mapDeps = (dir) => {
63
124
  return walkdir.sync(path.join(process.cwd(), dir))
64
125
  .map((s) => s.replace(process.cwd(), ""))
@@ -74,45 +135,21 @@ const mapDeps = (dir) => {
74
135
  }, {});
75
136
  }
76
137
 
77
- const mapPages = () => walkdir.sync(path.join(process.cwd(), "pages"))
78
- .filter((p) => p.includes('page.jsx'))
79
- .map((s) => s.replace(process.cwd(), ""))
80
- .map((s) => s.replace("/pages", ""))
81
- .map((s) => s.replace("/page.jsx", ""));
82
-
83
- const serverSideRoutes = mapServerRoutes();
84
- const clientSideRoutes = mapPages();
85
-
86
- const serverRouter = createRouter({
138
+ const serverRouter = await createServerRouter();
87
- strictTrailingSlash: true,
88
- routes: serverSideRoutes,
89
- });
90
-
91
- const clientRoutes = await clientSideRoutes.reduce(async (accp, r) => {
139
+ const clientRouter = await createClientRouter();
92
- const acc = await accp;
140
+ const transpiler = new Bun.Transpiler({
93
- const src = await import(`${process.cwd()}/pages${r}/page.jsx`);
94
- const exists = fs.existsSync(`${process.cwd()}/pages${r}/layout.jsx`);
95
- const lpath = exists ? `/pages${r}/layout.jsx` : `/pages/layout.jsx`;
96
- const lsrc = await import(`${process.cwd()}${lpath}`);
97
- acc[r === "" ? "/" : r] = {
98
- Head: src.Head,
99
- Body: src.Body,
100
- Layout: lsrc.default,
141
+ loader: "jsx",
101
- LayoutPath: lpath,
142
+ autoImportJSX: true,
102
- }
103
- return acc
104
- }, Promise.resolve({}));
143
+ jsxOptimizationInline: true,
105
-
106
- // console.log(clientRoutes);
107
144
 
145
+ // TODO
108
- const clientRouter = createRouter({
146
+ // autoImportJSX: false,
109
- strictTrailingSlash: true,
147
+ // jsxOptimizationInline: false,
110
- routes: clientRoutes,
111
148
  });
112
149
 
113
150
  const renderApi = async (key, filePath, req) => {
114
151
  const url = new URL(req.url);
115
- const params = await req.json();
152
+ const params = req.method === "POST" ? await req.json() : Object.fromEntries(url.searchParams);
116
153
  const funcName = url.pathname.replace(`${key}/`, "");
117
154
  const js = await import(path.join(process.cwd(), filePath));
118
155
  const result = await js[funcName](params);
@@ -122,8 +159,6 @@ const renderApi = async (key, filePath, req) => {
122
159
  });
123
160
  }
124
161
 
125
- console.log(clientRoutes)
126
-
127
162
  const renderPage = async (url) => {
128
163
  const packageJson = await import(path.join(process.cwd(), "package.json"));
129
164
  const config = packageJson.default.parotta || { hydrate: true };
@@ -143,7 +178,7 @@ const renderPage = async (url) => {
143
178
  "react-dom/client": `https://esm.sh/[email protected]${devTag}/client`,
144
179
  "nprogress": "https://esm.sh/[email protected]",
145
180
  // "parotta/runtime": `https://esm.sh/parotta@${version}/runtime.js`,
146
- "parotta/runtime": `/parotta/runtime.js`,
181
+ "parotta/runtime": `../parotta/runtime.js`,
147
182
  ...nodeDeps,
148
183
  ...components,
149
184
  ...containers,
@@ -161,40 +196,12 @@ const renderPage = async (url) => {
161
196
  />
162
197
  </head>
163
198
  <body>
164
- <BodyApp history={history} radixRouter={clientRouter} />
199
+ <BodyApp nProgress={{ start: () => { }, done: () => { } }} history={history} radixRouter={clientRouter} />
165
200
  {config.hydrate &&
166
201
  <>
167
202
  <script type="module" defer={true} dangerouslySetInnerHTML={{
168
- __html: `
169
- import React from "react";
170
- import { hydrateRoot } from "react-dom/client";
171
- import { createBrowserHistory } from "history";
172
- import { createRouter } from "radix3";
173
- import { HeadApp, BodyApp } from "parotta/runtime";
174
-
175
-
176
- const history = createBrowserHistory();
177
- const radixRouter = createRouter({
203
+ __html: clientRouter.hydrationScript
178
- strictTrailingSlash: true,
179
- routes: {
180
- ${Object.keys(clientRoutes).map((r) => `"${r}": {
181
- Head: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Head }))),
182
- Body: React.lazy(() => import("/pages${r}/page.jsx").then((js) => ({ default: js.Body }))),
183
- Layout: React.lazy(() => import("${clientRoutes[r].LayoutPath}")),
184
- LayoutPath: "${clientRoutes[r].LayoutPath}",
185
- }`).join(',\n ')}
186
- },
187
- });
188
-
189
- hydrateRoot(document.head, React.createElement(HeadApp, {
190
- history,
191
- radixRouter,
192
- }))
193
-
194
- hydrateRoot(document.body, React.createElement(BodyApp, {
195
- history,
196
- radixRouter,
197
- }));`}}>
204
+ }}>
198
205
  </script>
199
206
  </>
200
207
  }
@@ -228,19 +235,9 @@ const renderCss = async (src) => {
228
235
  }
229
236
  }
230
237
 
231
- const transpiler = new Bun.Transpiler({
232
- loader: "jsx",
233
- autoImportJSX: true,
234
- jsxOptimizationInline: true,
235
-
236
- // TODO
237
- // autoImportJSX: false,
238
- // jsxOptimizationInline: false,
239
- });
240
-
241
- const renderJs = async (src) => {
238
+ const renderJs = async (srcFile) => {
242
239
  try {
243
- const jsText = await Bun.file(src).text();
240
+ const jsText = await Bun.file(srcFile).text();
244
241
  const result = await transpiler.transform(jsText);
245
242
  // inject code which calls the api for that function
246
243
  const lines = result.split("\n");
@@ -298,10 +295,10 @@ export default {
298
295
  port: 3000,
299
296
  async fetch(req) {
300
297
  const url = new URL(req.url);
301
- console.log("GET", url.pathname);
298
+ console.log(req.method, url.pathname);
302
299
  // maybe this is needed
303
300
  if (url.pathname.startsWith("/parotta/")) {
304
- return renderJs(path.join(import.meta.dir, url.pathname.replace("/parotta/", "")));
301
+ return renderJs(path.join(import.meta.dir, url.pathname.replace("/parotta/", "../")));
305
302
  }
306
303
  if (url.pathname.endsWith(".css")) {
307
304
  return renderCss(path.join(process.cwd(), url.pathname));
packages/create-parotta/index.js → bin/create-app.js RENAMED
File without changes
{packages/example → example}/.gitignore RENAMED
File without changes
{packages/example → example}/.vscode/extensions.json RENAMED
File without changes
{packages/example → example}/.vscode/settings.json RENAMED
File without changes
{packages/example → example}/Dockerfile RENAMED
File without changes
{packages/example → example}/components/Counter/Counter.jsx RENAMED
File without changes
{packages/example → example}/components/Timer/Timer.jsx RENAMED
File without changes
{packages/example → example}/components/Todo/Todo.css RENAMED
File without changes
{packages/example → example}/components/Todo/Todo.jsx RENAMED
File without changes
{packages/example → example}/containers/TodoList/TodoList.jsx RENAMED
File without changes
{packages/example → example}/db/index.js RENAMED
@@ -4,7 +4,7 @@ import { Pool } from '@neondatabase/serverless';
4
4
  import { migrate } from 'drizzle-orm/neon-serverless/migrator';
5
5
  import { highlight } from 'sql-highlight';
6
6
 
7
- export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
7
+ export const pool = new Pool({ connectionString: process.env.PG_CONN_URL });
8
8
  const db = drizzle(pool, {
9
9
  logger: {
10
10
  logQuery: (query, params) => {
{packages/example → example}/db/migrations/0000_empty_shatterstar.sql RENAMED
File without changes
{packages/example → example}/db/migrations/meta/0000_snapshot.json RENAMED
File without changes
{packages/example → example}/db/migrations/meta/_journal.json RENAMED
File without changes
{packages/example → example}/drizzle.config.json RENAMED
File without changes
{packages/example → example}/jsconfig.json RENAMED
File without changes
example/main.js ADDED
@@ -0,0 +1,3 @@
1
+ import DevServer from "parotta/bin/cli";
2
+
3
+ export default DevServer;
{packages/example → example}/package.json RENAMED
@@ -14,6 +14,8 @@
14
14
  "drizzle-auth-adaptor-pg": "^0.0.3",
15
15
  "drizzle-orm": "0.25.4",
16
16
  "next-auth": "^4.22.1",
17
+ "normalize.css": "^8.0.1",
18
+ "parotta": "file:../",
17
19
  "react": "18.2.0",
18
20
  "react-dom": "^18.2.0",
19
21
  "sql-highlight": "^4.3.2"
@@ -27,7 +29,9 @@
27
29
  },
28
30
  "parotta": {
29
31
  "hydrate": true,
32
+ "css": [
30
- "css": "@blueprintjs/core/lib/css/blueprint.css"
33
+ "node_modules/normalize.css/normalize.css"
34
+ ]
31
35
  },
32
36
  "eslintConfig": {
33
37
  "root": true,
{packages/example → example}/pages/404/page.css RENAMED
File without changes
{packages/example → example}/pages/404/page.jsx RENAMED
File without changes
{packages/example → example}/pages/about/page.css RENAMED
File without changes
{packages/example → example}/pages/about/page.jsx RENAMED
File without changes
{packages/example → example}/pages/layout.css RENAMED
File without changes
{packages/example → example}/pages/layout.jsx RENAMED
File without changes
{packages/example → example}/pages/page.css RENAMED
File without changes
{packages/example → example}/pages/page.jsx RENAMED
File without changes
{packages/example → example}/pages/page.spec.js RENAMED
File without changes
{packages/example → example}/pages/todos/page.css RENAMED
File without changes
{packages/example → example}/pages/todos/page.jsx RENAMED
File without changes
{packages/example → example}/playwright.config.js RENAMED
File without changes
{packages/example → example}/readme.md RENAMED
@@ -1,4 +1,4 @@
1
- # parotta-example
1
+ # Sample Parotta Application
2
2
 
3
3
  ## Requirements
4
4
 
@@ -9,6 +9,10 @@
9
9
  1. `bun i`
10
10
  2. `bunx playright install`
11
11
 
12
+ ## Running
13
+
14
+ `bun run dev`
15
+
12
16
  ## Testing
13
17
 
14
- `bun run test`
18
+ `bun test`
{packages/example → example}/services/auth.service.js RENAMED
File without changes
{packages/example → example}/services/todos.service.js RENAMED
File without changes
{packages/example → example}/static/favicon.ico RENAMED
File without changes
{packages/example → example}/static/logo192.png RENAMED
File without changes
{packages/example → example}/static/logo512.png RENAMED
File without changes
{packages/example → example}/static/manifest.json RENAMED
File without changes
{packages/example → example}/static/robots.txt RENAMED
File without changes
package.json CHANGED
@@ -1,5 +1,27 @@
1
1
  {
2
+ "name": "parotta",
3
+ "version": "0.3.0",
4
+ "type": "module",
2
- "workspaces": [
5
+ "dependencies": {
6
+ "autoprefixer": "^10.4.14",
3
- "packages/*"
7
+ "chalk": "^5.2.0",
8
+ "commander": "10.0.0",
9
+ "extract-files": "^13.0.0",
10
+ "gunzip-file": "^0.1.1",
11
+ "history": "^5.3.0",
12
+ "mime-types": "2.1.35",
13
+ "nprogress": "^0.2.0",
14
+ "postcss": "^8.4.21",
15
+ "postcss-custom-media": "^9.1.2",
16
+ "postcss-nesting": "^11.2.1",
17
+ "radix3": "^1.0.0",
18
+ "react": "^18.2.0",
19
+ "react-dom": "^18.2.0",
20
+ "tar": "^6.1.13",
21
+ "unzip": "^0.1.11",
22
+ "walkdir": "0.4.1"
4
- ]
23
+ },
24
+ "bin": {
25
+ "parotta": "./bin/cli.js"
5
- }
26
+ }
27
+ }
packages/create-parotta/package.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "name": "create-parotta-app",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "dependencies": {
6
- "chalk": "^5.2.0",
7
- "commander": "10.0.0",
8
- "extract-files": "^13.0.0",
9
- "gunzip-file": "^0.1.1",
10
- "tar": "^6.1.13",
11
- "unzip": "^0.1.11"
12
- },
13
- "bin": {
14
- "parotta": "./index.js"
15
- }
16
- }
packages/parotta/package.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "name": "parotta",
3
- "version": "0.3.0",
4
- "type": "module",
5
- "dependencies": {
6
- "autoprefixer": "^10.4.14",
7
- "history": "^5.3.0",
8
- "mime-types": "2.1.35",
9
- "nprogress": "^0.2.0",
10
- "postcss": "^8.4.21",
11
- "postcss-custom-media": "^9.1.2",
12
- "postcss-nesting": "^11.2.1",
13
- "postcss-normalize": "^10.0.1",
14
- "radix3": "^1.0.0",
15
- "walkdir": "0.4.1"
16
- },
17
- "bin": {
18
- "parotta": "./cli.js"
19
- }
20
- }
readme.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # parotta
2
2
 
3
- parotta is a next level meta-framework for react that runs only on edge runtimes mainly bun.
3
+ parotta is a next level meta-framework for react that runs only on edge runtimes.
4
+ It uses bun as its bundler/transpiler and development mode as its quick and fast.
4
5
  It uses File System routing with streaming SSR + CSR as the method to render pages. Basically a MPA + SPA Transitional App.
5
6
  It is very opionated and has set of idiomatic ways of doing things.
7
+ It has inbuilt rpc mechanism to access server resources instead of a typical REST API.
6
8
 
7
9
  ### Todo
8
10
  1. Add build step
9
11
  2. Deploy to Node (using edge-runtime), Docker, Deno deploy, Vercel edge functions, Cloudflare workers, Bun edge (whenever it releases)
10
- 3. Hydrate fetch cache
12
+ 3. Hydrate rpc cache
11
- 4. Build a proper example (bunx create-parotta@latest)
12
- 5. Build a Website with Docs using parotta
13
+ 4. Build a Website with Docs using parotta
packages/parotta/runtime.js → runtime.js RENAMED
@@ -2,7 +2,6 @@ import React, {
2
2
  Fragment, Suspense, createElement, createContext,
3
3
  useContext, useState, useEffect, useMemo, useSyncExternalStore, useTransition
4
4
  } from "react";
5
- import nProgress from "nprogress";
6
5
 
7
6
  export const domain = () => typeof window !== 'undefined' ? window.origin : "http://0.0.0.0:3000";
8
7
  export const globalCache = new Map();
@@ -106,7 +105,7 @@ export const HeadApp = ({ history, radixRouter, importMap }) => {
106
105
  });
107
106
  }
108
107
 
109
- export const BodyApp = ({ history, radixRouter }) => {
108
+ export const BodyApp = ({ nProgress, history, radixRouter }) => {
110
109
  const [isPending, startTransition] = useTransition();
111
110
  const [match, setMatch] = useState(() => getMatch(radixRouter, history.location.pathname));
112
111
  useEffect(() => {
@@ -186,7 +185,11 @@ export const Link = (props) => {
186
185
  export const NavLink = ({ children, className, activeClassName, ...props }) => {
187
186
  const { pathname } = useRouter();
188
187
  const classNames = pathname === props.href ? [activeClassName, className] : [className];
189
- return <Link className={classNames} {...props} >{children}</Link>
188
+ return createElement(Link, {
189
+ children,
190
+ className: classNames,
191
+ ...props,
192
+ })
190
193
  }
191
194
 
192
195
  export class ErrorBoundary extends React.Component {