round-core 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ name: Framework Benchmarks
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ benchmark:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v1
19
+ with:
20
+ bun-version: latest
21
+
22
+ - name: Install Root Dependencies
23
+ run: bun install
24
+
25
+ - name: Build Round Core
26
+ run: bun run build:core
27
+
28
+ - name: Install Benchmark Dependencies
29
+ working-directory: ./benchmarks
30
+ run: bun install
31
+
32
+ - name: Run Build Benchmarks
33
+ working-directory: ./benchmarks
34
+ run: bun run bench:build
35
+
36
+ - name: Run Runtime Benchmarks
37
+ working-directory: ./benchmarks
38
+ run: bun run bench:runtime
39
+
40
+ - name: Upload Report
41
+ uses: actions/upload-artifact@v4
42
+ with:
43
+ name: benchmark-report
44
+ path: benchmarks/reports/build-bench.json
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  <h1 align="center">Round Framework</h1>
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/ZtaMDev/RoundJS/main/Round.png" alt="Dars Framework Logo" width="200" />
4
+ <img src="https://raw.githubusercontent.com/ZtaMDev/RoundJS/main/Round.png" alt="Round Framework Logo" width="200" />
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <img src="https://img.shields.io/npm/v/round-core?color=brightgreen" alt="PyPI Version" />
8
+ <img src="https://img.shields.io/npm/v/round-core?color=brightgreen" alt="NPM Version" />
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -26,6 +26,16 @@ Instead of a Virtual DOM diff, Round updates the UI by subscribing DOM updates d
26
26
  - **A JSX superset**: `.round` files support extra control-flow syntax that compiles to JavaScript.
27
27
  - **Minimal runtime**: DOM-first runtime (no VDOM diffing).
28
28
 
29
+ ## Architecture
30
+
31
+ Round is a **No-VDOM** framework.
32
+
33
+ 1. **Direct DOM Manipulation**: Components run once. They return real DOM nodes (via `document.createElement`).
34
+ 2. **Fine-Grained Reactivity**: Use of `signal`, `effect`, and `bindable` creates a dependency graph.
35
+ 3. **Surgical Updates**: When a signal changes, only the specific text node, attribute, or property subscribed to that signal is updated. The component function does not re-run.
36
+
37
+ This avoids the overhead of Virtual DOM diffing and reconciliation entirely.
38
+
29
39
  ## Concepts
30
40
 
31
41
  ### SPA
@@ -89,7 +99,7 @@ Example `src/app.round`:
89
99
 
90
100
  ```jsx
91
101
  import { Route } from 'round-core';
92
- import { Counter } from './counter';
102
+ import { Counter } from './counter.round';
93
103
 
94
104
  export default function App() {
95
105
  return (
@@ -115,23 +125,41 @@ Create a reactive signal.
115
125
  ```jsx
116
126
  import { signal } from 'round-core';
117
127
 
