sprae 8.1.3 → 9.0.1

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
@@ -1,73 +1,33 @@
1
1
  # ∴ spræ [![tests](https://github.com/dy/sprae/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/sprae/actions/workflows/node.js.yml) [![size](https://img.shields.io/bundlephobia/minzip/sprae?label=size)](https://bundlephobia.com/result?p=sprae) [![npm](https://img.shields.io/npm/v/sprae?color=orange)](https://npmjs.org/sprae)
2
2
 
3
- > DOM tree hydration with reactivity.
3
+ > DOM tree microhydration
4
4
 
5
- _Sprae_ is compact ergonomic progressive enhancement framework.<br/>
6
- It provides reactive `:`-attributes that enable simple markup logic without need for complex scripts.<br/>
7
- Perfect for small-scale websites, prototypes or UI logic.<br/>
8
- It is tiny, performant and open alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue) or [template-parts](https://github.com/github/template-parts).
5
+ _Sprae_ is a compact & ergonomic progressive enhancement framework.<br/>
6
+ It provides `:`-attributes for inline markup logic with _signals_-based reactivity.<br/>
7
+ Perfect for small-scale websites, prototypes, or lightweight UI.<br/>
9
8
 
10
- ## Usage
11
-
12
- ### Autoinit
13
-
14
- To autoinit document, include [`sprae.auto.js`](./sprae.auto.js):
15
-
16
- ```html
17
- <!-- <script src="https://cdn.jsdelivr.net/npm/sprae/dist/sprae.auto.js" defer></script> -->
18
- <script defer src="./path/to/sprae.auto.js"></script>
19
-
20
- <ul>
21
- <li :each="item in ['apple', 'bananas', 'citrus']"">
22
- <a :href="`#${item}`" :text="item" />
23
- </li>
24
- </ul>
25
- ```
26
9
 
27
- ### Manual init
28
-
29
- To init manually as module, import [`sprae.js`](./sprae.js):
10
+ ## Usage
30
11
 
31
12
  ```html
32
13
  <div id="container" :if="user">
33
- Logged in as <span :text="user.name">Guest.</span>
14
+ Hello <span :text="user.name">World</span>.
34
15
  </div>
35
16
 
36
17
  <script type="module">
37
- // import sprae from 'https://cdn.jsdelivr.net/npm/sprae/dist/sprae.js';
38
- import sprae from './path/to/sprae.js';
18
+ import sprae, { signal } from 'sprae'
39
19
 
40
- const state = sprae(container, { user: { name: 'Dmitry Ivanov' } });
41
- state.user.name = 'dy'; // updates DOM
20
+ const name = signal('Kitty')
21
+ sprae(container, { user: { name } }) // init
22
+
23
+ name.value = 'Dolly' // update
42
24
  </script>
43
25
  ```
44
26
 
45
- Sprae evaluates `:`-attributes and evaporates them.<br/>
46
-
47
- ## State
48
-
49
- Sprae creates reactive state that mirrors current DOM values.<br/>
50
- It is based on [signals](https://github.com/preactjs/signals) and can take them as inputs.
51
-
52
- ```js
53
- import { signal } from '@preact/signals-core'
54
-
55
- const version = signal('alpha')
56
-
57
- // Sprae container with initial state values
58
- const state = sprae(container, { foo: 'bar', version })
27
+ Sprae evaluates `:`-directives and evaporates them, attaching state to html.
59
28
 
60
- // Modify state property 'foo', triggering a DOM update
61
- state.foo = 'baz'
62
29
 
63
- // Update the version signal, which also triggers a DOM refresh
64
- version.value = 'beta'
65
-
66
- // For batch update, re-sprae with new state values
67
- sprae(container, { foo: 'qux', version: 'gamma' })
68
- ```
69
-
70
- ## Attributes
30
+ ## Directives
71
31
 
72
32
  #### `:if="condition"`, `:else`
73
33
 
@@ -77,60 +37,73 @@ Control flow of elements.
77
37
  <span :if="foo">foo</span>
78
38
  <span :else :if="bar">bar</span>
79
39
  <span :else>baz</span>
40
+
41
+ <!-- fragment -->
42
+ <template :if="foo">foo <span>bar</span> baz</template>
80
43
  ```
81
44
 
45
+
82
46
  #### `:each="item, index in items"`
83
47
 
84
- Multiply element.
48
+ Multiply element. Item is identified either by `item.id`, `item.key` or `item` itself.
85
49
 
86
50
  ```html
87
- <ul><li :each="item in items" :text="item"></ul>
51
+ <ul><li :each="item in items" :text="item"/></ul>
88
52
 
89
- <!-- Cases -->
53
+ <!-- cases -->
90
54
  <li :each="item, idx in list" />
91
55
  <li :each="val, key in obj" />
92
56
  <li :each="idx in number" />
93
57
 
94
- <!-- Loop by condition -->
58
+ <!-- by condition -->
95
59
  <li :if="items" :each="item in items" :text="item" />
96
60
  <li :else>Empty list</li>
61
+
62
+ <!-- fragment -->
63
+ <template :each="item in items">
64
+ <dt :text="item.term"/>
65
+ <dd :text="item.definition"/>
66
+ </template>
67
+
68
+ <!-- prevent FOUC -->
69
+ <style>[:each] {visibility: hidden}</style>
97
70
  ```
98
71
 
99
72
  #### `:text="value"`
100
73
 
101
- Set text content of an element. Default text can be used as fallback:
74
+ Set text content of an element.
102
75
 
103
76
  ```html
104
77
  Welcome, <span :text="user.name">Guest</span>.
78
+
79
+ <!-- fragment -->
80
+ Welcome, <template :text="user.name" />.
105
81
  ```
106
82
 
107
83
  #### `:class="value"`
108
84
 
109
- Set class value from either a string, array or object.
85
+ Set class value, extends existing `class`.
110
86
 
111
87
  ```html
112
- <!-- set from string -->
113
- <div :class="`foo ${bar}`"></div>
88
+ <!-- string with interpolation -->
89
+ <div :class="'foo $<bar>'"></div>
114
90
 
115
- <!-- extends existing class as "foo bar" -->
116
- <div class="foo" :class="`bar`"></div>
117
-
118
- <!-- clsx: object / list -->
91
+ <!-- array/object a-la clsx -->
119
92
  <div :class="[foo && 'foo', {bar: bar}]"></div>
120
93
  ```
121
94
 
122
95
  #### `:style="value"`
123
96
 
124
- Set style value from an object or a string. Extends existing `style` attribute, if any.
97
+ Set style value, extends existing `style`.
125
98
 
126
99
  ```html
127
- <!-- from string -->
128
- <div :style="`foo: ${bar}`"></div>
100
+ <!-- string with interpolation -->
101
+ <div :style="'foo: $<bar>'"></div>
129
102
 
130
- <!-- from object -->
103
+ <!-- object -->
131
104
  <div :style="{foo: 'bar'}"></div>
132
105
 
133
- <!-- set CSS variable -->
106
+ <!-- CSS variable -->
134
107
  <div :style="{'--baz': qux}"></div>
135
108
  ```
136
109
 
@@ -139,7 +112,6 @@ Set style value from an object or a string. Extends existing `style` attribute,
139
112
  Set value of an input, textarea or select. Takes handle of `checked` and `selected` attributes.
140
113
 
141
114
  ```html
142
- <!-- set from value -->
143
115
  <input :value="value" />
144
116
  <textarea :value="value" />
145
117
 
@@ -149,98 +121,73 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
149
121
  </select>
150
122
  ```
151
123
 
152
- #### `:with="data"`
124
+ #### `:*="value"`, `:="values"`
153
125
 
154
- Define or extend data scope for a subtree.
126
+ Set any attribute(s).
155
127
 
156
128
  ```html
157
- <!-- Inline data -->
158
- <x :with="{ foo: 'bar' }" :text="foo"></x>
159
-
160
- <!-- External data -->
161
- <y :with="data"></y>
162
-
163
- <!-- Extend scope -->
164
- <x :with="{ foo: 'bar' }">
165
- <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
166
- </x>
167
- ```
168
-
169
- #### `:<prop>="value?"`
170
-
171
- Set any attribute value or run an effect.
172
-
173
- ```html
174
- <!-- Single property -->
175
129
  <label :for="name" :text="name" />
176
130
 
177
- <!-- Multiple properties -->
131
+ <!-- multiple attributes -->
178
132
  <input :id:name="name" />
179
133
 
180
- <!-- Effect - returns undefined, triggers any time bar changes -->
181
- <div :fx="void bar()" ></div>
182
-
183
- <!-- Raw event listener (see events) -->
184
- <div :onclick="e=>e.preventDefault()"></div>
134
+ <!-- spread attributes -->
135
+ <input :="{ id: name, name, type: 'text', value }" />
185
136
  ```
186
137
 
187
- #### `:="props?"`
138
+ #### `:scope="data"`
188
139
 
189
- Spread multiple attibures.
140
+ Define or extend data scope for a subtree.
190
141
 
191
142
  ```html
192
- <input :="{ id: name, name, type:'text', value }" />
143
+ <x :scope="{ foo: signal('bar') }">
144
+ <!-- extends parent scope -->
145
+ <y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
146
+ </x>
193
147
  ```
194
148
 
195
- #### `:ref="id"`
149
+ #### `:ref="name"`
196
150
 
197
- Expose element to current data scope with the `id`:
151
+ Expose element to current scope with `name`.
198
152
 
199
153
  ```html
200
- <!-- single item -->
201
154
  <textarea :ref="text" placeholder="Enter text..."></textarea>
202
- <span :text="text.value"></span>
203
155
 
204
156
  <!-- iterable items -->
205
- <ul>
206
- <li :each="item in items" :ref="item">
207
- <input @focus="item.classList.add('editing')" @blur="item.classList.remove('editing')"/>
208
- </li>
209
- </ul>
157
+ <li :each="item in items" :ref="item">
158
+ <input :onfocus..onblur=="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
159
+ </li>
210
160
  ```
211
161
 
212
- #### `:render="ref"`
162
+ #### `:fx="code"`
213
163
 
214
- Include template as element content.
164
+ Run effect, not changing any attribute.<br/>Optional cleanup is called in-between effect calls or on disposal.
215
165
 
216
166
  ```html
217
- <!-- assign template element to foo variable -->
218
- <template :ref="foo"><span :text="foo"></span></template>
167
+ <div :fx="a.value ? foo() : bar()" />
219
168
 
220
- <!-- rended template as content -->
221
- <div :render="foo" :with="{foo:'bar'}">...inserted here...</div>
222
- <div :render="foo" :with="{foo:'baz'}">...inserted here...</div>
169
+ <!-- cleanup function -->
170
+ <div :fx="id = setInterval(tick, interval), () => clearInterval(tick)" />
223
171
  ```
224
172
 
173
+ #### `:on*="handler"`
225
174
 
226
- ## Events
227
-
228
- #### `@<event>="handle"`, `@<foo>@<bar>.<baz>="handle"`
229
-
230
- Attach event(s) listener with possible modifiers. `event` variable holds current event. Allows async handlers.
175
+ Attach event(s) listener with possible modifiers.
231
176
 
232
177
  ```html
233
- <!-- Single event -->
234
- <input type="checkbox" @change="isChecked = event.target.value">
178
+ <input type="checkbox" :onchange="e => isChecked = e.target.value">
235
179
 
236
- <!-- Multiple events -->
237
- <input :value="text" @input@change="text = event.target.value">
180
+ <!-- multiple events -->
181
+ <input :value="text" :oninput:onchange="e => text = e.target.value">
238
182
 
239
- <!-- Event modifiers -->
240
- <button @click.throttle-500="handler(event)">Not too often</button>
183
+ <!-- events sequence -->
184
+ <button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
185
+
186
+ <!-- event modifiers -->
187
+ <button :onclick.throttle-500="handler">Not too often</button>
241
188
  ```
242
189
 
243
- ##### Event modifiers
190
+ ##### Modifiers:
244
191
 
245
192
  * `.once`, `.passive`, `.capture` – listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
246
193
  * `.prevent`, `.stop` – prevent default or stop propagation.
@@ -250,46 +197,207 @@ Attach event(s) listener with possible modifiers. `event` variable holds current
250
197
  * `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
251
198
  * `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
252
199
 
200
+ #### `:html="element"` 🔌
253
201
 
254
- ## Sandbox
202
+ > Include as `import 'sprae/directive/html'`.
255
203
 
256
- Expressions are sandboxed, ie. don't access global/window scope by default (since sprae can be run in server environment).
204
+ Set html content of an element or instantiate a template.
257
205
 
258
206
  ```html
259
- <div :x="scrollY"></div>
260
- <!-- scrollY is undefined -->
207
+ Hello, <span :html="userElement">Guest</span>.
208
+
209
+ <!-- fragment -->
210
+ Hello, <template :html="user.name">Guest</template>.
211
+
212
+ <!-- instantiate template -->
213
+ <template :ref="tpl"><span :text="foo"></span></template>
214
+ <div :html="tpl" :scope="{foo:'bar'}">...inserted here...</div>
261
215
  ```
262
216
 
263
- Default sandbox provides most popular global objects: _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_,
264
- _console_, _window_, _document_, _history_, _navigator_, _location_, _screen_, _localStorage_, _sessionStorage_,
265
- _alert_, _prompt_, _confirm_, _fetch_, _performance_,
266
- _setTimeout_, _setInterval_, _requestAnimationFrame_.
217
+ #### `:data="values"` 🔌
218
+
219
+ > Include as `import 'sprae/directive/data'`.
267
220
 
268
- Sandbox can be extended as `Object.assign(sprae.globals, { BigInt })`.
221
+ Set `data-*` attributes. CamelCase is converted to dash-case.
222
+
223
+ ```html
224
+ <input :data="{foo: 1, barBaz: true}" />
225
+ <!-- <input data-foo="1" data-bar-baz /> -->
226
+ ```
269
227
 
270
- ## FOUC
228
+ #### `:aria="values"` 🔌
271
229
 
272
- To avoid _flash of unstyled content_, you can hide sprae attribute or add a custom effect, eg. `:hidden` - that will be removed once sprae is initialized:
230
+ > Include as `import 'sprae/directive/aria'`.
231
+
232
+ Set `aria-*` attributes. Boolean values are stringified.
273
233
 
274
234
  ```html
275
- <div :hidden></div>
276
- <style>[:each],[:hidden] {visibility: hidden}</style>
235
+ <input role="combobox" :aria="{
236
+ controls: 'joketypes',
237
+ autocomplete: 'list',
238
+ expanded: false,
239
+ activeOption: 'item1',
240
+ activedescendant: ''
241
+ }" />
242
+ <!--
243
+ <input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant>
244
+ -->
277
245
  ```
278
246
 
279
- ## Dispose
247
+ <!--
248
+ #### `:onvisible..oninvisible="e => e => {}"`
280
249
 
281
- To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`.
250
+ Trigger when element is in/out of the screen.
282
251
 
283
- ## Benchmark
252
+ ```html
253
+ <div :onvisible..oninvisible="e => (
254
+ e.target.classList.add('visible'),
255
+ e => e.target.classlist.remove('visible')
256
+ )"/>
257
+ ```
284
258
 
285
- See [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbIm5vbi1rZXllZC9wZXRpdGUtdnVlIiwibm9uLWtleWVkL3NwcmFlIl0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIiwiNDFfc2l6ZS11bmNvbXByZXNzZWQiLCI0Ml9zaXplLWNvbXByZXNzZWQiXSwiZGlzcGxheU1vZGUiOjF9).
259
+ #### `:onmount..onunmount="e => e => {}"`
260
+
261
+ Trigger when element is connected / disconnected from DOM.
262
+
263
+ ```html
264
+ <div :onmount..onunmount="e => (dispose = init(), e => dispose())"/>
265
+ ```
266
+ -->
267
+
268
+
269
+ ## Expressions
270
+
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_.
272
+
273
+ ###### Operators:
274
+
275
+ `++ -- ! - + ** * / % && || ??`<br/>
276
+ `= < <= > >= == != === !==`<br/>
277
+ `<< >> & ^ | ~ ?: . ?. [] ()=>{} in`
278
+
279
+ ###### Primitives:
286
280
 
281
+ `[] {} "" ''`<br/>
282
+ `1 2.34 -5e6 0x7a`<br/>
283
+ `true false null undefined NaN`
284
+
285
+
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
301
+
302
+ Sprae build can be tailored to project needs via `sprae/core` and `sprae/directive/*`:
303
+
304
+ ```js
305
+ import sprae, { directive, compile } from 'sprae/core.js'
306
+
307
+ // include directives
308
+ import 'sprae/directive/if.js';
309
+ import 'sprae/directive/text.js';
310
+
311
+ // define custom directive
312
+ directive.id = (el, expr, state) => {
313
+ const evaluate = compile(state, 'id') // expression string -> evaluator
314
+ return () => el.id = evaluate(state) // return update function
315
+ }
316
+ ```
317
+
318
+ <!--
319
+ ### DOM diffing
320
+
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):
322
+
323
+ ```js
324
+ import sprae from 'sprae';
325
+ import domdiff from 'list-difference';
326
+
327
+ // swap(parentNode, prevEls, newEls, endNode?)
328
+ sprae.use({ swap: domdiff });
329
+ ```
330
+ -->
331
+
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
+
347
+ <!-- ## Dispose
348
+
349
+ To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
350
+
351
+
352
+ ## v9 changes
353
+
354
+ * No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
355
+ * No default globals (`console`, `setTimeout` etc) - pass to state if required.
356
+ * ``:class="`abc ${def}`"`` → `:class="'abc $<def>'"` (_justin_)
357
+ * `:with={x:'x'}` -> `:scope={x:'x'}`
358
+ * No reactive store → use signals for reactive values.
359
+ * `:render="tpl"` → `:html="tpl"`
360
+ * `@click="event.target"` → `:onclick="event => event.target"`
361
+ * Async props / events are not supported, pass async functions via state.
362
+ * Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
363
+ * Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
364
+
365
+
366
+ ## Justification
367
+
368
+ [Template-parts](https://github.com/dy/template-parts) / [templize](https://github.com/dy/templize) is progressive, but is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). [Alpine](https://github.com/alpinejs/alpine) / [petite-vue](https://github.com/vuejs/petite-vue) / [lucia](https://github.com/aidenyabi/lucia) escape native HTML quirks, but have excessive API (`:`, `x-`, `{}`, `@`, `$`) and tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223).
369
+
370
+ _Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with _signals_.
371
+
372
+ <!--
373
+ | | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
374
+ |-----------------------|-------------------|-------------------|------------------|
375
+ | _Size_ | ~10KB | ~6KB | ~5KB |
376
+ | _Memory_ | 5.05 | 3.16 | 2.78 |
377
+ | _Performance_ | 2.64 | 2.43 | 1.76 |
378
+ | _CSP_ | Limited | No | Yes |
379
+ | _SSR_ | No | No | No |
380
+ | _Evaluation_ | [`new AsyncFunction`](https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L81) | [`new Function`](https://github.com/vuejs/petite-vue/blob/main/src/eval.ts#L20) | [`new Function`]() / [justin](https://github.com/dy/subscript) |
381
+ | _Reactivity_ | `Alpine.store` | _@vue/reactivity_ | _signals_ |
382
+ | _Sandboxing_ | No | No | Yes |
383
+ | _Directives_ | `:`, `x-`, `{}` | `:`, `v-`, `@`, `{}` | `:` |
384
+ | _Magic_ | `$data` | `$app` | - |
385
+ | _Fragments_ | Yes | No | Yes |
386
+ | _Plugins_ | Yes | No | Yes |
387
+ | _Modifiers_ | Yes | No | Yes |
388
+ -->
389
+
390
+ <!--
287
391
  <details>
288
- <summary>Results table</summary>
392
+ <summary><strong>Benchmark</strong></summary>
393
+
394
+ See [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbIm5vbi1rZXllZC9wZXRpdGUtdnVlIiwibm9uLWtleWVkL3NwcmFlIl0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIiwiNDFfc2l6ZS11bmNvbXByZXNzZWQiLCI0Ml9zaXplLWNvbXByZXNzZWQiXSwiZGlzcGxheU1vZGUiOjF9).
289
395
 
290
396
  ![Benchmark](./bench.png)
291
397
  </details>
398
+ -->
292
399
 
400
+ <!--
293
401
  <details>
294
402
  <summary>How to run</summary>
295
403
 
@@ -305,52 +413,43 @@ npm ci
305
413
  npm run build-prod
306
414
 
307
415
  # bench
308
- cd ../../..
309
- cd webdriver-ts
416
+ cd ../../../webdriver-ts
310
417
  npm ci
311
418
  npm run compile
312
419
  npm run bench keyed/sprae
313
420
 
314
421
  # show results
315
- cd ..
316
- cd webdriver-ts-results
422
+ cd ../webdriver-ts-results
317
423
  npm ci
318
- cd ..
319
- cd webdriver-ts
424
+ cd ../webdriver-ts
320
425
  npm run results
321
426
  ```
322
427
  </details>
428
+ -->
323
429
 
324
- ## Examples
430
+ <!-- ## See also -->
325
431
 
326
- * TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
327
- * JS Framework Benchmark: [demo](https://dy.github.io/sprae/examples/js-framework-benchmark), [code](https://github.com/dy/sprae/blob/main/examples/js-framework-benchmark.html)
328
- * Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
329
- * Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
432
+ <!--
433
+ ## Alternatives
330
434
 
331
- ## Justification
435
+ * [Alpine](https://github.com/alpinejs/alpine)
436
+ * ~~[Lucia](https://github.com/aidenybai/lucia)~~ deprecated
437
+ * [Petite-vue](https://github.com/vuejs/petite-vue)
438
+ * [nuejs](https://github.com/nuejs/nuejs)
439
+ -->
332
440
 
333
- * [Template-parts](https://github.com/dy/template-parts) / [templize](https://github.com/dy/templize) is progressive, but is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). Also ergonomics of `attr="{{}}"` is inferior to `:attr=""` since it creates flash of uninitialized values. Also it's just nice to keep `{{}}` generic, regardless of markup, and attributes as part of markup.
334
- * [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escape native HTML quirks, but the syntax space (`:attr`, `v-*`,`x-*`, `l-*` `@evt`, `{{}}`) is too broad, as well as functionality. Perfection is when there's nothing to take away, not add (c). Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223) making interop hard, invent own tooling or complex reactivity.
335
- * React / [preact](https://ghub.io/preact) does the job wiring up JS to HTML, but with an extreme of migrating HTML to JSX and enforcing SPA, which is not organic for HTML. Also it doesn't support reactive fields (needs render call).
336
441
 
337
- _Sprae_ takes idea of _templize_ / _alpine_ / _vue_ attributes and builds simple reactive state based on [_@preact/signals_](https://ghub.io/@preact/signals).
442
+ ## Examples
338
443
 
339
- * It doesn't break or modify static html markup.
340
- * It falls back to element content if uninitialized.
341
- * It doesn't enforce SPA nor JSX.
342
- * It enables island hydration.
343
- * It reserves minimal syntax space as `:` convention (keeping tree neatly decorated, not scattered).
344
- * Expressions are naturally reactive and incur minimal updates.
345
- * Elements / data API is open and enable easy interop.
444
+ * ToDo MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
445
+ * JS Framework Benchmark: [demo](https://dy.github.io/sprae/examples/js-framework-benchmark), [code](https://github.com/dy/sprae/blob/main/examples/js-framework-benchmark.html)
446
+ * Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
447
+ * Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
346
448
 
347
- It is reminiscent of [XSLT](https://www.w3schools.com/xml/xsl_intro.asp), considered a [buried treasure](https://github.com/bahrus/be-restated) by web-connoisseurs.
449
+ <!--
450
+ ## See Also
348
451
 
349
- ## Alternatives
452
+ * [nadi](https://github.com/dy/nadi) - 101 signals. -->
350
453
 
351
- * [Alpine](https://github.com/alpinejs/alpine)
352
- * ~~[Lucia](https://github.com/aidenybai/lucia)~~ deprecated
353
- * [Petite-vue](https://github.com/vuejs/petite-vue)
354
- * [nuejs](https://github.com/nuejs/nuejs)
355
454
 
356
455
  <p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
package/sprae.js ADDED
@@ -0,0 +1,14 @@
1
+ export * from './core.js'
2
+ export { default } from './core.js'
3
+
4
+ import './directive/if.js'
5
+ import './directive/each.js'
6
+ import './directive/ref.js'
7
+ import './directive/scope.js'
8
+ import './directive/html.js'
9
+ import './directive/text.js'
10
+ import './directive/class.js'
11
+ import './directive/style.js'
12
+ import './directive/value.js'
13
+ import './directive/fx.js'
14
+ import './directive/default.js'