round-core 0.0.8 → 0.1.0
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/README.md +42 -42
- package/dist/index.js +214 -192
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<h1 align="center">Round
|
|
1
|
+
<h1 align="center">Round JS</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://raw.githubusercontent.com/ZtaMDev/RoundJS/main/Round.png" alt="Round Framework Logo" width="200" />
|
|
@@ -14,32 +14,44 @@
|
|
|
14
14
|
|
|
15
15
|
<div align="center">
|
|
16
16
|
|
|
17
|
-
Extension for VSCode
|
|
17
|
+
Extension for [VSCode](https://marketplace.visualstudio.com/items?itemName=ZtaMDev.round) and [OpenVSX](https://open-vsx.org/extension/ztamdev/round)
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Instead of a Virtual DOM diff, Round updates the UI by subscribing DOM updates directly to reactive primitives (**signals**) and **bindables**. This keeps rendering predictable, small, and fast for interactive apps.
|
|
24
|
+
|
|
25
|
+
The `round-core` package is the **foundation of RoundJS**.
|
|
26
|
+
|
|
27
|
+
You can think of `round-core` as:
|
|
28
|
+
- A **framework-level runtime**, not just a state library
|
|
29
|
+
- Comparable in scope to React + Router + Signals, but significantly smaller
|
|
30
|
+
- Suitable for fast SPAs and simple SSR setups without heavy infrastructure
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
To use Round JS today, install the core package:
|
|
21
35
|
|
|
22
36
|
```bash
|
|
23
37
|
npm install round-core
|
|
24
38
|
```
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## What Round is focused on
|
|
40
|
+
Or with Bun:
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- **A JSX superset**: `.round` files support extra control-flow syntax that compiles to JavaScript.
|
|
34
|
-
- **Minimal runtime**: DOM-first runtime (no VDOM diffing).
|
|
42
|
+
```bash
|
|
43
|
+
bun add round-core
|
|
44
|
+
```
|
|
35
45
|
|
|
36
|
-
##
|
|
46
|
+
## What Round is focused on
|
|
37
47
|
|
|
38
48
|
Round is a **No-VDOM** framework.
|
|
39
49
|
|
|
40
50
|
1. **Direct DOM Manipulation**: Components run once. They return real DOM nodes (via `document.createElement`).
|
|
41
51
|
2. **Fine-Grained Reactivity**: Use of `signal`, `effect`, and `bindable` creates a dependency graph.
|
|
42
|
-
3. **
|
|
52
|
+
3. **Ergonomic bindings**: built-in two-way bindings with `bind:*` directives.
|
|
53
|
+
4. **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.
|
|
54
|
+
5. **A JSX superset**: `.round` files support extra control-flow syntax that compiles to JavaScript.
|
|
43
55
|
|
|
44
56
|
This avoids the overhead of Virtual DOM diffing and reconciliation entirely.
|
|
45
57
|
|
|
@@ -56,35 +68,6 @@ A **signal** is a small reactive container.
|
|
|
56
68
|
- Reading a signal inside an `effect()` tracks a dependency.
|
|
57
69
|
- Writing to a signal triggers only the subscribed computations.
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
## Normal Installation
|
|
61
|
-
|
|
62
|
-
Simply install the `round-core` package
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
bun add round-core
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Or:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm install round-core
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Repo Installation
|
|
75
|
-
|
|
76
|
-
> Round is currently in active development. If you are using the repository directly, install dependencies and run the CLI locally.
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
bun install
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Or:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
npm install
|
|
86
|
-
```
|
|
87
|
-
|
|
88
71
|
## Quick start (create a new app)
|
|
89
72
|
|
|
90
73
|
Round includes a CLI with a project initializer.
|
|
@@ -100,7 +83,9 @@ This scaffolds a minimal Round app with `src/app.round` and an example `src/coun
|
|
|
100
83
|
|
|
101
84
|
## `.round` files
|
|
102
85
|
|
|
103
|
-
A `.round` file is a JSX-based component module (ESM) compiled by the Round toolchain.
|
|
86
|
+
A `.round` file is a JSX-based component module (ESM) compiled by the Round toolchain.
|
|
87
|
+
You can also use `.jsx` files, but you will not get the Round JSX superset features
|
|
88
|
+
such as extended control flow.
|
|
104
89
|
|
|
105
90
|
Example `src/app.round`:
|
|
106
91
|
|
|
@@ -424,6 +409,21 @@ The CLI is intended for day-to-day development:
|
|
|
424
409
|
|
|
425
410
|
Run `round -h` to see available commands.
|
|
426
411
|
|
|
412
|
+
## Signals Internals
|
|
413
|
+
|
|
414
|
+
RoundJS utilizes a high-performance reactivity engine designed for efficiency and minimal memory overhead:
|
|
415
|
+
|
|
416
|
+
- **Doubly-Linked List Dependency Tracking**: Instead of using heavy `Set` objects, RoundJS uses a linked-list of subscription nodes. This eliminates array spreads and object allocations during signal updates, providing constant-time performance for adding/removing dependencies.
|
|
417
|
+
- **Global Versioning (Clock)**: Every signal write increments a global version counter. Computed signals (`derive`) track the version of their dependencies and only recompute if they are "dirty" (out of date). This ensures true lazyness and avoids redundant calculations.
|
|
418
|
+
- **Automatic Batching**: Multiple signal updates within the same execution cycle are batched. Effects and DOM updates only trigger once at the end of the batch, preventing "glitches" and unnecessary re-renders.
|
|
419
|
+
|
|
420
|
+
## Performance
|
|
421
|
+
|
|
422
|
+
RoundJS sits in a powerful "middle ground" of performance:
|
|
423
|
+
|
|
424
|
+
- **vs React**: Round's fine-grained reactivity is **massively faster** (>30x in micro-benchmarks) than React's component-level reconciliation. DOM updates are surgical and don't require diffing a virtual tree.
|
|
425
|
+
- **vs Preact Signals**: While highly optimized, RoundJS signals are currently slightly slower than Preact Signals (~10x difference in raw signal-to-signal updates), as Preact utilizes more aggressive internal optimizations. However, for most real-world applications, RoundJS provides more than enough performance.
|
|
426
|
+
|
|
427
427
|
## Status
|
|
428
428
|
|
|
429
429
|
Round is under active development and the API is still stabilizing. The README is currently the primary documentation; a dedicated documentation site will be built later using Round itself.
|
package/dist/index.js
CHANGED
|
@@ -37,13 +37,11 @@ function onMount(fn) {
|
|
|
37
37
|
if (component) {
|
|
38
38
|
component.mountHooks.push(fn);
|
|
39
39
|
} else {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
}, 0);
|
|
40
|
+
try {
|
|
41
|
+
fn();
|
|
42
|
+
} catch (e) {
|
|
43
|
+
reportErrorSafe(e, { phase: "onMount" });
|
|
44
|
+
}
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
function onUnmount(fn) {
|
|
@@ -145,135 +143,180 @@ const Lifecycle = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
|
|
|
145
143
|
triggerUpdate,
|
|
146
144
|
unmountComponent
|
|
147
145
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
148
|
-
let context =
|
|
146
|
+
let context = null;
|
|
147
|
+
let batchCount = 0;
|
|
148
|
+
let pendingEffects = [];
|
|
149
|
+
let globalVersion = 0;
|
|
149
150
|
function isPromiseLike$2(v) {
|
|
150
151
|
return v && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
151
152
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
running.dependencies.add(subscriptions);
|
|
153
|
+
function isSignalLike(v) {
|
|
154
|
+
return typeof v === "function" && typeof v.peek === "function" && "value" in v;
|
|
155
155
|
}
|
|
156
156
|
function untrack(fn) {
|
|
157
|
-
context
|
|
157
|
+
const prev = context;
|
|
158
|
+
context = null;
|
|
158
159
|
try {
|
|
159
160
|
return typeof fn === "function" ? fn() : void 0;
|
|
160
161
|
} finally {
|
|
161
|
-
context
|
|
162
|
+
context = prev;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function batch(fn) {
|
|
166
|
+
batchCount++;
|
|
167
|
+
try {
|
|
168
|
+
return fn();
|
|
169
|
+
} finally {
|
|
170
|
+
if (--batchCount === 0) {
|
|
171
|
+
const effects = pendingEffects;
|
|
172
|
+
pendingEffects = [];
|
|
173
|
+
for (let i = 0; i < effects.length; i++) {
|
|
174
|
+
effects[i].queued = false;
|
|
175
|
+
effects[i].run();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function subscribe(sub, dep) {
|
|
181
|
+
let link = sub.deps;
|
|
182
|
+
while (link) {
|
|
183
|
+
if (link.dep === dep) return;
|
|
184
|
+
link = link.nextDep;
|
|
185
|
+
}
|
|
186
|
+
link = {
|
|
187
|
+
sub,
|
|
188
|
+
dep,
|
|
189
|
+
nextSub: dep.subs,
|
|
190
|
+
prevSub: null,
|
|
191
|
+
nextDep: sub.deps,
|
|
192
|
+
prevDep: null
|
|
193
|
+
};
|
|
194
|
+
if (dep.subs) dep.subs.prevSub = link;
|
|
195
|
+
dep.subs = link;
|
|
196
|
+
if (sub.deps) sub.deps.prevDep = link;
|
|
197
|
+
sub.deps = link;
|
|
198
|
+
}
|
|
199
|
+
function cleanup(sub) {
|
|
200
|
+
let link = sub.deps;
|
|
201
|
+
while (link) {
|
|
202
|
+
const { dep, prevSub, nextSub } = link;
|
|
203
|
+
if (prevSub) prevSub.nextSub = nextSub;
|
|
204
|
+
else dep.subs = nextSub;
|
|
205
|
+
if (nextSub) nextSub.prevSub = prevSub;
|
|
206
|
+
link = link.nextDep;
|
|
207
|
+
}
|
|
208
|
+
sub.deps = null;
|
|
209
|
+
}
|
|
210
|
+
function notify(dep) {
|
|
211
|
+
let link = dep.subs;
|
|
212
|
+
while (link) {
|
|
213
|
+
const sub = link.sub;
|
|
214
|
+
if (sub.isComputed) {
|
|
215
|
+
sub.version = -1;
|
|
216
|
+
notify(sub);
|
|
217
|
+
} else {
|
|
218
|
+
if (batchCount > 0) {
|
|
219
|
+
if (!sub.queued) {
|
|
220
|
+
sub.queued = true;
|
|
221
|
+
pendingEffects.push(sub);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
sub.run();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
link = link.nextSub;
|
|
162
228
|
}
|
|
163
229
|
}
|
|
164
230
|
function effect(arg1, arg2, arg3) {
|
|
165
|
-
let callback;
|
|
166
|
-
let explicitDeps = null;
|
|
167
|
-
let options = { onLoad: true };
|
|
231
|
+
let callback, explicitDeps = null, options = { onLoad: true };
|
|
168
232
|
let owner = getCurrentComponent();
|
|
169
233
|
if (typeof arg1 === "function") {
|
|
170
234
|
callback = arg1;
|
|
171
|
-
if (arg2 && typeof arg2 === "object") {
|
|
172
|
-
options = { ...options, ...arg2 };
|
|
173
|
-
}
|
|
235
|
+
if (arg2 && typeof arg2 === "object") options = { ...options, ...arg2 };
|
|
174
236
|
} else {
|
|
175
237
|
explicitDeps = arg1;
|
|
176
238
|
callback = arg2;
|
|
177
|
-
if (arg3 && typeof arg3 === "object") {
|
|
178
|
-
options = { ...options, ...arg3 };
|
|
179
|
-
}
|
|
239
|
+
if (arg3 && typeof arg3 === "object") options = { ...options, ...arg3 };
|
|
180
240
|
}
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
cleanup(execute);
|
|
192
|
-
context.push(execute);
|
|
193
|
-
try {
|
|
194
|
-
if (explicitDeps) {
|
|
195
|
-
if (Array.isArray(explicitDeps)) {
|
|
196
|
-
explicitDeps.forEach((dep) => {
|
|
197
|
-
if (typeof dep === "function") dep();
|
|
198
|
-
});
|
|
199
|
-
} else if (typeof explicitDeps === "function") {
|
|
200
|
-
explicitDeps();
|
|
241
|
+
const sub = {
|
|
242
|
+
deps: null,
|
|
243
|
+
queued: false,
|
|
244
|
+
run() {
|
|
245
|
+
if (this._cleanup) {
|
|
246
|
+
try {
|
|
247
|
+
this._cleanup();
|
|
248
|
+
} catch (e) {
|
|
249
|
+
reportErrorSafe(e, { phase: "effect.cleanup", component: owner?.name });
|
|
201
250
|
}
|
|
251
|
+
this._cleanup = null;
|
|
202
252
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
253
|
+
cleanup(this);
|
|
254
|
+
const prev = context;
|
|
255
|
+
context = this;
|
|
256
|
+
try {
|
|
257
|
+
if (explicitDeps) {
|
|
258
|
+
if (Array.isArray(explicitDeps)) {
|
|
259
|
+
for (let i = 0; i < explicitDeps.length; i++) {
|
|
260
|
+
const d = explicitDeps[i];
|
|
261
|
+
if (typeof d === "function") d();
|
|
262
|
+
}
|
|
263
|
+
} else if (typeof explicitDeps === "function") {
|
|
264
|
+
explicitDeps();
|
|
265
|
+
}
|
|
207
266
|
}
|
|
267
|
+
const res = callback();
|
|
268
|
+
if (typeof res === "function") this._cleanup = res;
|
|
269
|
+
if (owner?.isMounted) triggerUpdate(owner);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
if (!isPromiseLike$2(e)) reportErrorSafe(e, { phase: "effect", component: owner?.name });
|
|
272
|
+
else throw e;
|
|
273
|
+
} finally {
|
|
274
|
+
context = prev;
|
|
208
275
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (isPromiseLike$2(e)) throw e;
|
|
212
|
-
const name = owner ? owner.name ?? "Anonymous" : null;
|
|
213
|
-
reportErrorSafe(e, { phase: "effect", component: name });
|
|
214
|
-
} finally {
|
|
215
|
-
context.pop();
|
|
216
|
-
}
|
|
276
|
+
},
|
|
277
|
+
_cleanup: null
|
|
217
278
|
};
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (options.onLoad) {
|
|
221
|
-
onMount(execute);
|
|
222
|
-
} else {
|
|
223
|
-
execute();
|
|
224
|
-
}
|
|
225
|
-
return () => {
|
|
226
|
-
if (typeof execute._cleanup === "function") {
|
|
279
|
+
const dispose = () => {
|
|
280
|
+
if (sub._cleanup) {
|
|
227
281
|
try {
|
|
228
|
-
|
|
282
|
+
sub._cleanup();
|
|
229
283
|
} catch (e) {
|
|
230
|
-
const name = owner ? owner.name ?? "Anonymous" : null;
|
|
231
|
-
reportErrorSafe(e, { phase: "effect.cleanup", component: name });
|
|
232
284
|
}
|
|
285
|
+
sub._cleanup = null;
|
|
233
286
|
}
|
|
234
|
-
|
|
235
|
-
cleanup(execute);
|
|
287
|
+
cleanup(sub);
|
|
236
288
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
289
|
+
if (options.onLoad) {
|
|
290
|
+
onMount(() => sub.run());
|
|
291
|
+
} else {
|
|
292
|
+
sub.run();
|
|
293
|
+
}
|
|
294
|
+
return dispose;
|
|
241
295
|
}
|
|
242
296
|
function defineBindMarkerIfNeeded(source, target) {
|
|
243
297
|
if (source && source.bind === true) {
|
|
244
298
|
try {
|
|
245
|
-
Object.defineProperty(target, "bind", {
|
|
246
|
-
enumerable: true,
|
|
247
|
-
configurable: false,
|
|
248
|
-
writable: false,
|
|
249
|
-
value: true
|
|
250
|
-
});
|
|
299
|
+
Object.defineProperty(target, "bind", { enumerable: true, value: true, configurable: true });
|
|
251
300
|
} catch {
|
|
252
|
-
|
|
253
|
-
target.bind = true;
|
|
254
|
-
} catch {
|
|
255
|
-
}
|
|
301
|
+
target.bind = true;
|
|
256
302
|
}
|
|
257
303
|
}
|
|
258
304
|
}
|
|
259
305
|
function attachHelpers(s) {
|
|
260
306
|
if (!s || typeof s !== "function") return s;
|
|
261
307
|
if (typeof s.transform === "function" && typeof s.validate === "function" && typeof s.$pick === "function") return s;
|
|
262
|
-
s.$pick = (p) =>
|
|
263
|
-
return pick(s, p);
|
|
264
|
-
};
|
|
308
|
+
s.$pick = (p) => pick(s, p);
|
|
265
309
|
s.transform = (fromInput, toOutput) => {
|
|
266
310
|
const fromFn = typeof fromInput === "function" ? fromInput : (v) => v;
|
|
267
311
|
const toFn = typeof toOutput === "function" ? toOutput : (v) => v;
|
|
268
312
|
const wrapped = function(...args) {
|
|
269
|
-
if (args.length > 0)
|
|
270
|
-
return s(fromFn(args[0]));
|
|
271
|
-
}
|
|
313
|
+
if (args.length > 0) return s(fromFn(args[0]));
|
|
272
314
|
return toFn(s());
|
|
273
315
|
};
|
|
274
316
|
wrapped.peek = () => toFn(s.peek());
|
|
275
317
|
Object.defineProperty(wrapped, "value", {
|
|
276
318
|
enumerable: true,
|
|
319
|
+
configurable: true,
|
|
277
320
|
get() {
|
|
278
321
|
return wrapped.peek();
|
|
279
322
|
},
|
|
@@ -287,8 +330,8 @@ function attachHelpers(s) {
|
|
|
287
330
|
s.validate = (validator, options = {}) => {
|
|
288
331
|
const validateFn = typeof validator === "function" ? validator : null;
|
|
289
332
|
const error = signal(null);
|
|
290
|
-
const validateOn = options
|
|
291
|
-
const validateInitial =
|
|
333
|
+
const validateOn = options?.validateOn || "input";
|
|
334
|
+
const validateInitial = !!options?.validateInitial;
|
|
292
335
|
const wrapped = function(...args) {
|
|
293
336
|
if (args.length > 0) {
|
|
294
337
|
const next = args[0];
|
|
@@ -303,11 +346,7 @@ function attachHelpers(s) {
|
|
|
303
346
|
error(null);
|
|
304
347
|
return s(next);
|
|
305
348
|
}
|
|
306
|
-
|
|
307
|
-
error(res);
|
|
308
|
-
} else {
|
|
309
|
-
error("Invalid value");
|
|
310
|
-
}
|
|
349
|
+
error(typeof res === "string" && res.length ? res : "Invalid value");
|
|
311
350
|
return s.peek();
|
|
312
351
|
}
|
|
313
352
|
error(null);
|
|
@@ -331,13 +370,13 @@ function attachHelpers(s) {
|
|
|
331
370
|
error(null);
|
|
332
371
|
return true;
|
|
333
372
|
}
|
|
334
|
-
|
|
335
|
-
else error("Invalid value");
|
|
373
|
+
error(typeof res === "string" && res.length ? res : "Invalid value");
|
|
336
374
|
return false;
|
|
337
375
|
};
|
|
338
376
|
wrapped.peek = () => s.peek();
|
|
339
377
|
Object.defineProperty(wrapped, "value", {
|
|
340
378
|
enumerable: true,
|
|
379
|
+
configurable: true,
|
|
341
380
|
get() {
|
|
342
381
|
return wrapped.peek();
|
|
343
382
|
},
|
|
@@ -359,66 +398,50 @@ function attachHelpers(s) {
|
|
|
359
398
|
return s;
|
|
360
399
|
}
|
|
361
400
|
function signal(initialValue) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (running) {
|
|
367
|
-
subscribe(running, subscriptions);
|
|
368
|
-
}
|
|
369
|
-
return value;
|
|
401
|
+
const dep = {
|
|
402
|
+
value: initialValue,
|
|
403
|
+
version: 0,
|
|
404
|
+
subs: null
|
|
370
405
|
};
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const signal2 = function(...args) {
|
|
380
|
-
if (args.length > 0) {
|
|
381
|
-
return write(args[0]);
|
|
406
|
+
const s = function(newValue) {
|
|
407
|
+
if (arguments.length > 0) {
|
|
408
|
+
if (dep.value !== newValue) {
|
|
409
|
+
dep.value = newValue;
|
|
410
|
+
dep.version = ++globalVersion;
|
|
411
|
+
notify(dep);
|
|
412
|
+
}
|
|
413
|
+
return dep.value;
|
|
382
414
|
}
|
|
383
|
-
|
|
415
|
+
if (context) subscribe(context, dep);
|
|
416
|
+
return dep.value;
|
|
384
417
|
};
|
|
385
|
-
|
|
418
|
+
s.peek = () => dep.value;
|
|
419
|
+
Object.defineProperty(s, "value", {
|
|
386
420
|
enumerable: true,
|
|
421
|
+
configurable: true,
|
|
387
422
|
get() {
|
|
388
|
-
return
|
|
423
|
+
return s();
|
|
389
424
|
},
|
|
390
425
|
set(v) {
|
|
391
|
-
|
|
426
|
+
s(v);
|
|
392
427
|
}
|
|
393
428
|
});
|
|
394
|
-
|
|
395
|
-
return attachHelpers(signal2);
|
|
429
|
+
return attachHelpers(s);
|
|
396
430
|
}
|
|
397
431
|
function bindable(initialValue) {
|
|
398
432
|
const s = signal(initialValue);
|
|
399
433
|
try {
|
|
400
|
-
Object.defineProperty(s, "bind", {
|
|
401
|
-
enumerable: true,
|
|
402
|
-
configurable: false,
|
|
403
|
-
writable: false,
|
|
404
|
-
value: true
|
|
405
|
-
});
|
|
434
|
+
Object.defineProperty(s, "bind", { enumerable: true, value: true, configurable: true });
|
|
406
435
|
} catch {
|
|
407
|
-
|
|
408
|
-
s.bind = true;
|
|
409
|
-
} catch {
|
|
410
|
-
}
|
|
436
|
+
s.bind = true;
|
|
411
437
|
}
|
|
412
438
|
return attachHelpers(s);
|
|
413
439
|
}
|
|
414
|
-
function isSignalLike(v) {
|
|
415
|
-
return typeof v === "function" && typeof v.peek === "function" && "value" in v;
|
|
416
|
-
}
|
|
417
440
|
function getIn(obj, path) {
|
|
418
441
|
let cur = obj;
|
|
419
|
-
for (
|
|
442
|
+
for (let i = 0; i < path.length; i++) {
|
|
420
443
|
if (cur == null) return void 0;
|
|
421
|
-
cur = cur[
|
|
444
|
+
cur = cur[path[i]];
|
|
422
445
|
}
|
|
423
446
|
return cur;
|
|
424
447
|
}
|
|
@@ -445,9 +468,7 @@ function parsePath(path) {
|
|
|
445
468
|
return [String(path)];
|
|
446
469
|
}
|
|
447
470
|
function pick(root, path) {
|
|
448
|
-
if (!isSignalLike(root))
|
|
449
|
-
throw new Error("[round] pick(root, path) expects root to be a signal (use bindable.object(...) or signal({...})).");
|
|
450
|
-
}
|
|
471
|
+
if (!isSignalLike(root)) throw new Error("[round] pick() expects a signal.");
|
|
451
472
|
const pathArr = parsePath(path);
|
|
452
473
|
const view = function(...args) {
|
|
453
474
|
if (args.length > 0) {
|
|
@@ -460,6 +481,7 @@ function pick(root, path) {
|
|
|
460
481
|
view.peek = () => getIn(root.peek(), pathArr);
|
|
461
482
|
Object.defineProperty(view, "value", {
|
|
462
483
|
enumerable: true,
|
|
484
|
+
configurable: true,
|
|
463
485
|
get() {
|
|
464
486
|
return view.peek();
|
|
465
487
|
},
|
|
@@ -469,17 +491,9 @@ function pick(root, path) {
|
|
|
469
491
|
});
|
|
470
492
|
if (root.bind === true) {
|
|
471
493
|
try {
|
|
472
|
-
Object.defineProperty(view, "bind", {
|
|
473
|
-
enumerable: true,
|
|
474
|
-
configurable: false,
|
|
475
|
-
writable: false,
|
|
476
|
-
value: true
|
|
477
|
-
});
|
|
494
|
+
Object.defineProperty(view, "bind", { enumerable: true, value: true, configurable: true });
|
|
478
495
|
} catch {
|
|
479
|
-
|
|
480
|
-
view.bind = true;
|
|
481
|
-
} catch {
|
|
482
|
-
}
|
|
496
|
+
view.bind = true;
|
|
483
497
|
}
|
|
484
498
|
}
|
|
485
499
|
return view;
|
|
@@ -489,21 +503,14 @@ function createBindableObjectProxy(root, basePath) {
|
|
|
489
503
|
const handler = {
|
|
490
504
|
get(_target, prop) {
|
|
491
505
|
if (prop === Symbol.toStringTag) return "BindableObject";
|
|
492
|
-
if (prop === Symbol.iterator) return void 0;
|
|
493
506
|
if (prop === "peek") return () => basePath.length ? pick(root, basePath).peek() : root.peek();
|
|
494
507
|
if (prop === "value") return basePath.length ? pick(root, basePath).peek() : root.peek();
|
|
495
508
|
if (prop === "bind") return true;
|
|
496
509
|
if (prop === "$pick") {
|
|
497
|
-
return (p) =>
|
|
498
|
-
const nextPath2 = basePath.concat(parsePath(p));
|
|
499
|
-
return createBindableObjectProxy(root, nextPath2);
|
|
500
|
-
};
|
|
510
|
+
return (p) => createBindableObjectProxy(root, basePath.concat(parsePath(p)));
|
|
501
511
|
}
|
|
502
512
|
if (prop === "_root") return root;
|
|
503
513
|
if (prop === "_path") return basePath.slice();
|
|
504
|
-
if (prop === "call" || prop === "apply") {
|
|
505
|
-
return Reflect.get(_target, prop);
|
|
506
|
-
}
|
|
507
514
|
const key = String(prop);
|
|
508
515
|
const nextPath = basePath.concat(key);
|
|
509
516
|
const cacheKey = nextPath.join(".");
|
|
@@ -535,44 +542,25 @@ function createBindableObjectProxy(root, basePath) {
|
|
|
535
542
|
return true;
|
|
536
543
|
},
|
|
537
544
|
has(_target, prop) {
|
|
538
|
-
|
|
539
|
-
if (Reflect.has(_target, prop)) return true;
|
|
540
|
-
} catch {
|
|
541
|
-
}
|
|
545
|
+
if (prop === "peek" || prop === "value" || prop === "bind" || prop === "$pick") return true;
|
|
542
546
|
const v = basePath.length ? pick(root, basePath).peek() : root.peek();
|
|
543
547
|
return v != null && Object.prototype.hasOwnProperty.call(v, prop);
|
|
544
548
|
}
|
|
545
549
|
};
|
|
546
550
|
const fn = function(...args) {
|
|
547
|
-
if (args.length > 0)
|
|
548
|
-
|
|
549
|
-
return root(args[0]);
|
|
550
|
-
}
|
|
551
|
-
if (basePath.length) return pick(root, basePath)();
|
|
552
|
-
return root();
|
|
551
|
+
if (args.length > 0) return basePath.length ? pick(root, basePath)(args[0]) : root(args[0]);
|
|
552
|
+
return basePath.length ? pick(root, basePath)() : root();
|
|
553
553
|
};
|
|
554
554
|
fn.peek = () => basePath.length ? pick(root, basePath).peek() : root.peek();
|
|
555
|
-
Object.defineProperty(fn, "value", {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
set(v) {
|
|
561
|
-
fn(v);
|
|
562
|
-
}
|
|
563
|
-
});
|
|
555
|
+
Object.defineProperty(fn, "value", { enumerable: true, configurable: true, get() {
|
|
556
|
+
return fn.peek();
|
|
557
|
+
}, set(v) {
|
|
558
|
+
fn(v);
|
|
559
|
+
} });
|
|
564
560
|
try {
|
|
565
|
-
Object.defineProperty(fn, "bind", {
|
|
566
|
-
enumerable: true,
|
|
567
|
-
configurable: false,
|
|
568
|
-
writable: false,
|
|
569
|
-
value: true
|
|
570
|
-
});
|
|
561
|
+
Object.defineProperty(fn, "bind", { enumerable: true, value: true, configurable: true });
|
|
571
562
|
} catch {
|
|
572
|
-
|
|
573
|
-
fn.bind = true;
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
563
|
+
fn.bind = true;
|
|
576
564
|
}
|
|
577
565
|
return new Proxy(fn, handler);
|
|
578
566
|
}
|
|
@@ -581,14 +569,44 @@ bindable.object = function(initialObject = {}) {
|
|
|
581
569
|
return createBindableObjectProxy(root, []);
|
|
582
570
|
};
|
|
583
571
|
function derive(fn) {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
572
|
+
const dep = {
|
|
573
|
+
fn,
|
|
574
|
+
value: void 0,
|
|
575
|
+
version: -1,
|
|
576
|
+
depsVersion: -1,
|
|
577
|
+
subs: null,
|
|
578
|
+
deps: null,
|
|
579
|
+
isComputed: true,
|
|
580
|
+
run() {
|
|
581
|
+
cleanup(this);
|
|
582
|
+
const prev = context;
|
|
583
|
+
context = this;
|
|
584
|
+
try {
|
|
585
|
+
this.value = this.fn();
|
|
586
|
+
this.depsVersion = globalVersion;
|
|
587
|
+
this.version = ++globalVersion;
|
|
588
|
+
} finally {
|
|
589
|
+
context = prev;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const s = function() {
|
|
594
|
+
if (dep.version === -1 || dep.depsVersion < globalVersion) dep.run();
|
|
595
|
+
if (context) subscribe(context, dep);
|
|
596
|
+
return dep.value;
|
|
597
|
+
};
|
|
598
|
+
s.peek = () => {
|
|
599
|
+
if (dep.version === -1 || dep.depsVersion < globalVersion) dep.run();
|
|
600
|
+
return dep.value;
|
|
601
|
+
};
|
|
602
|
+
Object.defineProperty(s, "value", { enumerable: true, configurable: true, get() {
|
|
603
|
+
return s();
|
|
604
|
+
} });
|
|
605
|
+
return attachHelpers(s);
|
|
589
606
|
}
|
|
590
607
|
const Signals = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
591
608
|
__proto__: null,
|
|
609
|
+
batch,
|
|
592
610
|
bindable,
|
|
593
611
|
derive,
|
|
594
612
|
effect,
|
|
@@ -1432,6 +1450,9 @@ function Link(props = {}) {
|
|
|
1432
1450
|
const onClick = (e) => {
|
|
1433
1451
|
if (typeof props.onClick === "function") props.onClick(e);
|
|
1434
1452
|
if (e.defaultPrevented) return;
|
|
1453
|
+
if (props.target === "_blank") return;
|
|
1454
|
+
const strHref = String(href);
|
|
1455
|
+
if (strHref.includes("://") || strHref.startsWith("mailto:") || strHref.startsWith("tel:")) return;
|
|
1435
1456
|
if (!spa || reload) return;
|
|
1436
1457
|
if (e.button !== 0) return;
|
|
1437
1458
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
@@ -1980,6 +2001,7 @@ export {
|
|
|
1980
2001
|
Route,
|
|
1981
2002
|
Suspense,
|
|
1982
2003
|
SuspenseContext,
|
|
2004
|
+
batch,
|
|
1983
2005
|
bindContext,
|
|
1984
2006
|
bindable,
|
|
1985
2007
|
captureContext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "round-core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A lightweight frontend framework for SPA with signals and fine grained reactivity",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"dev": "node ./src/cli.js dev --config ./test/main/round.config.json",
|
|
22
22
|
"build": "node ./src/cli.js build --config ./test/main/round.config.json",
|
|
23
23
|
"build:core": "vite build -c vite.config.build.js",
|
|
24
|
-
"test": "vitest run",
|
|
25
24
|
"bench": "bun run --cwd benchmarks bench",
|
|
26
25
|
"bench:build": "bun run --cwd benchmarks bench:build",
|
|
27
26
|
"bench:runtime": "bun run --cwd benchmarks bench:runtime"
|