118
- export function Counter() {
128
+ export default function Counter() {
119
129
  const count = signal(0);
120
130
 
121
131
  return (
122
132
  <div>
123
133
  <h1>Count: {count()}</h1>
124
- <button onClick={() => count(count() + 1)}>Increment</button>
134
+ <div style={{ display: 'flex', gap: '8px' }}>
135
+ <button onClick={() => count(count() + 1)}>Increment</button>
136
+ <button onClick={() => count(count() - 1)}>Decrement</button>
137
+ </div>
125
138
  </div>
126
139
  );
127
140
  }
128
141
  ```
129
142
 
143
+ ### `derive(fn)`
144
+
145
+ Create a computed signal that updates automatically when its dependencies change.
146
+
147
+ ```javascript
148
+ import { signal, derive } from 'round-core';
149
+
150
+ const count = signal(1);
151
+ const double = derive(() => count() * 2);
152
+
153
+ console.log(double()); // 2
154
+ count(5);
155
+ console.log(double()); // 10
156
+ ```
157
+
130
158
  ### `effect(fn)`
131
159
 
132
160
  Run `fn` whenever the signals it reads change.
133
161
 
134
- ```js
162
+ ```javascript
135
163
  import { signal, effect } from 'round-core';
136
164
 
137
165
  const name = signal('Ada');
@@ -162,6 +190,40 @@ export function Example() {
162
190
  }
163
191
  ```
164
192
 
193
+ ### `createStore(initialState, actions)`
194
+
195
+ Create a shared global state store with actions and optional persistence.
196
+
197
+ ```javascript
198
+ import { createStore } from 'round-core';
199
+
200
+ // 1. Define Store
201
+ const store = createStore({
202
+ todos: [],
203
+ filter: 'all'
204
+ }, {
205
+ addTodo: (state, text) => ({
206
+ ...state,
207
+ todos: [...state.todos, { text, done: false }]
208
+ })
209
+ });
210
+
211
+ // 2. Use in Component
212
+ export function TodoList() {
213
+ const todos = store.use('todos'); // Returns a bindable signal
214
+
215
+ return (
216
+ <div>
217
+ {todos().map(todo => <div>{todo.text}</div>)}
218
+ <button onClick={() => store.addTodo('Buy Milk')}>Add</button>
219
+ </div>
220
+ );
221
+ }
222
+
223
+ // 3. Persistence (Optional)
224
+ store.persist('my-app-store');
225
+ ```
226
+
165
227
  ### `bindable.object(initialObject)` and deep binding
166
228
 
167
229
  Round supports object-shaped state with ergonomic deep bindings via proxies.
@@ -187,37 +249,6 @@ export function Profile() {
187
249
  }
188
250
  ```
189
251
 
190
- ### `$pick(path)`
191
-
192
- Create a view signal from a signal/bindable that holds an object.
193
-
194
- ```js
195
- import { bindable } from 'round-core';
196
-
197
- const user = bindable({ profile: { bio: 'Hello' } });
198
- const bio = user.$pick('profile.bio');
199
-
200
- console.log(bio());
201
- ```
202
-
203
- ### `.transform(fromInput, toOutput)`
204
-
205
- Transform a signal/bindable to adapt between DOM values (often strings) and your internal representation.
206
-
207
- ```jsx
208
- import { bindable } from 'round-core';
209
-
210
- export function AgeField() {
211
- const age = bindable('18')
212
- .transform(
213
- (str) => Math.max(0, parseInt(str, 10) || 0),
214
- (num) => String(num)
215
- );
216
-
217
- return <input type="number" bind:value={age} />;
218
- }
219
- ```
220
-
221
252
  ### `.validate(validator, options)`
222
253
 
223
254
  Attach validation to a signal/bindable.
@@ -277,6 +308,7 @@ Notes:
277
308
 
278
309
  - Conditions may be normal JS expressions.
279
310
  - For *simple paths* like `flags.showCounter` (identifier/member paths), Round will auto-unwrap signal-like values (call them) so the condition behaves as expected.
311
+ - Multiple elements inside a block are automatically wrapped in a Fragment.
280
312
 
281
313
  ### `for (... in ...)`
282
314
 
@@ -288,44 +320,70 @@ Notes:
288
320
 
289
321
  This compiles roughly to a `.map(...)` under the hood.
290
322
 
291
- ## Rendering model (no VDOM)
292
-
293
- Round renders to the DOM directly using a small runtime:
294
-
295
- - Elements are created with `document.createElement(...)`.
296
- - Dynamic children and reactive props are updated via `effect()` subscriptions.
297
- - Components are functions returning DOM nodes (or arrays of nodes).
298
-
299
- This is a **DOM-first, fine-grained reactive model**, rather than a Virtual DOM diffing renderer.
300
-
301
323
  ## Routing
302
324
 
303
- Round includes router primitives intended for SPA navigation.
325
+ Round includes router primitives intended for SPA navigation. All route paths must start with a forward slash `/`.
304
326
 
305
- Typical usage:
327
+ ### Basic Usage
306
328
 
307
329
  ```jsx
308
- import { Route } from 'round-core';
330
+ import { Route, Link } from 'round-core';
309
331
 
310
332
  export default function App() {
311
333
  return (
312
334
  <div>
313
- <Route route="/" title="Home">
314
- <div>Home</div>
335
+ <nav>
336
+ <Link href="/">Home</Link>
337
+ <Link href="/about">About</Link>
338
+ </nav>
339
+
340
+ <Route route="/" title="Home" exact>
341
+ <div>Welcome Home</div>
315
342
  </Route>
316
343
  <Route route="/about" title="About">
317
- <div>About</div>
344
+ <div>About Us Content</div>
318
345
  </Route>
319
346
  </div>
320
347
  );
321
348
  }
322
349
  ```
323
350
 
351
+ ### Nested Routing and Layouts
352
+
353
+ Routes can be nested to create hierarchical layouts. Child routes automatically inherit and combine paths with their parents.
354
+
355
+ - **Prefix Matching**: By default, routes use prefix matching (except for the root `/`). This allows a parent route to stay rendered as a "shell" or layout while its children are visited.
356
+ - **Exact Matching**: Use the `exact` prop to ensure a route only renders when the path matches precisely (default for root `/`).
357
+
358
+ ```jsx
359
+ <Route route="/dashboard" title="Dashboard">
360
+ <h1>Dashboard Shell</h1>
361
+
362
+ {/* This route matches /dashboard/profile */}
363
+ <Route route="/dashboard/profile">
364
+ <h2>User Profile</h2>
365
+ </Route>
366
+
367
+ {/* This route matches /dashboard/settings */}
368
+ <Route route="/dashboard/settings">
369
+ <h2>Settings</h2>
370
+ </Route>
371
+ </Route>
372
+ ```
373
+
324
374
  ## Suspense and lazy loading
325
375
 
326
376
  Round supports `Suspense` for promise-based rendering and `lazy()` for code splitting.
327
377
 
328
- (These APIs are evolving; expect improvements as Round’s compiler and runtime expand.)
378
+ ```jsx
379
+ import { Suspense, lazy } from 'round-core';
380
+ const LazyWidget = lazy(() => import('./Widget'));
381
+
382
+ <Suspense fallback={<div>Loading...</div>}>
383
+ <LazyWidget />
384
+ </Suspense>
385
+ ```
386
+
329
387
 
330
388
  ## Error handling
331
389
 
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <body>
5
+ <div id="root"></div>
6
+ <script type="module" src="./main.jsx"></script>
7
+ </body>
8
+
9
+ </html>
@@ -0,0 +1,25 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+
4
+ function App() {
5
+ const [count, setCount] = useState(0);
6
+ const [items] = useState(Array.from({ length: 1000 }, (_, i) => i));
7
+
8
+ useEffect(() => {
9
+ console.log('React App Mounted');
10
+ }, []);
11
+
12
+ return (
13
+ <div>
14
+ <h1>React Benchmark</h1>
15
+ <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
16
+ <ul>
17
+ {items.map(i => (
18
+ <li key={i}>Item {i}</li>
19
+ ))}
20
+ </ul>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ root: __dirname,
6
+ plugins: [react()],
7
+ build: {
8
+ outDir: '../../dist-bench/react',
9
+ emptyOutDir: true,
10
+ minify: true
11
+ }
12
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <body>
4
+ <div id="app"></div>
5
+ <script type="module">
6
+ import { render } from 'round-core';
7
+ import App from './main.jsx';
8
+ render(App, document.getElementById('app'));
9
+ </script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,22 @@
1
+ import { createElement, signal, onMount } from 'round-core';
2
+
3
+ export default function App() {
4
+ const count = signal(0);
5
+ const items = signal(Array.from({ length: 1000 }, (_, i) => i));
6
+
7
+ onMount(() => {
8
+ console.log('Round App Mounted');
9
+ });
10
+
11
+ return (
12
+ <div>
13
+ <h1>Round Benchmark</h1>
14
+ <button onClick={() => count(count() + 1)}>Count: {count}</button>
15
+ <ul>
16
+ {() => items().map(i => (
17
+ <li key={i}>Item {i}</li>
18
+ ))}
19
+ </ul>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vite';
2
+ import RoundPlugin from 'round-core/vite-plugin'; // Use package export
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ root: __dirname,
7
+ plugins: [
8
+ RoundPlugin()
9
+ ],
10
+ build: {
11
+ outDir: '../../dist-bench/round',
12
+ emptyOutDir: true,
13
+ minify: true
14
+ }
15
+ });