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/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_ based reactivity.<br/>
6
- Perfect for small-scale websites, static pages, landings, prototypes, lightweight UI or SSR companion (see [JSX](#JSX)).<br/>
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, as CJS, AMD etc.
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: 's-'
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'), _change = Symbol('change'),
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
- // NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
17
- let signals = { ...parent?.[_signals] }, _len = signal(Object.values(values).length),
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
- state = new Proxy(signals, {
21
- get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
22
- set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s ?? (++_len.value), 1), // bump length for new signal
23
- deleteProperty: (_, key) => (signals[key] && (signals[key][Symbol.dispose]?.(), delete signals[key], _len.value--), 1),
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 key in values) {
37
+ for (let k in values) {
33
38
  // getter turns into computed
34
- if ((desc = descs[key])?.get)
39
+ if (descs[k]?.get)
35
40
  // stash setter
36
- (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
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[key] = null, set(signals, key, values[key]);
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(_, key) {
64
+ get(_, k) {
60
65
  // covers Symbol.isConcatSpreadable etc.
61
- if (typeof key === 'symbol') return key === _change ? _len : key === _signals ? signals : signals[key]
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 (key === 'length') return mut.includes(lastProp) ? _len.peek() : _len.value;
65
-
66
- lastProp = key;
69
+ if (k === 'length') return mut.includes(lastProp) ? _len.peek() : _len.value;
67
70
 
68
- if (signals[key]) return signals[key].valueOf()
71
+ lastProp = k;
69
72
 
70
- // I hope reading values here won't diverge from signals
71
- if (key < signals.length) return (signals[key] = signal(store(values[key]))).value
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(_, key, v) {
78
+ set(_, k, v) {
75
79
  // .length
76
- if (key === 'length') {
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, key, v)
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 (key >= _len.peek()) _len.value = signals.length = +key + 1
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: (_, key) => (signals[key]?.[Symbol.dispose]?.(), delete signals[key], 1),
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, key, v) => {
103
- let s = signals[key], cur
106
+ const set = (signals, k, v) => {
107
+ let s = signals[k], cur
104
108
 
105
109
  // untracked
106
- if (key[0] === '_') signals[key] = v
110
+ if (k[0] === '_') signals[k] = v
107
111
  // new property. preserve signal value as is
108
- else if (!s) signals[key] = s = v?.peek ? v : signal(store(v))
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