sprae 11.2.0 → 11.2.2

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  > DOM tree microhydration
4
4
 
5
5
  _Sprae_ is open & minimalistic progressive enhancement framework with _preact-signals_ based reactivity.<br/>
6
- Perfect for small-scale websites, static pages, landings, prototypes, lightweight UI or as [nextjs / SSR companion](#JSX).<br/>
6
+ Perfect for small-scale websites, static pages, landings, prototypes, lightweight UI or as [SSR companion](#JSX).<br/>
7
7
  A light and fast alternative to _alpine_, _petite-vue_ etc (see [why](#justification)).
8
8
 
9
9
  ## Usage
@@ -366,7 +366,6 @@ sprae.use({ compile })
366
366
  * `key` is not used, `:each` uses direct list mapping instead of DOM diffing.
367
367
  * `await` is not supported in attributes, it’s a strong indicator you need to put these methods into state.
368
368
  * `:ref` comes after `:if` for mount/unmount events `<div :if="cond" :ref="(init(), ()=>dispose())"></div>`.
369
- * Directives be `s-` prefixed instead of `:` for JSX.
370
369
 
371
370
  ## Justification
372
371
 
@@ -457,21 +456,19 @@ npm run results
457
456
 
458
457
  ## JSX
459
458
 
460
- Sprae works with JSX, eg. Next.js companion for SSR via `s-` prefixed attributes.
459
+ Sprae works with JSX via `s-` attributes, eg. Next.js companion for SSR.
461
460
 
462
- Next.js server components fall short for dynamic UI, like active nav items, collapsible sections, tabs etc. That forces into client components, which screws up data fetching, bloats hydration and adds overhead.`<Script>` is heavy and clunky hack.
463
-
464
- Sprae can offload UI logic to keep server components intact.
461
+ Next.js server components fail at dynamic UI, like active nav, tabs, sliders etc. That forces into client components, which screws up data fetching, bloats hydration and adds overhead.`<Script>` is heavy and clunky hack. Sprae can offload UI logic to keep server components intact.
465
462
 
466
463
  ```jsx
467
- // app/page.jsx
464
+ // app/page.jsx - server component
468
465
  export default function Page() {
469
466
  return <>
470
467
  <nav id="nav">
471
468
  <a href="/" s-class="location.path === '/' && 'active'">Home</a>
472
469
  <a href="/about" s-class="location.path === '/about' && 'active'">About</a>
473
470
  </nav>
474
- <script src="https://unpkg.com/sprae" init></script>
471
+ ...
475
472
  </>
476
473
  }
477
474
  ```
package/signal.js CHANGED
@@ -40,14 +40,13 @@ export let signal = (v, s, obs = new Set) => (
40
40
  c
41
41
  ),
42
42
  batch = fn => fn(),
43
- untracked = batch
43
+ untracked = batch,
44
44
 
45
- // signals adapter - allows switching signals implementation and not depend on core
46
-
47
- export function use(s) {
48
- signal = s.signal
49
- effect = s.effect
50
- computed = s.computed
51
- batch = s.batch || batch
52
- untracked = s.untracked || batch
53
- }
45
+ // signals adapter - allows switching signals implementation and not depend on core
46
+ use = (s) => (
47
+ signal = s.signal,
48
+ effect = s.effect,
49
+ computed = s.computed,
50
+ batch = s.batch || batch,
51
+ untracked = s.untracked || untracked
52
+ )
package/store.js CHANGED
@@ -1,111 +1,111 @@
1
1
  // signals-based proxy
2
2
  import { signal, computed, batch } from './signal.js'
3
3
 
4
- export const _signals = Symbol('signals'), _change = Symbol('change');
4
+ export const _signals = Symbol('signals'), _change = Symbol('change'),
5
5
 
6
- // object store is not lazy
7
- export default function store(values, parent) {
8
- if (!values) return values
6
+ // object store is not lazy
7
+ store = (values, parent) => {
8
+ if (!values) return values
9
9
 
10
- // ignore existing state as argument
11
- if (values[_signals]) return values;
10
+ // ignore existing state as argument
11
+ if (values[_signals]) return values;
12
12
 
13
- // redirect for optimized array store
14
- if (Array.isArray(values)) return list(values)
13
+ // redirect for optimized array store
14
+ if (Array.isArray(values)) return list(values)
15
15
 
16
- // ignore non-objects or custom objects
17
- if (values.constructor !== Object || values[Symbol.toStringTag]) return values;
16
+ // ignore non-objects or custom objects
17
+ if (values.constructor !== Object || values[Symbol.toStringTag]) return values;
18
18
 
19
- // NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
20
- let signals = { ...parent?.[_signals] }, _len = signal(Object.values(values).length)
19
+ // NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
20
+ let signals = { ...parent?.[_signals] }, _len = signal(Object.values(values).length)
21
21
 
22
- // proxy conducts prop access to signals
23
- const state = new Proxy(signals, {
24
- get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
25
- set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s ?? (++_len.value), 1), // bump length for new signal
26
- deleteProperty: (_, key) => (signals[key] && (signals[key][Symbol.dispose]?.(), delete signals[key], _len.value--), 1),
27
- // subscribe to length when object is spread
28
- ownKeys: () => (_len.value, Reflect.ownKeys(signals)),
29
- })
22
+ // proxy conducts prop access to signals
23
+ const state = new Proxy(signals, {
24
+ get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
25
+ set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s ?? (++_len.value), 1), // bump length for new signal
26
+ deleteProperty: (_, key) => (signals[key] && (signals[key][Symbol.dispose]?.(), delete signals[key], _len.value--), 1),
27
+ // subscribe to length when object is spread
28
+ ownKeys: () => (_len.value, Reflect.ownKeys(signals)),
29
+ })
30
30
 
31
- // init signals for values
32
- for (let key in values) {
33
- const desc = Object.getOwnPropertyDescriptor(values, key)
31
+ // init signals for values
32
+ for (let key in values) {
33
+ const desc = Object.getOwnPropertyDescriptor(values, key)
34
34
 
35
- // getter turns into computed
36
- if (desc?.get) {
37
- // stash setter
38
- (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
39
- }
40
- else {
41
- // init blank signal - make sure we don't take prototype one
42
- signals[key] = null
43
- set(signals, key, values[key]);
35
+ // getter turns into computed
36
+ if (desc?.get) {
37
+ // stash setter
38
+ (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
39
+ }
40
+ else {
41
+ // init blank signal - make sure we don't take prototype one
42
+ signals[key] = null
43
+ set(signals, key, values[key]);
44
+ }
44
45
  }
45
- }
46
46
 
47
- return state
48
- }
47
+ return state
48
+ },
49
49
 
50
- // length changing methods
51
- const mut = ['push', 'pop', 'shift', 'unshift', 'splice']
50
+ // array store - signals are lazy since arrays can be very large & expensive
51
+ list = values => {
52
+ // track last accessed property to find out if .length was directly accessed from expression or via .push/etc method
53
+ let lastProp
52
54
 
53
- // array store - signals are lazy since arrays can be very large & expensive
54
- export function list(values) {
55
- // track last accessed property to find out if .length was directly accessed from expression or via .push/etc method
56
- let lastProp
55
+ // ignore existing state as argument
56
+ if (values[_signals]) return values;
57
57
 
58
- // ignore existing state as argument
59
- if (values[_signals]) return values;
58
+ // .length signal is stored separately, since it cannot be replaced on array
59
+ let _len = signal(values.length),
60
+ // gotta fill with null since proto methods like .reduce may fail
61
+ signals = Array(values.length).fill();
60
62
 
61
- // .length signal is stored separately, since it cannot be replaced on array
62
- let _len = signal(values.length),
63
- // gotta fill with null since proto methods like .reduce may fail
64
- signals = Array(values.length).fill();
63
+ // proxy conducts prop access to signals
64
+ const state = new Proxy(signals, {
65
+ get(_, key) {
66
+ // covers Symbol.isConcatSpreadable etc.
67
+ if (typeof key === 'symbol') return key === _change ? _len : key === _signals ? signals : signals[key]
65
68
 
66
- // proxy conducts prop access to signals
67
- const state = new Proxy(signals, {
68
- get(_, key) {
69
- // covers Symbol.isConcatSpreadable etc.
70
- if (typeof key === 'symbol') return key === _change ? _len : key === _signals ? signals : signals[key]
69
+ // if .length is read within .push/etc - peek signal to avoid recursive subscription
70
+ if (key === 'length') return mut.includes(lastProp) ? _len.peek() : _len.value;
71
71
 
72
- // if .length is read within .push/etc - peek signal to avoid recursive subscription
73
- if (key === 'length') return mut.includes(lastProp) ? _len.peek() : _len.value;
72
+ lastProp = key;
74
73
 
75
- lastProp = key;
74
+ if (signals[key]) return signals[key].valueOf()
76
75
 
77
- if (signals[key]) return signals[key].valueOf()
76
+ // I hope reading values here won't diverge from signals
77
+ if (key < signals.length) return (signals[key] = signal(store(values[key]))).value
78
+ },
78
79
 
79
- // I hope reading values here won't diverge from signals
80
- if (key < signals.length) return (signals[key] = signal(store(values[key]))).value
81
- },
80
+ set(_, key, v) {
81
+ // .length
82
+ if (key === 'length') {
83
+ // force cleaning up tail
84
+ for (let i = v; i < signals.length; i++) delete state[i]
85
+ // .length = N directly
86
+ _len.value = signals.length = v;
87
+ return true
88
+ }
82
89
 
83
- set(_, key, v) {
84
- // .length
85
- if (key === 'length') {
86
- // force cleaning up tail
87
- for (let i = v; i < signals.length; i++) delete state[i]
88
- // .length = N directly
89
- _len.value = signals.length = v;
90
- return true
91
- }
90
+ set(signals, key, v)
92
91
 
93
- set(signals, key, v)
92
+ // force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
93
+ if (key >= _len.peek()) _len.value = signals.length = +key + 1
94
94
 
95
- // force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
96
- if (key >= _len.peek()) _len.value = signals.length = +key + 1
95
+ return true
96
+ },
97
97
 
98
- return true
99
- },
98
+ deleteProperty: (_, key) => (signals[key]?.[Symbol.dispose]?.(), delete signals[key], 1),
99
+ })
100
100
 
101
- deleteProperty: (_, key) => (signals[key]?.[Symbol.dispose]?.(), delete signals[key], 1),
102
- })
101
+ return state
102
+ }
103
103
 
104
- return state
105
- }
104
+ // length changing methods
105
+ const mut = ['push', 'pop', 'shift', 'unshift', 'splice']
106
106
 
107
107
  // set/update signal value
108
- function set(signals, key, v) {
108
+ const set = (signals, key, v) => {
109
109
  let s = signals[key]
110
110
 
111
111
  // untracked
@@ -129,3 +129,5 @@ function set(signals, key, v) {
129
129
  // .x = y
130
130
  else s.value = store(v)
131
131
  }
132
+
133
+ export default store