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 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 { signal, effect, batch, computed, untracked } = signals;
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 update = (directive[name] || directive.default)(el, attr.value, state, name);
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
- export let compile = (expr, dir, evaluate) => {
97
- if (evaluate = evalMemo[expr = expr.trim()]) return evaluate
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
- // evaluate = new Function(`__scope`, `with (__scope) { return ${expr} };`);
102
- evaluate = justin(expr);
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] = evaluate
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 = swapdom
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/compiler/differ
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, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
  import { attr, dashcase } from './default.js'
3
3
 
4
- directive['aria'] = (el, expr, state) => {
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
  }
@@ -1,7 +1,6 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
- directive.class = (el, expr, state) => {
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, compile } from "../core.js";
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];
@@ -1,15 +1,14 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
3
  // set generic property directive
4
- directive.default = (el, expr, state, name) => {
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, compile, swap } from "../core.js";
1
+ import sprae, { directive, swap } from "../core.js";
2
2
 
3
3
  export const _each = Symbol(":each");
4
4
 
5
- const keys = {}; // boxed primitives pool
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, expr, state, name) => {
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
- const evaluate = compile(itemsExpr, name);
17
- const memo = new WeakMap;
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
- const count = new WeakSet
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
- if (key == null) el = tpl.cloneNode(true)
37
- else {
38
- // make sure key is object
39
- if (Object(key) !== key) key = (keys[key] ||= Object(key));
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
- if (count.has(key)) {
42
- console.warn('Duplicate key', key), el = tpl.cloneNode(true);
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
- if (el.content) el = el.content.cloneNode(true) // <template>
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(holder.parentNode, cur, cur = els, holder);
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
@@ -1,6 +1,5 @@
1
- import { directive, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
 
3
- directive.fx = (el, expr, state, name) => {
4
- let evaluate = compile(expr, name);
3
+ directive.fx = (el, evaluate, state) => {
5
4
  return () => evaluate(state);
6
5
  };
package/directive/html.js CHANGED
@@ -1,7 +1,7 @@
1
- import sprae, { directive, compile } from "../core.js";
1
+ import sprae, { directive } from "../core.js";
2
2
 
3
- directive.html = (el, expr, state, name) => {
4
- let evaluate = compile(expr, name), tpl = evaluate(state);
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, { compile, directive, swap } from "../core.js";
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, expr, state, name) => {
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
@@ -1,9 +1,8 @@
1
- import sprae, { directive, compile } from "../core.js";
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, expr, rootState, name) => {
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?.() || {}) });
@@ -1,7 +1,6 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
- directive.style = (el, expr, state) => {
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, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
 
3
3
  // set text content
4
- directive.text = (el, expr, state) => {
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 () => {
@@ -1,10 +1,8 @@
1
- import { directive, compile } from "../core.js";
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, expr, state) => {
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.1",
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
- "subscript": "^8.3.4",
16
- "swapdom": "^1.1.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_-based reactivity.<br/>
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
- #### `:*="value"`, `:="values"`
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*="handler"`
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
- ## Expressions
282
+ sprae(el, { name: signal('Kitty') });
283
+ ```
270
284
 
271
- Expressions use [_justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin), a minimal JS subset. It avoids "unsafe-eval" CSP and provides sandboxing. Also it's _fast_.
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
- ## Signals
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` and `sprae/directive/*`:
321
+ Sprae build can be tailored to project needs via `sprae/core`:
303
322
 
304
323
  ```js
305
- import sprae, { directive, compile } from 'sprae/core.js'
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, expr, state) => {
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 differ uses [swapdom](https://github.com/dy/swapdom), can be reconfigured to [list-difference](https://github.com/paldepind/list-difference/), [udomdiff](https://github.com/WebReflection/udomdiff), [domdiff](https://github.com/WebReflection/domdiff), [etc](https://github.com/luwes/js-diff-benchmark):
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
- export * from './core.js'
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'