sprae 9.0.1 → 9.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/core.js +18 -16
- package/directive/aria.js +2 -3
- package/directive/class.js +2 -3
- package/directive/data.js +2 -4
- package/directive/default.js +3 -4
- package/directive/each.js +39 -39
- package/directive/fx.js +2 -3
- package/directive/html.js +3 -3
- package/directive/if.js +2 -4
- package/directive/ref.js +2 -2
- package/directive/scope.js +2 -3
- package/directive/style.js +2 -3
- package/directive/text.js +2 -3
- package/directive/value.js +2 -4
- package/package.json +5 -4
- package/readme.md +49 -42
- package/sprae.js +17 -2
package/core.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import swapdom from 'swapdom'
|
|
2
|
-
import * as signals from 'ulive'
|
|
3
|
-
import justin from 'subscript/justin'
|
|
4
|
-
|
|
5
1
|
// polyfill
|
|
6
2
|
const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
7
3
|
|
|
@@ -9,7 +5,7 @@ const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
|
9
5
|
const SPRAE = `∴`
|
|
10
6
|
|
|
11
7
|
// signals impl
|
|
12
|
-
export let
|
|
8
|
+
export let signal, effect, batch, computed, untracked
|
|
13
9
|
|
|
14
10
|
// reserved directives - order matters!
|
|
15
11
|
export const directive = {};
|
|
@@ -49,7 +45,9 @@ export default function sprae(container, values) {
|
|
|
49
45
|
|
|
50
46
|
// NOTE: secondary directives don't stop flow nor extend state, so no need to check
|
|
51
47
|
for (let name of names) {
|
|
52
|
-
let
|
|
48
|
+
let dir = directive[name] || directive.default
|
|
49
|
+
let evaluate = (dir.parse || parse)(attr.value, parse)
|
|
50
|
+
let update = dir(el, evaluate, state, name);
|
|
53
51
|
if (update) {
|
|
54
52
|
update[_dispose] = effect(update);
|
|
55
53
|
effects.push(update);
|
|
@@ -83,6 +81,7 @@ export default function sprae(container, values) {
|
|
|
83
81
|
while (effects.length) effects.pop()[_dispose]();
|
|
84
82
|
container.classList.remove(SPRAE)
|
|
85
83
|
memo.delete(container);
|
|
84
|
+
// NOTE: each child disposes own children etc.
|
|
86
85
|
let els = container.getElementsByClassName(SPRAE);
|
|
87
86
|
while (els.length) els[0][_dispose]?.()
|
|
88
87
|
}
|
|
@@ -93,29 +92,31 @@ export default function sprae(container, values) {
|
|
|
93
92
|
// default compiler
|
|
94
93
|
const evalMemo = {};
|
|
95
94
|
|
|
96
|
-
|
|
97
|
-
if (
|
|
95
|
+
const parse = (expr, dir, fn) => {
|
|
96
|
+
if (fn = evalMemo[expr = expr.trim()]) return fn
|
|
98
97
|
|
|
99
98
|
// static-time errors
|
|
100
|
-
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
catch (e) { throw Object.assign(e, { message: `${SPRAE} ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
|
|
99
|
+
try { fn = compile(expr); }
|
|
100
|
+
catch (e) { throw Object.assign(e, { message: `∴ ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
|
|
101
|
+
|
|
102
|
+
fn.expr = expr
|
|
105
103
|
|
|
106
104
|
// runtime errors
|
|
107
|
-
return evalMemo[expr] =
|
|
105
|
+
return evalMemo[expr] = fn
|
|
108
106
|
}
|
|
109
107
|
|
|
108
|
+
// default compiler is simple new Function (tiny obfuscation against direct new Function detection)
|
|
109
|
+
export let compile
|
|
110
|
+
|
|
110
111
|
// DOM swapper
|
|
111
|
-
export let swap
|
|
112
|
+
export let swap
|
|
112
113
|
|
|
113
114
|
// interpolate a$<b> fields from context
|
|
114
115
|
export const ipol = (v, state) => {
|
|
115
116
|
return v?.replace ? v.replace(/\$<([^>]+)>/g, (match, field) => state[field]?.valueOf?.() ?? '') : v
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
// configure signals/
|
|
119
|
+
// configure signals/compile/differ
|
|
119
120
|
// it's more compact than using sprae.signal = signal etc.
|
|
120
121
|
sprae.use = s => {
|
|
121
122
|
s.signal && (
|
|
@@ -126,4 +127,5 @@ sprae.use = s => {
|
|
|
126
127
|
untracked = s.untracked || batch
|
|
127
128
|
);
|
|
128
129
|
s.swap && (swap = s.swap)
|
|
130
|
+
s.compile && (compile = s.compile)
|
|
129
131
|
}
|
package/directive/aria.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
import { attr, dashcase } from './default.js'
|
|
3
3
|
|
|
4
|
-
directive['aria'] = (el,
|
|
5
|
-
let evaluate = compile(expr, 'aria')
|
|
4
|
+
directive['aria'] = (el, evaluate, state) => {
|
|
6
5
|
const update = (value) => {
|
|
7
6
|
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
8
7
|
}
|
package/directive/class.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { directive,
|
|
1
|
+
import { directive, ipol } from "../core.js";
|
|
2
2
|
|
|
3
|
-
directive.class = (el,
|
|
4
|
-
let evaluate = compile(expr, 'class');
|
|
3
|
+
directive.class = (el, evaluate, state) => {
|
|
5
4
|
let cur = new Set
|
|
6
5
|
return () => {
|
|
7
6
|
let v = evaluate(state);
|
package/directive/data.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { directive
|
|
2
|
-
|
|
3
|
-
directive['data'] = (el, expr, state) => {
|
|
4
|
-
let evaluate = compile(expr, 'data')
|
|
1
|
+
import { directive } from "../core.js";
|
|
5
2
|
|
|
3
|
+
directive['data'] = (el, evaluate, state) => {
|
|
6
4
|
return () => {
|
|
7
5
|
let value = evaluate(state)?.valueOf()
|
|
8
6
|
for (let key in value) el.dataset[key] = value[key];
|
package/directive/default.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { directive,
|
|
1
|
+
import { directive, ipol } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// set generic property directive
|
|
4
|
-
directive.default = (el,
|
|
4
|
+
directive.default = (el, evaluate, state, name) => {
|
|
5
5
|
let evt = name.startsWith("on") && name.slice(2);
|
|
6
|
-
let evaluate = compile(expr, name);
|
|
7
6
|
|
|
8
7
|
if (evt) {
|
|
9
8
|
let off
|
|
10
9
|
return () => (
|
|
11
10
|
off?.(), // intermediate teardown
|
|
12
|
-
off = on(el, evt, evaluate(state))
|
|
11
|
+
off = on(el, evt, evaluate(state)?.valueOf())
|
|
13
12
|
);
|
|
14
13
|
}
|
|
15
14
|
|
package/directive/each.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import sprae, { directive,
|
|
1
|
+
import sprae, { directive, swap } from "../core.js";
|
|
2
2
|
|
|
3
3
|
export const _each = Symbol(":each");
|
|
4
4
|
|
|
5
|
-
const keys = {}
|
|
5
|
+
const keys = {}, _key = Symbol('key');
|
|
6
6
|
|
|
7
7
|
// :each must init before :ref, :id or any others, since it defines scope
|
|
8
|
-
directive.each = (tpl,
|
|
9
|
-
let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
|
|
10
|
-
let [itemVar, idxVar = "_$"] = leftSide.split(/\s*,\s*/);
|
|
11
|
-
|
|
8
|
+
(directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
|
|
12
9
|
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
13
|
-
const holder = (tpl[_each] = document.createTextNode(""));
|
|
10
|
+
const holder = (tpl[_each] = document.createTextNode("")), parent = tpl.parentNode;
|
|
14
11
|
tpl.replaceWith(holder);
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
tpl.removeAttribute(':key')
|
|
13
|
+
// key -> el
|
|
14
|
+
const elCache = new WeakMap, stateCache = new WeakMap
|
|
20
15
|
|
|
21
16
|
let cur = [];
|
|
22
17
|
|
|
18
|
+
const remove = el => {
|
|
19
|
+
el.remove()
|
|
20
|
+
el[Symbol.dispose]?.()
|
|
21
|
+
if (el[_key]) {
|
|
22
|
+
elCache.delete(el[_key])
|
|
23
|
+
stateCache.delete(el[_key])
|
|
24
|
+
}
|
|
25
|
+
}, { insert, replace } = swap
|
|
26
|
+
|
|
27
|
+
const options = { remove, insert, replace }
|
|
28
|
+
|
|
29
|
+
// naive approach: whenever items change we replace full list
|
|
23
30
|
return () => {
|
|
24
|
-
// naive approach: whenever items change we replace full list
|
|
25
31
|
let items = evaluate(state)?.valueOf(), els = [];
|
|
26
|
-
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i);
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
for (let idx in items) {
|
|
30
|
-
let item = items[idx]
|
|
31
|
-
// creating via prototype is faster in both creation time & reading time
|
|
32
|
-
let substate = Object.create(state, { [idxVar]: { value: idx } });
|
|
33
|
-
substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
|
|
34
|
-
let el, key = (item.key ?? item.id ?? item); // NOTE: no need to unwrap singnal, since item fallback covers it
|
|
33
|
+
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i)
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// let c = 0, inc = () => { if (c++ > 100) throw 'Inf recursion' }
|
|
36
|
+
const count = new WeakMap
|
|
37
|
+
for (let idx in items) {
|
|
38
|
+
let el, item = items[idx], key = item?.key ?? item?.id ?? item ?? idx
|
|
39
|
+
key = (Object(key) !== key) ? (keys[key] ||= Object(key)) : item
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
count.add(key);
|
|
46
|
-
el = memo.get(key) || memo.set(key, tpl.cloneNode(true)).get(key);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
41
|
+
if (key == null || count.has(key) || tpl.content) el = (tpl.content || tpl).cloneNode(true)
|
|
42
|
+
else count.set(key, 1), (el = elCache.get(key) || (elCache.set(key, tpl.cloneNode(true)), elCache.get(key)))[_key] = key;
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
// creating via prototype is faster in both creation time & reading time
|
|
45
|
+
let substate = stateCache.get(key) || (stateCache.set(key, Object.create(state, { [idxVar]: { value: idx } })), stateCache.get(key));
|
|
46
|
+
substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
|
|
51
47
|
|
|
52
|
-
sprae(el, substate)
|
|
48
|
+
sprae(el, substate);
|
|
53
49
|
|
|
54
50
|
// document fragment
|
|
55
|
-
if (el.nodeType === 11) els.push(...el.childNodes);
|
|
56
|
-
else els.push(el);
|
|
51
|
+
if (el.nodeType === 11) els.push(...el.childNodes); else els.push(el);
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
swap(
|
|
60
|
-
}
|
|
61
|
-
}
|
|
54
|
+
swap(parent, cur, cur = els, holder, options);
|
|
55
|
+
}
|
|
56
|
+
}).parse = (expr, parse) => {
|
|
57
|
+
let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
|
|
58
|
+
let [itemVar, idxVar = "$"] = leftSide.split(/\s*,\s*/);
|
|
59
|
+
|
|
60
|
+
return [itemVar, idxVar, parse(itemsExpr)]
|
|
61
|
+
}
|
package/directive/fx.js
CHANGED
package/directive/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import sprae, { directive
|
|
1
|
+
import sprae, { directive } from "../core.js";
|
|
2
2
|
|
|
3
|
-
directive.html = (el,
|
|
4
|
-
let
|
|
3
|
+
directive.html = (el, evaluate, state) => {
|
|
4
|
+
let tpl = evaluate(state);
|
|
5
5
|
|
|
6
6
|
if (!tpl) return
|
|
7
7
|
|
package/directive/if.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import sprae, {
|
|
1
|
+
import sprae, { directive, swap } from "../core.js";
|
|
2
2
|
import { _each } from './each.js';
|
|
3
3
|
|
|
4
4
|
// :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
|
|
5
5
|
// as for :if :scope - :if must init first, since it is lazy, to avoid initializing component ahead of time by :scope
|
|
6
6
|
// we consider :scope={x} :if={x} case insignificant
|
|
7
7
|
const _prevIf = Symbol("if");
|
|
8
|
-
directive.if = (ifEl,
|
|
8
|
+
directive.if = (ifEl, evaluate, state) => {
|
|
9
9
|
let parent = ifEl.parentNode,
|
|
10
10
|
next = ifEl.nextElementSibling,
|
|
11
11
|
holder = document.createTextNode(''),
|
|
12
12
|
|
|
13
|
-
evaluate = compile(expr, name),
|
|
14
|
-
|
|
15
13
|
// actual replaceable els (takes <template>)
|
|
16
14
|
cur, ifs, elses, none = [];
|
|
17
15
|
|
package/directive/ref.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { directive, ipol } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// ref must be last within primaries, since that must be skipped by :each, but before secondaries
|
|
4
|
-
directive.ref = (el, expr, state) => {
|
|
4
|
+
(directive.ref = (el, expr, state) => {
|
|
5
5
|
let prev;
|
|
6
6
|
return () => {
|
|
7
7
|
if (prev) delete state[prev]
|
|
8
8
|
state[prev = ipol(expr, state)] = el;
|
|
9
9
|
}
|
|
10
|
-
}
|
|
10
|
+
}).parse = expr => expr
|
package/directive/scope.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import sprae, { directive
|
|
1
|
+
import sprae, { directive } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
4
4
|
// same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
|
|
5
|
-
directive.scope = (el,
|
|
6
|
-
let evaluate = compile(expr, name);
|
|
5
|
+
directive.scope = (el, evaluate, rootState) => {
|
|
7
6
|
// local state may contain signals that update, so we take them over
|
|
8
7
|
return () => {
|
|
9
8
|
sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
|
package/directive/style.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { directive,
|
|
1
|
+
import { directive, ipol } from "../core.js";
|
|
2
2
|
|
|
3
|
-
directive.style = (el,
|
|
4
|
-
let evaluate = compile(expr, 'style');
|
|
3
|
+
directive.style = (el, evaluate, state) => {
|
|
5
4
|
let initStyle = el.getAttribute("style") || "";
|
|
6
5
|
if (!initStyle.endsWith(";")) initStyle += "; ";
|
|
7
6
|
|
package/directive/text.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// set text content
|
|
4
|
-
directive.text = (el,
|
|
5
|
-
let evaluate = compile(expr, 'text');
|
|
4
|
+
directive.text = (el, evaluate, state) => {
|
|
6
5
|
if (el.content) el.replaceWith(el = document.createTextNode('')) // <template :text="abc"/>
|
|
7
6
|
|
|
8
7
|
return () => {
|
package/directive/value.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { directive
|
|
1
|
+
import { directive } from "../core.js";
|
|
2
2
|
import { attr } from './default.js';
|
|
3
3
|
|
|
4
4
|
// connect expr to element value
|
|
5
|
-
directive.value = (el,
|
|
6
|
-
let evaluate = compile(expr, 'value');
|
|
7
|
-
|
|
5
|
+
directive.value = (el, evaluate, state) => {
|
|
8
6
|
let from, to;
|
|
9
7
|
let update = el.type === "text" || el.type === ""
|
|
10
8
|
? (value) => el.setAttribute("value", (el.value = value == null ? "" : value))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "9.0
|
|
4
|
+
"version": "9.1.0",
|
|
5
5
|
"main": "./sprae.js",
|
|
6
6
|
"module": "./sprae.js",
|
|
7
7
|
"type": "module",
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"swapdom": "^1.
|
|
17
|
-
"ulive": "^1.0.1"
|
|
15
|
+
"signal-polyfill": "^0.1.0",
|
|
16
|
+
"swapdom": "^1.2.1"
|
|
18
17
|
},
|
|
19
18
|
"devDependencies": {
|
|
20
19
|
"@preact/signals": "^1.1.3",
|
|
@@ -23,8 +22,10 @@
|
|
|
23
22
|
"esbuild": "^0.15.14",
|
|
24
23
|
"hyperf": "^1.6.2",
|
|
25
24
|
"jsdom": "^21.1.0",
|
|
25
|
+
"subscript": "^8.3.4",
|
|
26
26
|
"terser": "^5.15.1",
|
|
27
27
|
"tst": "^7.1.1",
|
|
28
|
+
"ulive": "^1.0.1",
|
|
28
29
|
"usignal": "^0.9.0",
|
|
29
30
|
"wait-please": "^3.1.0"
|
|
30
31
|
},
|
package/readme.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> DOM tree microhydration
|
|
4
4
|
|
|
5
5
|
_Sprae_ is a compact & ergonomic progressive enhancement framework.<br/>
|
|
6
|
-
It provides `:`-attributes for inline markup logic with _signals_-
|
|
6
|
+
It provides `:`-attributes for inline markup logic with [_signals_](https://github.com/proposal-signals/proposal-signals) reactivity.<br/>
|
|
7
7
|
Perfect for small-scale websites, prototypes, or lightweight UI.<br/>
|
|
8
8
|
|
|
9
9
|
|
|
@@ -121,7 +121,7 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
|
|
|
121
121
|
</select>
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
####
|
|
124
|
+
#### `:[prop]="value"`, `:="values"`
|
|
125
125
|
|
|
126
126
|
Set any attribute(s).
|
|
127
127
|
|
|
@@ -170,7 +170,7 @@ Run effect, not changing any attribute.<br/>Optional cleanup is called in-betwee
|
|
|
170
170
|
<div :fx="id = setInterval(tick, interval), () => clearInterval(tick)" />
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
#### `:on
|
|
173
|
+
#### `:on[event]="handler"`
|
|
174
174
|
|
|
175
175
|
Attach event(s) listener with possible modifiers.
|
|
176
176
|
|
|
@@ -265,10 +265,43 @@ Trigger when element is connected / disconnected from DOM.
|
|
|
265
265
|
```
|
|
266
266
|
-->
|
|
267
267
|
|
|
268
|
+
## Customization
|
|
269
|
+
|
|
270
|
+
_Sprae_ can be reconfigured to use alternative signals provider, expressions evaluator or directives.
|
|
271
|
+
|
|
272
|
+
### Signals
|
|
273
|
+
|
|
274
|
+
Sprae uses [standard signals](https://github.com/proposal-signals/proposal-signals) for reactivity, but can be switched to any preact-flavored signals library:
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
import sprae, { signal, computed, effect, batch, untracked } from 'sprae';
|
|
278
|
+
import * as signals from '@preact/signals-core';
|
|
279
|
+
|
|
280
|
+
sprae.use(signals);
|
|
268
281
|
|
|
269
|
-
|
|
282
|
+
sprae(el, { name: signal('Kitty') });
|
|
283
|
+
```
|
|
270
284
|
|
|
271
|
-
|
|
285
|
+
Provider | Size | Feature
|
|
286
|
+
:---|:---|:---
|
|
287
|
+
[`ulive`](https://ghub.io/ulive) | 350b | Minimal implementation, basic performance, good for small states
|
|
288
|
+
[`@webreflection/signal`](https://ghib.io/@webreflection/signal) | 531b | Class-based, better performance, good for small-medium states
|
|
289
|
+
[`usignal`](https://ghib.io/usignal) | 850b | Class-based with optimizations, good for medium states
|
|
290
|
+
[`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
### Evaluator
|
|
294
|
+
|
|
295
|
+
Expressions use _new Function_ as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP. To make eval stricter & safer, an alternative evaluator can be configured, eg. _justin_:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
import sprae from 'sprae'
|
|
299
|
+
import justin from 'subscript/justin'
|
|
300
|
+
|
|
301
|
+
sprae.use({compile: justin}) // set up justin as default compiler
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset. It avoids "unsafe-eval" CSP and provides sandboxing.
|
|
272
305
|
|
|
273
306
|
###### Operators:
|
|
274
307
|
|
|
@@ -283,42 +316,29 @@ Expressions use [_justin_](https://github.com/dy/subscript?tab=readme-ov-file#ju
|
|
|
283
316
|
`true false null undefined NaN`
|
|
284
317
|
|
|
285
318
|
|
|
286
|
-
##
|
|
287
|
-
|
|
288
|
-
Sprae uses minimal signals based on [`ulive`](https://ghub.io/ulive). It can be switched to [`@preact/signals-core`](https://ghub.io/@preact/signals-core), [`@webreflection/signal`](https://ghib.io/@webreflection/signal), [`usignal`](https://ghib.io/usignal), which are better for complex states:
|
|
289
|
-
|
|
290
|
-
```js
|
|
291
|
-
import sprae, { signal, computed, effect, batch, untracked } from 'sprae';
|
|
292
|
-
import * as signals from '@preact/signals-core';
|
|
293
|
-
|
|
294
|
-
sprae.use(signals);
|
|
295
|
-
|
|
296
|
-
sprae(el, { name: signal('Kitty') });
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
## Customization
|
|
319
|
+
## Directives
|
|
301
320
|
|
|
302
|
-
Sprae build can be tailored to project needs via `sprae/core
|
|
321
|
+
Sprae build can be tailored to project needs via `sprae/core`:
|
|
303
322
|
|
|
304
323
|
```js
|
|
305
|
-
import sprae, { directive
|
|
324
|
+
import sprae, { directive } from 'sprae/core.js'
|
|
306
325
|
|
|
307
326
|
// include directives
|
|
308
|
-
import 'sprae/directive/if.js'
|
|
309
|
-
import 'sprae/directive/text.js'
|
|
327
|
+
import 'sprae/directive/if.js'
|
|
328
|
+
import 'sprae/directive/text.js'
|
|
310
329
|
|
|
311
330
|
// define custom directive
|
|
312
|
-
directive.id = (el,
|
|
313
|
-
const evaluate = compile(state, 'id') // expression string -> evaluator
|
|
331
|
+
directive.id = (el, evaluate, state) => {
|
|
314
332
|
return () => el.id = evaluate(state) // return update function
|
|
315
333
|
}
|
|
316
334
|
```
|
|
317
335
|
|
|
336
|
+
See [`sprae.js`](./sprae.js) for example.
|
|
337
|
+
|
|
318
338
|
<!--
|
|
319
339
|
### DOM diffing
|
|
320
340
|
|
|
321
|
-
DOM
|
|
341
|
+
DOM diffing uses [swapdom](https://github.com/dy/swapdom), but can be reconfigured to [list-difference](https://github.com/paldepind/list-difference/), [udomdiff](https://github.com/WebReflection/udomdiff), [domdiff](https://github.com/WebReflection/domdiff), or any other ([benchmark](https://github.com/luwes/js-diff-benchmark)):
|
|
322
342
|
|
|
323
343
|
```js
|
|
324
344
|
import sprae from 'sprae';
|
|
@@ -329,26 +349,13 @@ sprae.use({ swap: domdiff });
|
|
|
329
349
|
```
|
|
330
350
|
-->
|
|
331
351
|
|
|
332
|
-
<!--
|
|
333
|
-
### Custom Build
|
|
334
|
-
|
|
335
|
-
`sprae/core` exports bare-bones engine without directives, which allows tailoring build to project needs:
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
import sprae, { directive, effect } from 'sprae/core'
|
|
339
|
-
|
|
340
|
-
// include required directives
|
|
341
|
-
import 'sprae/directive/if'
|
|
342
|
-
import 'sprae/directive/text'
|
|
343
|
-
```
|
|
344
|
-
-->
|
|
345
|
-
|
|
346
352
|
|
|
347
353
|
<!-- ## Dispose
|
|
348
354
|
|
|
349
355
|
To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
|
|
350
356
|
|
|
351
357
|
|
|
358
|
+
<!--
|
|
352
359
|
## v9 changes
|
|
353
360
|
|
|
354
361
|
* No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
|
|
@@ -361,7 +368,7 @@ To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. --
|
|
|
361
368
|
* Async props / events are not supported, pass async functions via state.
|
|
362
369
|
* Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
|
|
363
370
|
* Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
|
|
364
|
-
|
|
371
|
+
-->
|
|
365
372
|
|
|
366
373
|
## Justification
|
|
367
374
|
|
package/sprae.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export { default } from './core.js'
|
|
1
|
+
import sprae from './core.js'
|
|
3
2
|
|
|
3
|
+
import * as signals from './signal.js'
|
|
4
|
+
import swap from 'swapdom/inflate'
|
|
5
|
+
|
|
6
|
+
// default directives
|
|
4
7
|
import './directive/if.js'
|
|
5
8
|
import './directive/each.js'
|
|
6
9
|
import './directive/ref.js'
|
|
@@ -12,3 +15,15 @@ import './directive/style.js'
|
|
|
12
15
|
import './directive/value.js'
|
|
13
16
|
import './directive/fx.js'
|
|
14
17
|
import './directive/default.js'
|
|
18
|
+
|
|
19
|
+
// default signals
|
|
20
|
+
sprae.use(signals)
|
|
21
|
+
|
|
22
|
+
// default compiler (indirect new Function to avoid detector)
|
|
23
|
+
sprae.use({ compile: expr => sprae.constructor(`__scope`, `with (__scope) { return ${expr} };`) })
|
|
24
|
+
|
|
25
|
+
// defaul dom swapper
|
|
26
|
+
sprae.use({ swap })
|
|
27
|
+
|
|
28
|
+
export default sprae
|
|
29
|
+
export { signal, computed, effect, batch, untracked } from './core.js'
|