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.
- package/.github/workflows/benchmarks.yml +44 -0
- package/README.md +112 -54
- 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/bun.lock +11 -0
- package/dist/cli.js +530 -0
- package/dist/index.js +2025 -0
- package/dist/vite-plugin.js +774 -0
- package/package.json +46 -39
- package/src/cli.js +11 -55
- package/src/compiler/vite-plugin.js +48 -1
- package/src/runtime/context.js +22 -11
- package/src/runtime/dom.js +23 -9
- package/src/runtime/lifecycle.js +1 -1
- package/src/runtime/router.js +83 -14
- package/vite.config.build.js +36 -0
- package/index.html +0 -19
|
@@ -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="
|
|
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,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
|
-
|
|
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
|
-
<
|
|
314
|
-
<
|
|
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
|
-
|
|
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,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
|
+
});
|