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.
- package/.github/workflows/benchmarks.yml +44 -0
- package/LICENSE +21 -0
- package/README.md +78 -48
- package/Round.png +0 -0
- package/benchmarks/apps/react/index.html +9 -0
- package/benchmarks/apps/react/main.jsx +25 -0
- package/benchmarks/apps/react/vite.config.js +12 -0
- package/benchmarks/apps/round/index.html +11 -0
- package/benchmarks/apps/round/main.jsx +22 -0
- package/benchmarks/apps/round/vite.config.js +15 -0
- package/benchmarks/bun.lock +497 -0
- package/benchmarks/dist-bench/react/assets/index-9KGqIPOU.js +8 -0
- package/benchmarks/dist-bench/react/index.html +10 -0
- package/benchmarks/dist-bench/round/assets/index-CBBIRhox.js +52 -0
- package/benchmarks/dist-bench/round/index.html +8 -0
- package/benchmarks/package.json +22 -0
- package/benchmarks/scripts/measure-build.js +64 -0
- package/benchmarks/tests/runtime.bench.js +51 -0
- package/benchmarks/vitest.config.js +8 -0
- package/dist/cli.js +582 -0
- package/dist/index.js +1975 -0
- package/dist/vite-plugin.js +736 -0
- package/logo.svg +9 -72
- package/package.json +14 -6
- package/src/cli.js +10 -3
- package/src/compiler/transformer.js +61 -2
- package/src/compiler/vite-plugin.js +5 -1
- package/src/runtime/context.js +17 -1
- package/src/runtime/dom.js +90 -42
- package/src/runtime/lifecycle.js +1 -1
- package/src/runtime/suspense.js +39 -17
- package/vite.config.build.js +47 -0
|
@@ -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="
|
|
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="
|
|
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
|
-
<
|
|
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
|
-
```
|
|
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
|
-
|
|
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,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,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
|
+
});
|