balises 0.2.1 → 0.4.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 +257 -46
- package/dist/balises.esm.js +143 -111
- package/dist/balises.esm.js.map +1 -1
- package/dist/balises.iife.js +143 -114
- package/dist/balises.iife.js.map +1 -1
- package/dist/balises.iife.min.js +1 -2
- package/dist/balises.iife.min.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/parser.d.ts +2 -3
- package/dist/esm/parser.d.ts.map +1 -1
- package/dist/esm/parser.js +17 -18
- package/dist/esm/parser.js.map +1 -1
- package/dist/esm/signals/computed.d.ts.map +1 -1
- package/dist/esm/signals/computed.js +12 -22
- package/dist/esm/signals/computed.js.map +1 -1
- package/dist/esm/signals/context.d.ts +24 -1
- package/dist/esm/signals/context.d.ts.map +1 -1
- package/dist/esm/signals/context.js +52 -2
- package/dist/esm/signals/context.js.map +1 -1
- package/dist/esm/signals/effect.d.ts.map +1 -1
- package/dist/esm/signals/effect.js +1 -0
- package/dist/esm/signals/effect.js.map +1 -1
- package/dist/esm/signals/index.d.ts +2 -2
- package/dist/esm/signals/index.d.ts.map +1 -1
- package/dist/esm/signals/index.js +2 -2
- package/dist/esm/signals/index.js.map +1 -1
- package/dist/esm/signals/store.d.ts.map +1 -1
- package/dist/esm/signals/store.js +3 -4
- package/dist/esm/signals/store.js.map +1 -1
- package/dist/esm/template.d.ts.map +1 -1
- package/dist/esm/template.js +116 -107
- package/dist/esm/template.js.map +1 -1
- package/package.json +17 -6
package/README.md
CHANGED
|
@@ -4,15 +4,37 @@
|
|
|
4
4
|
<img alt="balises" src="./assets/logo.svg" width="280">
|
|
5
5
|
</picture>
|
|
6
6
|
|
|
7
|
-
### A minimal reactive HTML templating library. ~3.0KB gzipped.
|
|
7
|
+
### A minimal reactive HTML templating library for building websites and web components. ~3.0KB gzipped.
|
|
8
|
+
|
|
9
|
+
Balises gives you reactive signals and HTML templates without the framework overhead. Works great with custom elements, vanilla JavaScript projects, or anywhere you need dynamic UIs but don't want to pull in React.
|
|
10
|
+
|
|
11
|
+
**You can also use it as a standalone signals library** - the reactivity system works independently of the templating, making it useful for any JavaScript project that needs reactive state management.
|
|
8
12
|
|
|
9
13
|
**[📚️ Documentation & Examples](https://elbywan.github.io/balises/)**
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## Preamble
|
|
16
|
+
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> 🚧 Use at your own discretion .
|
|
19
|
+
|
|
20
|
+
This library was built in a couple of days **using LLM assistance** as an experiment to see if it was possible to produce something high-quality and performant very quickly.
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
It all begun with me needing a lightweight reactive templating solution with zero dependencies for a non-critical work project at [Datadog](https://www.datadoghq.com/), and since I wanted to explore what modern AI-assisted development could achieve it was a good fit.
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
Ultimately it turns out that I am quite happy with the result! It is quite performant, ergonomic, has a very small bundle size, is thoroughly tested and suits my needs well. 🌟
|
|
25
|
+
|
|
26
|
+
**However, please be aware that this is a personal side project with limited maintenance and no guarantees of long-term support.**
|
|
27
|
+
|
|
28
|
+
## Table of Contents
|
|
29
|
+
|
|
30
|
+
- [Installation](#installation)
|
|
31
|
+
- [Quick Start](#quick-start)
|
|
32
|
+
- [Building Web Components](#building-web-components)
|
|
33
|
+
- [Composable Function Components](#composable-function-components)
|
|
34
|
+
- [Template Syntax](#template-syntax)
|
|
35
|
+
- [Reactivity API](#reactivity-api)
|
|
36
|
+
- [Tree-Shaking / Modular Imports](#tree-shaking--modular-imports)
|
|
37
|
+
- [Benchmarks](#benchmarks)
|
|
16
38
|
|
|
17
39
|
## Installation
|
|
18
40
|
|
|
@@ -22,6 +44,8 @@ npm install balises
|
|
|
22
44
|
|
|
23
45
|
## Quick Start
|
|
24
46
|
|
|
47
|
+
Balises uses tagged template literals to create reactive HTML. Just interpolate signals into your markup and they'll automatically update the DOM when they change.
|
|
48
|
+
|
|
25
49
|
```ts
|
|
26
50
|
import { html, signal } from "balises";
|
|
27
51
|
|
|
@@ -37,9 +61,72 @@ document.body.appendChild(fragment);
|
|
|
37
61
|
// Call dispose() when done to clean up subscriptions
|
|
38
62
|
```
|
|
39
63
|
|
|
64
|
+
## Building Web Components
|
|
65
|
+
|
|
66
|
+
Balises works naturally with the Web Components API. Just render templates in `connectedCallback` and clean up in `disconnectedCallback`.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { html, signal, effect } from "balises";
|
|
70
|
+
|
|
71
|
+
class Counter extends HTMLElement {
|
|
72
|
+
#count = signal(0);
|
|
73
|
+
#dispose?: () => void;
|
|
74
|
+
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
// Auto-sync to localStorage
|
|
77
|
+
const syncEffect = effect(() => {
|
|
78
|
+
localStorage.setItem("counter", String(this.#count.value));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const { fragment, dispose } = html`
|
|
82
|
+
<div>
|
|
83
|
+
<p>Count: ${this.#count}</p>
|
|
84
|
+
<button @click=${() => this.#count.update((n) => n - 1)}>-</button>
|
|
85
|
+
<button @click=${() => this.#count.update((n) => n + 1)}>+</button>
|
|
86
|
+
</div>
|
|
87
|
+
`.render();
|
|
88
|
+
|
|
89
|
+
this.appendChild(fragment);
|
|
90
|
+
this.#dispose = () => {
|
|
91
|
+
syncEffect();
|
|
92
|
+
dispose();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
disconnectedCallback() {
|
|
97
|
+
this.#dispose?.();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
customElements.define("x-counter", Counter);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use it in your HTML:
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<x-counter></x-counter>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
You can build entire apps this way, or just add interactive widgets to existing pages. No build step required if you use it from a CDN.
|
|
111
|
+
|
|
112
|
+
## Composable Function Components
|
|
113
|
+
|
|
114
|
+
You can also use plain functions that return templates. Pass the store and access its properties in function wrappers to keep things reactive:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
function Counter({ state }) {
|
|
118
|
+
return html`
|
|
119
|
+
<button @click=${() => state.count++}>${() => state.count}</button>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const state = store({ count: 0 });
|
|
124
|
+
html`<div>${Counter({ state })}</div>`.render();
|
|
125
|
+
```
|
|
126
|
+
|
|
40
127
|
## Template Syntax
|
|
41
128
|
|
|
42
|
-
The `html` tagged template creates reactive DOM fragments.
|
|
129
|
+
The `html` tagged template creates reactive DOM fragments. When you interpolate a signal, that specific part of the DOM updates automatically when the signal changes.
|
|
43
130
|
|
|
44
131
|
### Interpolation Types
|
|
45
132
|
|
|
@@ -53,6 +140,20 @@ The `html` tagged template creates reactive DOM fragments.
|
|
|
53
140
|
|
|
54
141
|
All interpolations accept reactive values (`Signal` or `Computed`) and will auto-update when they change.
|
|
55
142
|
|
|
143
|
+
### Function Interpolation
|
|
144
|
+
|
|
145
|
+
Functions are wrapped in `computed()` automatically:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const state = store({ count: 0 });
|
|
149
|
+
|
|
150
|
+
html`
|
|
151
|
+
<p>Count: ${() => state.count}</p>
|
|
152
|
+
<p>Doubled: ${() => state.count * 2}</p>
|
|
153
|
+
${() => (state.count > 10 ? html`<p>High score!</p>` : null)}
|
|
154
|
+
`.render();
|
|
155
|
+
```
|
|
156
|
+
|
|
56
157
|
### Nested Templates
|
|
57
158
|
|
|
58
159
|
Templates can be nested, and arrays of templates are flattened:
|
|
@@ -62,14 +163,14 @@ const items = signal(["a", "b", "c"]);
|
|
|
62
163
|
|
|
63
164
|
html`
|
|
64
165
|
<ul>
|
|
65
|
-
${
|
|
166
|
+
${() => items.value.map((item) => html`<li>${item}</li>`)}
|
|
66
167
|
</ul>
|
|
67
168
|
`.render();
|
|
68
169
|
```
|
|
69
170
|
|
|
70
171
|
### Efficient List Rendering with `each()`
|
|
71
172
|
|
|
72
|
-
|
|
173
|
+
When rendering lists that change frequently, use `each()` for keyed reconciliation. It caches templates by key so items can be reordered, added, or removed without recreating the DOM nodes:
|
|
73
174
|
|
|
74
175
|
```ts
|
|
75
176
|
import { html, signal, each } from "balises";
|
|
@@ -99,34 +200,20 @@ items.value[0].name.value = "Alicia";
|
|
|
99
200
|
items.value = [...items.value].reverse();
|
|
100
201
|
```
|
|
101
202
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
**With key function (recommended when objects may be recreated with same identity):**
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
each(list, keyFn, renderFn);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
- `list` - A reactive array (Signal, Computed, or getter function)
|
|
111
|
-
- `keyFn` - Extracts a unique key from each item: `(item, index) => key`
|
|
112
|
-
- `renderFn` - Renders each item (called once per unique key)
|
|
113
|
-
|
|
114
|
-
**Without key function (automatic keying):**
|
|
203
|
+
Signatures:
|
|
115
204
|
|
|
116
205
|
```ts
|
|
117
|
-
each(list, renderFn);
|
|
206
|
+
each(list, keyFn, renderFn); // keyed by keyFn(item, index)
|
|
207
|
+
each(list, renderFn); // keyed by object reference or index
|
|
118
208
|
```
|
|
119
209
|
|
|
120
|
-
|
|
121
|
-
- Primitives use index as key (duplicates are handled correctly)
|
|
122
|
-
|
|
123
|
-
For content updates without list reconciliation, use nested signals (like `name: signal("Alice")` above).
|
|
210
|
+
If you want to update item content without triggering list reconciliation, nest signals inside your items (like `name: signal("Alice")` above).
|
|
124
211
|
|
|
125
212
|
## Reactivity API
|
|
126
213
|
|
|
127
|
-
### `signal<T>(value)`
|
|
214
|
+
### `signal<T>(value)`
|
|
128
215
|
|
|
129
|
-
|
|
216
|
+
Wraps a value to make it reactive.
|
|
130
217
|
|
|
131
218
|
```ts
|
|
132
219
|
const name = signal("world");
|
|
@@ -134,7 +221,7 @@ console.log(name.value); // "world"
|
|
|
134
221
|
name.value = "everyone"; // Notifies subscribers
|
|
135
222
|
```
|
|
136
223
|
|
|
137
|
-
**Updating based on current value:**
|
|
224
|
+
**Updating based on the current value:**
|
|
138
225
|
|
|
139
226
|
```ts
|
|
140
227
|
const count = signal(0);
|
|
@@ -148,9 +235,9 @@ count.value = count.value + 1;
|
|
|
148
235
|
count.value = count.value * 2;
|
|
149
236
|
```
|
|
150
237
|
|
|
151
|
-
### `computed<T>(fn)`
|
|
238
|
+
### `computed<T>(fn)`
|
|
152
239
|
|
|
153
|
-
|
|
240
|
+
Derives a value from other signals. Automatically tracks dependencies.
|
|
154
241
|
|
|
155
242
|
```ts
|
|
156
243
|
const firstName = signal("John");
|
|
@@ -162,11 +249,11 @@ firstName.value = "Jane";
|
|
|
162
249
|
console.log(fullName.value); // "Jane Doe"
|
|
163
250
|
```
|
|
164
251
|
|
|
165
|
-
|
|
252
|
+
Computed values are lazy - they only recalculate when accessed and a dependency has changed.
|
|
166
253
|
|
|
167
254
|
### `effect(fn)`
|
|
168
255
|
|
|
169
|
-
|
|
256
|
+
Runs a side effect whenever its dependencies change. Under the hood, `effect()` is a computed with an automatic subscription, which makes it run eagerly on every dependency change rather than waiting to be accessed.
|
|
170
257
|
|
|
171
258
|
```ts
|
|
172
259
|
import { signal, effect } from "balises";
|
|
@@ -183,14 +270,14 @@ count.value = 1; // Logs "Count is now: 1" and updates title
|
|
|
183
270
|
dispose(); // Stop the effect
|
|
184
271
|
```
|
|
185
272
|
|
|
186
|
-
|
|
273
|
+
Good for things like:
|
|
187
274
|
|
|
188
275
|
- Syncing state to localStorage
|
|
189
|
-
- Updating document.title or other
|
|
276
|
+
- Updating document.title or other globals
|
|
190
277
|
- Logging and analytics
|
|
191
|
-
- Network requests
|
|
278
|
+
- Network requests based on state
|
|
192
279
|
|
|
193
|
-
|
|
280
|
+
When you call `dispose()` on a template, any effects created during rendering are cleaned up automatically.
|
|
194
281
|
|
|
195
282
|
**Example: Auto-sync to localStorage**
|
|
196
283
|
|
|
@@ -207,7 +294,7 @@ favorites.value = [...favorites.value, "new item"];
|
|
|
207
294
|
|
|
208
295
|
### `store<T>(obj)`
|
|
209
296
|
|
|
210
|
-
|
|
297
|
+
A proxy-based alternative to signals. Nested plain objects become reactive automatically.
|
|
211
298
|
|
|
212
299
|
```ts
|
|
213
300
|
const state = store({ count: 0, user: { name: "Alice" } });
|
|
@@ -215,7 +302,7 @@ state.count++; // Reactive
|
|
|
215
302
|
state.user.name = "Bob"; // Also reactive (nested)
|
|
216
303
|
```
|
|
217
304
|
|
|
218
|
-
**Note:** Array mutations like `push()`, `pop()`, `splice()` do **not** trigger reactivity.
|
|
305
|
+
**Note:** Array mutations like `push()`, `pop()`, `splice()` do **not** trigger reactivity. You need to reassign the array:
|
|
219
306
|
|
|
220
307
|
```ts
|
|
221
308
|
const state = store({ items: [1, 2, 3] });
|
|
@@ -226,7 +313,7 @@ state.items.push(4);
|
|
|
226
313
|
// ✅ Triggers reactivity
|
|
227
314
|
state.items = [...state.items, 4];
|
|
228
315
|
|
|
229
|
-
//
|
|
316
|
+
// Alternative: Use signal for arrays to get the .update() helper
|
|
230
317
|
const items = signal([1, 2, 3]);
|
|
231
318
|
items.update((arr) => [...arr, 4]);
|
|
232
319
|
items.update((arr) => arr.filter((n) => n !== 2));
|
|
@@ -234,7 +321,7 @@ items.update((arr) => arr.filter((n) => n !== 2));
|
|
|
234
321
|
|
|
235
322
|
### `batch<T>(fn)`
|
|
236
323
|
|
|
237
|
-
|
|
324
|
+
Batches multiple signal updates so subscribers only get notified once at the end.
|
|
238
325
|
|
|
239
326
|
```ts
|
|
240
327
|
import { batch, signal } from "balises";
|
|
@@ -248,6 +335,28 @@ batch(() => {
|
|
|
248
335
|
}); // Subscribers notified once after both updates
|
|
249
336
|
```
|
|
250
337
|
|
|
338
|
+
### `scope(fn)`
|
|
339
|
+
|
|
340
|
+
Groups reactive primitives together so you can dispose them all at once.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { scope, signal, computed, effect } from "balises";
|
|
344
|
+
|
|
345
|
+
const [state, dispose] = scope(() => {
|
|
346
|
+
const count = signal(0);
|
|
347
|
+
const doubled = computed(() => count.value * 2);
|
|
348
|
+
effect(() => console.log(doubled.value));
|
|
349
|
+
return { count, doubled };
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Use state.count, state.doubled...
|
|
353
|
+
|
|
354
|
+
// Later: clean up everything at once
|
|
355
|
+
dispose();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Handy for components or temporary reactive contexts where you need bulk cleanup.
|
|
359
|
+
|
|
251
360
|
### `isSignal(value)`
|
|
252
361
|
|
|
253
362
|
Type guard to check if a value is reactive (`Signal` or `Computed`).
|
|
@@ -274,7 +383,7 @@ unsubscribe(); // Stop listening
|
|
|
274
383
|
|
|
275
384
|
### `.dispose()`
|
|
276
385
|
|
|
277
|
-
|
|
386
|
+
Stops a computed from tracking dependencies and frees memory.
|
|
278
387
|
|
|
279
388
|
```ts
|
|
280
389
|
const doubled = computed(() => count.value * 2);
|
|
@@ -283,21 +392,40 @@ doubled.dispose(); // Stops tracking, frees memory
|
|
|
283
392
|
|
|
284
393
|
## Tree-Shaking / Modular Imports
|
|
285
394
|
|
|
286
|
-
|
|
395
|
+
You can import just what you need to keep bundle size down:
|
|
287
396
|
|
|
288
397
|
```ts
|
|
289
398
|
// Full library (~3.0KB gzipped)
|
|
290
399
|
import { html, signal, computed, effect } from "balises";
|
|
291
400
|
|
|
292
|
-
// Signals only
|
|
293
|
-
import { signal, computed, effect, store, batch } from "balises/signals";
|
|
401
|
+
// Signals only (no HTML templating - use in any JS project)
|
|
402
|
+
import { signal, computed, effect, store, batch, scope } from "balises/signals";
|
|
294
403
|
|
|
295
404
|
// Individual modules
|
|
296
405
|
import { signal } from "balises/signals/signal";
|
|
297
406
|
import { computed } from "balises/signals/computed";
|
|
298
407
|
import { effect } from "balises/signals/effect";
|
|
299
408
|
import { store } from "balises/signals/store";
|
|
300
|
-
import { batch } from "balises/signals/context";
|
|
409
|
+
import { batch, scope } from "balises/signals/context";
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Using as a Standalone Signals Library
|
|
413
|
+
|
|
414
|
+
The reactivity system is completely independent of the HTML templating. You can use just the signals in Node.js, Electron, or any JavaScript environment:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
import { signal, computed, effect } from "balises/signals";
|
|
418
|
+
|
|
419
|
+
// Reactive state management without DOM
|
|
420
|
+
const users = signal([]);
|
|
421
|
+
const userCount = computed(() => users.value.length);
|
|
422
|
+
|
|
423
|
+
effect(() => {
|
|
424
|
+
console.log(`Total users: ${userCount.value}`);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
users.value = [{ name: "Alice" }, { name: "Bob" }];
|
|
428
|
+
// Logs: "Total users: 2"
|
|
301
429
|
```
|
|
302
430
|
|
|
303
431
|
## Full Example
|
|
@@ -314,7 +442,7 @@ class Counter extends HTMLElement {
|
|
|
314
442
|
|
|
315
443
|
const { fragment, dispose } = html`
|
|
316
444
|
<div>
|
|
317
|
-
<p>Count: ${
|
|
445
|
+
<p>Count: ${() => state.count} (double: ${double})</p>
|
|
318
446
|
<button @click=${() => state.count++}>+</button>
|
|
319
447
|
<button @click=${() => state.count--}>-</button>
|
|
320
448
|
</div>
|
|
@@ -368,6 +496,89 @@ class Counter extends HTMLElement {
|
|
|
368
496
|
}
|
|
369
497
|
```
|
|
370
498
|
|
|
499
|
+
<!-- BENCHMARK_RESULTS_START -->
|
|
500
|
+
|
|
501
|
+
## Benchmarks
|
|
502
|
+
|
|
503
|
+
Performance comparison of Balises against other popular reactive libraries. Benchmarks run in isolated processes to prevent V8 JIT contamination.
|
|
504
|
+
|
|
505
|
+
**Test Environment:**
|
|
506
|
+
|
|
507
|
+
- Node.js with V8 engine
|
|
508
|
+
- Each test runs in a separate process (isolated mode)
|
|
509
|
+
- **10 warmup runs** to stabilize JIT
|
|
510
|
+
- **100 iterations per test**, keeping middle 20 (discarding 40 best + 40 worst to reduce outliers)
|
|
511
|
+
- Tests measure pure reactive propagation (not DOM rendering)
|
|
512
|
+
|
|
513
|
+
**Scoring Methodology:**
|
|
514
|
+
|
|
515
|
+
- Overall ranking uses a combined score (50% average rank + 50% normalized average time)
|
|
516
|
+
- This ensures both consistency across scenarios (rank) and absolute performance (time) are valued equally
|
|
517
|
+
- "vs Fastest" compares average time to the fastest library
|
|
518
|
+
|
|
519
|
+
### Overall Performance
|
|
520
|
+
|
|
521
|
+
```
|
|
522
|
+
┌───────┬───────────────────┬──────────┬───────────────┬──────────────────┐
|
|
523
|
+
│ Rank │ Library │ Avg Rank │ Avg Time (μs) │ vs Fastest │
|
|
524
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
525
|
+
│ #1 🏆 │ preact@1.12.1 │ 1.5 │ 47.33 │ 1.00x (baseline) │
|
|
526
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
527
|
+
│ #2 │ balises@0.4.0 │ 1.5 │ 71.15 │ 1.50x │
|
|
528
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
529
|
+
│ #3 │ vue@3.5.26 │ 3.2 │ 80.96 │ 1.71x │
|
|
530
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
531
|
+
│ #4 │ maverick@6.0.0 │ 3.8 │ 89.56 │ 1.89x │
|
|
532
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
533
|
+
│ #5 │ solid@1.9.10 │ 5.2 │ 214.92 │ 4.54x │
|
|
534
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
535
|
+
│ #6 │ mobx@6.15.0 │ 6.0 │ 623.36 │ 13.17x │
|
|
536
|
+
├───────┼───────────────────┼──────────┼───────────────┼──────────────────┤
|
|
537
|
+
│ #7 │ hyperactiv@0.11.3 │ 6.8 │ 695.71 │ 14.70x │
|
|
538
|
+
└───────┴───────────────────┴──────────┴───────────────┴──────────────────┘
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Performance by Scenario
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
┌───────────────────┬───────────────┬─────────────┬────────────────┬────────────────────┬─────────────┬──────────────┬──────────┐
|
|
545
|
+
│ Library │ S1: 1: Layers │ S2: 2: Wide │ S3: 3: Diamond │ S4: 4: Conditional │ S5: 5: List │ S6: 6: Batch │ Avg Rank │
|
|
546
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
547
|
+
│ preact@1.12.1 │ #1 🏆 │ #1 🏆 │ #2 │ #1 🏆 │ #2 │ #2 │ 1.5 │
|
|
548
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
549
|
+
│ balises@0.4.0 │ #2 │ #2 │ #1 🏆 │ #2 │ #1 🏆 │ #1 🏆 │ 1.5 │
|
|
550
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
551
|
+
│ vue@3.5.26 │ #3 │ #3 │ #3 │ #3 │ #3 │ #4 │ 3.2 │
|
|
552
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
553
|
+
│ maverick@6.0.0 │ #4 │ #4 │ #4 │ #4 │ #4 │ #3 │ 3.8 │
|
|
554
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
555
|
+
│ solid@1.9.10 │ #5 │ #6 │ #5 │ #5 │ #5 │ #5 │ 5.2 │
|
|
556
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
557
|
+
│ mobx@6.15.0 │ #7 │ #5 │ #6 │ #6 │ #6 │ #6 │ 6.0 │
|
|
558
|
+
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
559
|
+
│ hyperactiv@0.11.3 │ #6 │ #7 │ #7 │ #7 │ #7 │ #7 │ 6.8 │
|
|
560
|
+
└───────────────────┴───────────────┴─────────────┴────────────────┴────────────────────┴─────────────┴──────────────┴──────────┘
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**Scenarios:**
|
|
564
|
+
|
|
565
|
+
- **S1: Layers** - Deep dependency chains (A→B→C→D...)
|
|
566
|
+
- **S2: Wide** - Many independent signals updating in parallel
|
|
567
|
+
- **S3: Diamond** - Multiple paths to same computed (diamond dependencies)
|
|
568
|
+
- **S4: Conditional** - Dynamic subscriptions (like v-if logic)
|
|
569
|
+
- **S5: List** - List operations with filtering (like v-for patterns)
|
|
570
|
+
- **S6: Batch** - Batched/transactional updates
|
|
571
|
+
|
|
572
|
+
**Interpretation:**
|
|
573
|
+
|
|
574
|
+
- Balises performs well across all scenarios, particularly excelling at diamond dependencies, list operations, and batching
|
|
575
|
+
- These are synthetic benchmarks measuring pure reactivity - real apps should consider the whole picture (ecosystem, docs, community, etc.)
|
|
576
|
+
- Lower rank = better performance
|
|
577
|
+
|
|
578
|
+
_Last updated: 2025-12-31_
|
|
579
|
+
|
|
580
|
+
<!-- BENCHMARK_RESULTS_END -->
|
|
581
|
+
|
|
371
582
|
## Scripts
|
|
372
583
|
|
|
373
584
|
```bash
|