round-core 0.0.2 → 0.0.4

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@v3
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@v3
42
+ with:
43
+ name: benchmark-report
44
+ path: benchmarks/reports/build-bench.json
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ZtaMDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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,16 +320,6 @@ 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
325
  Round includes router primitives intended for SPA navigation.
@@ -325,7 +347,15 @@ export default function App() {
325
347
 
326
348
  Round supports `Suspense` for promise-based rendering and `lazy()` for code splitting.
327
349
 
328
- (These APIs are evolving; expect improvements as Round’s compiler and runtime expand.)
350
+ ```jsx
351
+ import { Suspense, lazy } from 'round-core';
352
+ const LazyWidget = lazy(() => import('./Widget'));
353
+
354
+ <Suspense fallback={<div>Loading...</div>}>
355
+ <LazyWidget />
356
+ </Suspense>
357
+ ```
358
+
329
359
 
330
360
  ## Error handling
331
361
 
package/Round.png CHANGED
Binary file
@@ -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
+ });