sprae 11.5.1 → 11.5.3
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/core.js +14 -8
- package/directive/class.js +3 -1
- package/directive/each.js +4 -0
- package/directive/ref.js +7 -6
- package/directive/value.js +7 -18
- package/directive/with.js +7 -1
- package/dist/sprae.auto.js +138 -108
- package/dist/sprae.auto.js.map +4 -4
- package/dist/sprae.auto.min.js +2 -2
- package/dist/sprae.auto.min.js.map +3 -3
- package/dist/sprae.js +103 -79
- package/dist/sprae.js.map +4 -4
- package/dist/sprae.min.js +2 -2
- package/dist/sprae.min.js.map +3 -3
- package/dist/sprae.umd.js +138 -108
- package/dist/sprae.umd.js.map +4 -4
- package/dist/sprae.umd.min.js +2 -2
- package/dist/sprae.umd.min.js.map +3 -3
- package/package.json +1 -1
- package/readme.md +45 -44
- package/signal.js +1 -0
- package/sprae.js +3 -0
- package/store.js +46 -30
package/readme.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> DOM tree microhydration
|
|
4
4
|
|
|
5
|
-
_Sprae_ is open & minimalistic progressive enhancement framework with _preact-signals_
|
|
6
|
-
Perfect for small
|
|
5
|
+
_Sprae_ is open & minimalistic progressive enhancement framework with _preact-signals_ reactivity.<br/>
|
|
6
|
+
Perfect for small websites, static pages, prototypes, lightweight UI or SSR.<br/>
|
|
7
7
|
A light and fast alternative to _alpine_, _petite-vue_, _lucia_ etc (see [why](#justification)).
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
@@ -27,18 +27,9 @@ A light and fast alternative to _alpine_, _petite-vue_, _lucia_ etc (see [why](#
|
|
|
27
27
|
|
|
28
28
|
Sprae evaluates `:`-directives and evaporates them, returning reactive state for updates.
|
|
29
29
|
|
|
30
|
-
### Autoinit
|
|
31
|
-
|
|
32
|
-
`sprae.auto` autoinits sprae on document, which is useful for SPA, [SSR](#jsx) etc.
|
|
33
|
-
|
|
34
|
-
```html
|
|
35
|
-
<!-- Optional attr `prefix` (by default ':'). -->
|
|
36
|
-
<script src="https://unpkg.com/sprae" prefix="s-"></script>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
30
|
### UMD
|
|
40
31
|
|
|
41
|
-
`sprae.umd` enables sprae via CDN,
|
|
32
|
+
`sprae.umd` enables sprae via CDN, CJS, AMD etc.
|
|
42
33
|
|
|
43
34
|
```html
|
|
44
35
|
<script src="https://unpkg.com/sprae/dist/sprae.umd"></script>
|
|
@@ -47,6 +38,15 @@ Sprae evaluates `:`-directives and evaporates them, returning reactive state for
|
|
|
47
38
|
</script>
|
|
48
39
|
```
|
|
49
40
|
|
|
41
|
+
### Autoinit
|
|
42
|
+
|
|
43
|
+
`sprae.auto` autoinits sprae on document body.
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<!-- Optional attr `prefix` (by default ':'). -->
|
|
47
|
+
<script src="https://unpkg.com/sprae/dist/sprae.auto" prefix="js-"></script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
50
|
|
|
51
51
|
## Directives
|
|
52
52
|
|
|
@@ -361,11 +361,42 @@ sprae.use({
|
|
|
361
361
|
// configure compiler
|
|
362
362
|
compile,
|
|
363
363
|
|
|
364
|
-
// custom prefix
|
|
365
|
-
prefix: '
|
|
364
|
+
// custom prefix, default is `:`
|
|
365
|
+
prefix: 'js-'
|
|
366
366
|
})
|
|
367
367
|
```
|
|
368
368
|
|
|
369
|
+
## JSX
|
|
370
|
+
|
|
371
|
+
Sprae works with JSX via custom prefix.
|
|
372
|
+
|
|
373
|
+
Case: Next.js server components fail at dynamic UI – active nav, tabs, sliders etc. Converting to client components screws up data fetching and adds overhead. Sprae can offload UI logic to keep server components intact.
|
|
374
|
+
|
|
375
|
+
```jsx
|
|
376
|
+
// app/page.jsx - server component
|
|
377
|
+
export default function Page() {
|
|
378
|
+
return <>
|
|
379
|
+
<nav id="nav">
|
|
380
|
+
<a href="/" js-class="location.pathname === '/' && 'active'">Home</a>
|
|
381
|
+
<a href="/about" js-class="location.pathname === '/about' && 'active'">About</a>
|
|
382
|
+
</nav>
|
|
383
|
+
...
|
|
384
|
+
</>
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```jsx
|
|
389
|
+
// layout.jsx
|
|
390
|
+
import Script from 'next/script'
|
|
391
|
+
|
|
392
|
+
export default function Layout({ children }) {
|
|
393
|
+
return <>
|
|
394
|
+
{children}
|
|
395
|
+
<Script src="https://unpkg.com/sprae" prefix="js-" />
|
|
396
|
+
</>
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
369
400
|
## Hints
|
|
370
401
|
|
|
371
402
|
* To prevent [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) add `<style>[:each],[:if],[:else] {visibility: hidden}</style>`.
|
|
@@ -468,36 +499,6 @@ npm run results
|
|
|
468
499
|
* [hmpl](https://github.com/hmpl-language/hmpl)
|
|
469
500
|
-->
|
|
470
501
|
|
|
471
|
-
## JSX
|
|
472
|
-
|
|
473
|
-
Sprae can work with JSX via custom prefix, eg. `s-`.
|
|
474
|
-
|
|
475
|
-
Useful as Next.js companion for SSR. Server components fail at dynamic UI – active nav, tabs, sliders etc. Converting to client components screws up data fetching and adds overhead. Sprae can offload UI logic to keep server components intact.
|
|
476
|
-
|
|
477
|
-
```jsx
|
|
478
|
-
// app/page.jsx - server component
|
|
479
|
-
export default function Page() {
|
|
480
|
-
return <>
|
|
481
|
-
<nav id="nav">
|
|
482
|
-
<a href="/" s-class="location.path === '/' && 'active'">Home</a>
|
|
483
|
-
<a href="/about" s-class="location.path === '/about' && 'active'">About</a>
|
|
484
|
-
</nav>
|
|
485
|
-
...
|
|
486
|
-
</>
|
|
487
|
-
}
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
```jsx
|
|
491
|
-
// layout.jsx
|
|
492
|
-
import Script from 'next/script'
|
|
493
|
-
|
|
494
|
-
export default function Layout({ children }) {
|
|
495
|
-
return <>
|
|
496
|
-
{children}
|
|
497
|
-
<Script type="module" src="https://unpkg.com/sprae/dist/sprae.auto" prefix="s-" />
|
|
498
|
-
</>
|
|
499
|
-
}
|
|
500
|
-
```
|
|
501
502
|
|
|
502
503
|
## Examples
|
|
503
504
|
|
package/signal.js
CHANGED
|
@@ -41,6 +41,7 @@ export let signal = (v, s, obs = new Set) => (
|
|
|
41
41
|
),
|
|
42
42
|
batch = fn => fn(),
|
|
43
43
|
untracked = batch,
|
|
44
|
+
// untracked = (fn, prev, v) => (prev = current, current = null, v = fn(), current = prev, v),
|
|
44
45
|
|
|
45
46
|
// signals adapter - allows switching signals implementation and not depend on core
|
|
46
47
|
use = (s) => (
|
package/sprae.js
CHANGED
|
@@ -17,4 +17,7 @@ import './directive/data.js'
|
|
|
17
17
|
// default compiler (indirect new Function to avoid detector)
|
|
18
18
|
sprae.use({ compile: expr => sprae.constructor(`with (arguments[0]) { return ${expr} };`) })
|
|
19
19
|
|
|
20
|
+
export * from './store.js'
|
|
21
|
+
export * from './signal.js'
|
|
22
|
+
|
|
20
23
|
export default sprae
|
package/store.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// signals-based proxy
|
|
2
2
|
import { signal, computed, batch } from './signal.js'
|
|
3
|
+
import { parse } from './core.js';
|
|
3
4
|
|
|
4
|
-
export const _signals = Symbol('signals'),
|
|
5
|
+
export const _signals = Symbol('signals'),
|
|
6
|
+
_change = Symbol('change'),
|
|
7
|
+
_stash = '__',
|
|
5
8
|
|
|
6
9
|
// object store is not lazy
|
|
7
10
|
store = (values, parent) => {
|
|
@@ -13,31 +16,33 @@ export const _signals = Symbol('signals'), _change = Symbol('change'),
|
|
|
13
16
|
// non-objects: for array redirect to list
|
|
14
17
|
if (values.constructor !== Object) return Array.isArray(values) ? list(values) : values
|
|
15
18
|
|
|
16
|
-
//
|
|
17
|
-
let signals =
|
|
19
|
+
// we must inherit signals to allow dynamic extend of parent state
|
|
20
|
+
let signals = Object.create(parent?.[_signals] || {}),
|
|
21
|
+
_len = signal(Object.keys(values).length),
|
|
22
|
+
stash
|
|
18
23
|
|
|
19
24
|
// proxy conducts prop access to signals
|
|
20
|
-
|
|
21
|
-
get: (_,
|
|
22
|
-
set: (_,
|
|
23
|
-
deleteProperty: (_,
|
|
25
|
+
let state = new Proxy(signals, {
|
|
26
|
+
get: (_, k) => k === _change ? _len : k === _signals ? signals : k === _stash ? stash : k in signals ? signals[k]?.valueOf() : globalThis[k],
|
|
27
|
+
set: (_, k, v, s) => k === _stash ? (stash = v, 1) : (s = k in signals, set(signals, k, v), s || ++_len.value), // bump length for new signal
|
|
28
|
+
deleteProperty: (_, k) => (signals[k] && (signals[k][Symbol.dispose]?.(), delete signals[k], _len.value--), 1),
|
|
24
29
|
// subscribe to length when object is spread
|
|
25
30
|
ownKeys: () => (_len.value, Reflect.ownKeys(signals)),
|
|
31
|
+
has: _ => true // sandbox prevents writing to global
|
|
26
32
|
}),
|
|
27
33
|
|
|
28
34
|
// init signals for values
|
|
29
|
-
descs = Object.getOwnPropertyDescriptors(values)
|
|
30
|
-
desc
|
|
35
|
+
descs = Object.getOwnPropertyDescriptors(values)
|
|
31
36
|
|
|
32
|
-
for (let
|
|
37
|
+
for (let k in values) {
|
|
33
38
|
// getter turns into computed
|
|
34
|
-
if (
|
|
39
|
+
if (descs[k]?.get)
|
|
35
40
|
// stash setter
|
|
36
|
-
(signals[
|
|
41
|
+
(signals[k] = computed(descs[k].get.bind(state)))._set = descs[k].set?.bind(state);
|
|
37
42
|
|
|
38
43
|
else
|
|
39
44
|
// init blank signal - make sure we don't take prototype one
|
|
40
|
-
signals[
|
|
45
|
+
signals[k] = null, set(signals, k, values[k]);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
return state
|
|
@@ -56,40 +61,39 @@ export const _signals = Symbol('signals'), _change = Symbol('change'),
|
|
|
56
61
|
|
|
57
62
|
// proxy conducts prop access to signals
|
|
58
63
|
state = new Proxy(signals, {
|
|
59
|
-
get(_,
|
|
64
|
+
get(_, k) {
|
|
60
65
|
// covers Symbol.isConcatSpreadable etc.
|
|
61
|
-
if (typeof
|
|
66
|
+
if (typeof k === 'symbol') return k === _change ? _len : k === _signals ? signals : signals[k]
|
|
62
67
|
|
|
63
68
|
// if .length is read within .push/etc - peek signal to avoid recursive subscription
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
lastProp = key;
|
|
69
|
+
if (k === 'length') return mut.includes(lastProp) ? _len.peek() : _len.value;
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
lastProp = k;
|
|
69
72
|
|
|
70
|
-
//
|
|
71
|
-
if
|
|
73
|
+
// create signal (lazy)
|
|
74
|
+
// NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
|
|
75
|
+
return (signals[k] ?? (signals[k] = signal(store(values[k])))).valueOf()
|
|
72
76
|
},
|
|
73
77
|
|
|
74
|
-
set(_,
|
|
78
|
+
set(_, k, v) {
|
|
75
79
|
// .length
|
|
76
|
-
if (
|
|
80
|
+
if (k === 'length') {
|
|
77
81
|
// force cleaning up tail
|
|
78
82
|
for (let i = v; i < signals.length; i++) delete state[i]
|
|
79
83
|
// .length = N directly
|
|
80
84
|
_len.value = signals.length = v;
|
|
81
85
|
}
|
|
82
86
|
else {
|
|
83
|
-
set(signals,
|
|
87
|
+
set(signals, k, v)
|
|
84
88
|
|
|
85
89
|
// force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
|
|
86
|
-
if (
|
|
90
|
+
if (k >= _len.peek()) _len.value = signals.length = +k + 1
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
return 1
|
|
90
94
|
},
|
|
91
95
|
|
|
92
|
-
deleteProperty: (_,
|
|
96
|
+
deleteProperty: (_, k) => (signals[k]?.[Symbol.dispose]?.(), delete signals[k], 1),
|
|
93
97
|
})
|
|
94
98
|
|
|
95
99
|
return state
|
|
@@ -99,13 +103,13 @@ export const _signals = Symbol('signals'), _change = Symbol('change'),
|
|
|
99
103
|
const mut = ['push', 'pop', 'shift', 'unshift', 'splice']
|
|
100
104
|
|
|
101
105
|
// set/update signal value
|
|
102
|
-
const set = (signals,
|
|
103
|
-
let s = signals[
|
|
106
|
+
const set = (signals, k, v) => {
|
|
107
|
+
let s = signals[k], cur
|
|
104
108
|
|
|
105
109
|
// untracked
|
|
106
|
-
if (
|
|
110
|
+
if (k[0] === '_') signals[k] = v
|
|
107
111
|
// new property. preserve signal value as is
|
|
108
|
-
else if (!s) signals[
|
|
112
|
+
else if (!s) signals[k] = s = v?.peek ? v : signal(store(v))
|
|
109
113
|
// skip unchanged (although can be handled by last condition - we skip a few checks this way)
|
|
110
114
|
else if (v === (cur = s.peek()));
|
|
111
115
|
// stashed _set for value with getter/setter
|
|
@@ -123,4 +127,16 @@ const set = (signals, key, v) => {
|
|
|
123
127
|
else s.value = store(v)
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
// create expression setter, reflecting value back to state
|
|
131
|
+
export const setter = (expr, set = parse(`${expr}=${_stash}`)) => (
|
|
132
|
+
(state, value) => (
|
|
133
|
+
state[_stash] = value, // save value to stash
|
|
134
|
+
set(state)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// make sure state contains first element of path, eg. `a` from `a.b[c]`
|
|
139
|
+
// NOTE: we don't need since we force proxy sandbox
|
|
140
|
+
// export const ensure = (state, expr, name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => name && (state[_signals][name[0]] ??= null)
|
|
141
|
+
|
|
126
142
|
export default store
|