sprae 9.0.0 → 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 +23 -19
- 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 -42
- 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/dist/sprae.js +45 -41
- package/dist/sprae.min.js +1 -1
- package/package.json +6 -4
- package/readme.md +200 -257
- package/sprae.js +17 -2
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
|
|
|
@@ -29,281 +29,220 @@ Sprae evaluates `:`-directives and evaporates them, attaching state to html.
|
|
|
29
29
|
|
|
30
30
|
## Directives
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
<summary><strong>:if, :else</strong></summary>
|
|
34
|
-
|
|
35
|
-
#### `:if="condition"`, `:else`
|
|
36
|
-
|
|
37
|
-
Control flow of elements.
|
|
38
|
-
|
|
39
|
-
```html
|
|
40
|
-
<span :if="foo">foo</span>
|
|
41
|
-
<span :else :if="bar">bar</span>
|
|
42
|
-
<span :else>baz</span>
|
|
43
|
-
|
|
44
|
-
<!-- fragment -->
|
|
45
|
-
<template :if="foo">
|
|
46
|
-
foo <span>bar</span> baz
|
|
47
|
-
</template>
|
|
48
|
-
```
|
|
49
|
-
</details>
|
|
50
|
-
|
|
51
|
-
<details>
|
|
52
|
-
<summary><strong>:each</strong></summary>
|
|
53
|
-
|
|
54
|
-
#### `:each="item, index in items"`
|
|
55
|
-
|
|
56
|
-
Multiply element. Item is identified either by `item.id` or `item.key`.
|
|
57
|
-
|
|
58
|
-
```html
|
|
59
|
-
<ul><li :each="item in items" :text="item"/></ul>
|
|
60
|
-
|
|
61
|
-
<!-- cases -->
|
|
62
|
-
<li :each="item, idx in list" />
|
|
63
|
-
<li :each="val, key in obj" />
|
|
64
|
-
<li :each="idx in number" />
|
|
65
|
-
|
|
66
|
-
<!-- by condition -->
|
|
67
|
-
<li :if="items" :each="item in items" :text="item" />
|
|
68
|
-
<li :else>Empty list</li>
|
|
69
|
-
|
|
70
|
-
<!-- fragment -->
|
|
71
|
-
<template :each="item in items">
|
|
72
|
-
<dt :text="item.term"/>
|
|
73
|
-
<dd :text="item.definition"/>
|
|
74
|
-
</template>
|
|
75
|
-
|
|
76
|
-
<!-- prevent FOUC -->
|
|
77
|
-
<style>[:each] {visibility: hidden}</style>
|
|
78
|
-
```
|
|
79
|
-
</details>
|
|
32
|
+
#### `:if="condition"`, `:else`
|
|
80
33
|
|
|
81
|
-
|
|
82
|
-
<summary><strong>:text</strong></summary>
|
|
34
|
+
Control flow of elements.
|
|
83
35
|
|
|
84
|
-
|
|
36
|
+
```html
|
|
37
|
+
<span :if="foo">foo</span>
|
|
38
|
+
<span :else :if="bar">bar</span>
|
|
39
|
+
<span :else>baz</span>
|
|
85
40
|
|
|
86
|
-
|
|
41
|
+
<!-- fragment -->
|
|
42
|
+
<template :if="foo">foo <span>bar</span> baz</template>
|
|
43
|
+
```
|
|
87
44
|
|
|
88
|
-
```html
|
|
89
|
-
Welcome, <span :text="user.name">Guest</span>.
|
|
90
45
|
|
|
91
|
-
|
|
92
|
-
Welcome, <template :text="user.name" />.
|
|
93
|
-
```
|
|
94
|
-
</details>
|
|
46
|
+
#### `:each="item, index in items"`
|
|
95
47
|
|
|
96
|
-
|
|
97
|
-
<summary><strong>:class</strong></summary>
|
|
48
|
+
Multiply element. Item is identified either by `item.id`, `item.key` or `item` itself.
|
|
98
49
|
|
|
99
|
-
|
|
50
|
+
```html
|
|
51
|
+
<ul><li :each="item in items" :text="item"/></ul>
|
|
100
52
|
|
|
101
|
-
|
|
53
|
+
<!-- cases -->
|
|
54
|
+
<li :each="item, idx in list" />
|
|
55
|
+
<li :each="val, key in obj" />
|
|
56
|
+
<li :each="idx in number" />
|
|
102
57
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
58
|
+
<!-- by condition -->
|
|
59
|
+
<li :if="items" :each="item in items" :text="item" />
|
|
60
|
+
<li :else>Empty list</li>
|
|
106
61
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
62
|
+
<!-- fragment -->
|
|
63
|
+
<template :each="item in items">
|
|
64
|
+
<dt :text="item.term"/>
|
|
65
|
+
<dd :text="item.definition"/>
|
|
66
|
+
</template>
|
|
111
67
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#### `:style="value"`
|
|
68
|
+
<!-- prevent FOUC -->
|
|
69
|
+
<style>[:each] {visibility: hidden}</style>
|
|
70
|
+
```
|
|
116
71
|
|
|
117
|
-
|
|
72
|
+
#### `:text="value"`
|
|
118
73
|
|
|
119
|
-
|
|
120
|
-
<!-- string with interpolation -->
|
|
121
|
-
<div :style="'foo: $<bar>'"></div>
|
|
74
|
+
Set text content of an element.
|
|
122
75
|
|
|
123
|
-
|
|
124
|
-
|
|
76
|
+
```html
|
|
77
|
+
Welcome, <span :text="user.name">Guest</span>.
|
|
125
78
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</details>
|
|
79
|
+
<!-- fragment -->
|
|
80
|
+
Welcome, <template :text="user.name" />.
|
|
81
|
+
```
|
|
130
82
|
|
|
131
|
-
|
|
132
|
-
<summary><strong>:value</strong></summary>
|
|
83
|
+
#### `:class="value"`
|
|
133
84
|
|
|
134
|
-
|
|
85
|
+
Set class value, extends existing `class`.
|
|
135
86
|
|
|
136
|
-
|
|
87
|
+
```html
|
|
88
|
+
<!-- string with interpolation -->
|
|
89
|
+
<div :class="'foo $<bar>'"></div>
|
|
137
90
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
91
|
+
<!-- array/object a-la clsx -->
|
|
92
|
+
<div :class="[foo && 'foo', {bar: bar}]"></div>
|
|
93
|
+
```
|
|
141
94
|
|
|
142
|
-
|
|
143
|
-
<select :value="selected">
|
|
144
|
-
<option :each="i in 5" :value="i" :text="i"></option>
|
|
145
|
-
</select>
|
|
146
|
-
```
|
|
147
|
-
</details>
|
|
95
|
+
#### `:style="value"`
|
|
148
96
|
|
|
149
|
-
|
|
150
|
-
<summary><strong>:*</strong></summary>
|
|
97
|
+
Set style value, extends existing `style`.
|
|
151
98
|
|
|
152
|
-
|
|
99
|
+
```html
|
|
100
|
+
<!-- string with interpolation -->
|
|
101
|
+
<div :style="'foo: $<bar>'"></div>
|
|
153
102
|
|
|
154
|
-
|
|
103
|
+
<!-- object -->
|
|
104
|
+
<div :style="{foo: 'bar'}"></div>
|
|
155
105
|
|
|
156
|
-
|
|
157
|
-
|
|
106
|
+
<!-- CSS variable -->
|
|
107
|
+
<div :style="{'--baz': qux}"></div>
|
|
108
|
+
```
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
<input :id:name="name" />
|
|
110
|
+
#### `:value="value"`
|
|
161
111
|
|
|
162
|
-
|
|
163
|
-
<input :="{ id: name, name, type: 'text', value }" />
|
|
164
|
-
```
|
|
165
|
-
</details>
|
|
112
|
+
Set value of an input, textarea or select. Takes handle of `checked` and `selected` attributes.
|
|
166
113
|
|
|
167
|
-
|
|
168
|
-
|
|
114
|
+
```html
|
|
115
|
+
<input :value="value" />
|
|
116
|
+
<textarea :value="value" />
|
|
169
117
|
|
|
170
|
-
|
|
118
|
+
<!-- selects right option -->
|
|
119
|
+
<select :value="selected">
|
|
120
|
+
<option :each="i in 5" :value="i" :text="i"></option>
|
|
121
|
+
</select>
|
|
122
|
+
```
|
|
171
123
|
|
|
172
|
-
|
|
124
|
+
#### `:[prop]="value"`, `:="values"`
|
|
173
125
|
|
|
174
|
-
|
|
175
|
-
<x :scope="{ foo: signal('bar') }">
|
|
176
|
-
<!-- extends parent scope -->
|
|
177
|
-
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
178
|
-
</x>
|
|
179
|
-
```
|
|
180
|
-
</details>
|
|
126
|
+
Set any attribute(s).
|
|
181
127
|
|
|
182
|
-
|
|
183
|
-
|
|
128
|
+
```html
|
|
129
|
+
<label :for="name" :text="name" />
|
|
184
130
|
|
|
185
|
-
|
|
131
|
+
<!-- multiple attributes -->
|
|
132
|
+
<input :id:name="name" />
|
|
186
133
|
|
|
187
|
-
|
|
134
|
+
<!-- spread attributes -->
|
|
135
|
+
<input :="{ id: name, name, type: 'text', value }" />
|
|
136
|
+
```
|
|
188
137
|
|
|
189
|
-
|
|
190
|
-
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
138
|
+
#### `:scope="data"`
|
|
191
139
|
|
|
192
|
-
|
|
193
|
-
<li :each="item in items" :ref="item">
|
|
194
|
-
<input :onfocus..onblur=="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
195
|
-
</li>
|
|
196
|
-
```
|
|
197
|
-
</details>
|
|
140
|
+
Define or extend data scope for a subtree.
|
|
198
141
|
|
|
199
|
-
|
|
200
|
-
|
|
142
|
+
```html
|
|
143
|
+
<x :scope="{ foo: signal('bar') }">
|
|
144
|
+
<!-- extends parent scope -->
|
|
145
|
+
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
146
|
+
</x>
|
|
147
|
+
```
|
|
201
148
|
|
|
202
|
-
|
|
149
|
+
#### `:ref="name"`
|
|
203
150
|
|
|
204
|
-
|
|
151
|
+
Expose element to current scope with `name`.
|
|
205
152
|
|
|
206
|
-
|
|
207
|
-
|
|
153
|
+
```html
|
|
154
|
+
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
208
155
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
</
|
|
156
|
+
<!-- iterable items -->
|
|
157
|
+
<li :each="item in items" :ref="item">
|
|
158
|
+
<input :onfocus..onblur=="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
159
|
+
</li>
|
|
160
|
+
```
|
|
213
161
|
|
|
214
|
-
|
|
215
|
-
<summary><strong>:on*</strong></summary>
|
|
162
|
+
#### `:fx="code"`
|
|
216
163
|
|
|
217
|
-
|
|
164
|
+
Run effect, not changing any attribute.<br/>Optional cleanup is called in-between effect calls or on disposal.
|
|
218
165
|
|
|
219
|
-
|
|
166
|
+
```html
|
|
167
|
+
<div :fx="a.value ? foo() : bar()" />
|
|
220
168
|
|
|
221
|
-
|
|
222
|
-
|
|
169
|
+
<!-- cleanup function -->
|
|
170
|
+
<div :fx="id = setInterval(tick, interval), () => clearInterval(tick)" />
|
|
171
|
+
```
|
|
223
172
|
|
|
224
|
-
|
|
225
|
-
<input :value="text" :oninput:onchange="e => text = e.target.value">
|
|
173
|
+
#### `:on[event]="handler"`
|
|
226
174
|
|
|
227
|
-
|
|
228
|
-
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
|
|
175
|
+
Attach event(s) listener with possible modifiers.
|
|
229
176
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
```
|
|
177
|
+
```html
|
|
178
|
+
<input type="checkbox" :onchange="e => isChecked = e.target.value">
|
|
233
179
|
|
|
234
|
-
|
|
180
|
+
<!-- multiple events -->
|
|
181
|
+
<input :value="text" :oninput:onchange="e => text = e.target.value">
|
|
235
182
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
239
|
-
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
240
|
-
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
241
|
-
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
242
|
-
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
183
|
+
<!-- events sequence -->
|
|
184
|
+
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
|
|
243
185
|
|
|
244
|
-
|
|
186
|
+
<!-- event modifiers -->
|
|
187
|
+
<button :onclick.throttle-500="handler">Not too often</button>
|
|
188
|
+
```
|
|
245
189
|
|
|
190
|
+
##### Modifiers:
|
|
246
191
|
|
|
247
|
-
|
|
248
|
-
|
|
192
|
+
* `.once`, `.passive`, `.capture` – listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
|
|
193
|
+
* `.prevent`, `.stop` – prevent default or stop propagation.
|
|
194
|
+
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
195
|
+
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
196
|
+
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
197
|
+
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
198
|
+
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
249
199
|
|
|
250
|
-
|
|
200
|
+
#### `:html="element"` 🔌
|
|
251
201
|
|
|
252
|
-
|
|
202
|
+
> Include as `import 'sprae/directive/html'`.
|
|
253
203
|
|
|
254
|
-
|
|
204
|
+
Set html content of an element or instantiate a template.
|
|
255
205
|
|
|
256
|
-
|
|
257
|
-
|
|
206
|
+
```html
|
|
207
|
+
Hello, <span :html="userElement">Guest</span>.
|
|
258
208
|
|
|
259
|
-
|
|
260
|
-
|
|
209
|
+
<!-- fragment -->
|
|
210
|
+
Hello, <template :html="user.name">Guest</template>.
|
|
261
211
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
</details>
|
|
212
|
+
<!-- instantiate template -->
|
|
213
|
+
<template :ref="tpl"><span :text="foo"></span></template>
|
|
214
|
+
<div :html="tpl" :scope="{foo:'bar'}">...inserted here...</div>
|
|
215
|
+
```
|
|
267
216
|
|
|
217
|
+
#### `:data="values"` 🔌
|
|
268
218
|
|
|
269
|
-
|
|
270
|
-
<summary><strong>:data</strong> 🔌</summary>
|
|
219
|
+
> Include as `import 'sprae/directive/data'`.
|
|
271
220
|
|
|
272
|
-
|
|
221
|
+
Set `data-*` attributes. CamelCase is converted to dash-case.
|
|
273
222
|
|
|
274
|
-
|
|
223
|
+
```html
|
|
224
|
+
<input :data="{foo: 1, barBaz: true}" />
|
|
225
|
+
<!-- <input data-foo="1" data-bar-baz /> -->
|
|
226
|
+
```
|
|
275
227
|
|
|
276
|
-
|
|
228
|
+
#### `:aria="values"` 🔌
|
|
277
229
|
|
|
278
|
-
|
|
279
|
-
<input :data="{foo: 1, barBaz: true}" />
|
|
280
|
-
<!-- <input data-foo="1" data-bar-baz /> -->
|
|
281
|
-
```
|
|
282
|
-
</details>
|
|
230
|
+
> Include as `import 'sprae/directive/aria'`.
|
|
283
231
|
|
|
232
|
+
Set `aria-*` attributes. Boolean values are stringified.
|
|
284
233
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
autocomplete: 'list',
|
|
298
|
-
expanded: false,
|
|
299
|
-
activeOption: 'item1',
|
|
300
|
-
activedescendant: ''
|
|
301
|
-
}" />
|
|
302
|
-
<!--
|
|
303
|
-
<input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant>
|
|
304
|
-
-->
|
|
305
|
-
```
|
|
306
|
-
</details>
|
|
234
|
+
```html
|
|
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
|
+
-->
|
|
245
|
+
```
|
|
307
246
|
|
|
308
247
|
<!--
|
|
309
248
|
#### `:onvisible..oninvisible="e => e => {}"`
|
|
@@ -326,10 +265,43 @@ Trigger when element is connected / disconnected from DOM.
|
|
|
326
265
|
```
|
|
327
266
|
-->
|
|
328
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);
|
|
281
|
+
|
|
282
|
+
sprae(el, { name: signal('Kitty') });
|
|
283
|
+
```
|
|
284
|
+
|
|
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_:
|
|
329
296
|
|
|
330
|
-
|
|
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
|
+
```
|
|
331
303
|
|
|
332
|
-
|
|
304
|
+
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset. It avoids "unsafe-eval" CSP and provides sandboxing.
|
|
333
305
|
|
|
334
306
|
###### Operators:
|
|
335
307
|
|
|
@@ -344,42 +316,29 @@ Expressions use [_justin_](https://github.com/dy/subscript?tab=readme-ov-file#ju
|
|
|
344
316
|
`true false null undefined NaN`
|
|
345
317
|
|
|
346
318
|
|
|
347
|
-
##
|
|
348
|
-
|
|
349
|
-
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:
|
|
350
|
-
|
|
351
|
-
```js
|
|
352
|
-
import sprae, { signal, computed, effect, batch, untracked } from 'sprae';
|
|
353
|
-
import * as signals from '@preact/signals-core';
|
|
354
|
-
|
|
355
|
-
sprae.use(signals);
|
|
356
|
-
|
|
357
|
-
sprae(el, { name: signal('Kitty') });
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
## Customization
|
|
319
|
+
## Directives
|
|
362
320
|
|
|
363
|
-
Sprae build can be tailored to project needs via `sprae/core
|
|
321
|
+
Sprae build can be tailored to project needs via `sprae/core`:
|
|
364
322
|
|
|
365
323
|
```js
|
|
366
|
-
import sprae, { directive
|
|
324
|
+
import sprae, { directive } from 'sprae/core.js'
|
|
367
325
|
|
|
368
326
|
// include directives
|
|
369
|
-
import 'sprae/directive/if.js'
|
|
370
|
-
import 'sprae/directive/text.js'
|
|
327
|
+
import 'sprae/directive/if.js'
|
|
328
|
+
import 'sprae/directive/text.js'
|
|
371
329
|
|
|
372
330
|
// define custom directive
|
|
373
|
-
directive.id = (el,
|
|
374
|
-
const evaluate = compile(state, 'id') // expression string -> evaluator
|
|
331
|
+
directive.id = (el, evaluate, state) => {
|
|
375
332
|
return () => el.id = evaluate(state) // return update function
|
|
376
333
|
}
|
|
377
334
|
```
|
|
378
335
|
|
|
336
|
+
See [`sprae.js`](./sprae.js) for example.
|
|
337
|
+
|
|
379
338
|
<!--
|
|
380
339
|
### DOM diffing
|
|
381
340
|
|
|
382
|
-
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)):
|
|
383
342
|
|
|
384
343
|
```js
|
|
385
344
|
import sprae from 'sprae';
|
|
@@ -390,26 +349,13 @@ sprae.use({ swap: domdiff });
|
|
|
390
349
|
```
|
|
391
350
|
-->
|
|
392
351
|
|
|
393
|
-
<!--
|
|
394
|
-
### Custom Build
|
|
395
|
-
|
|
396
|
-
`sprae/core` exports bare-bones engine without directives, which allows tailoring build to project needs:
|
|
397
|
-
|
|
398
|
-
```js
|
|
399
|
-
import sprae, { directive, effect } from 'sprae/core'
|
|
400
|
-
|
|
401
|
-
// include required directives
|
|
402
|
-
import 'sprae/directive/if'
|
|
403
|
-
import 'sprae/directive/text'
|
|
404
|
-
```
|
|
405
|
-
-->
|
|
406
|
-
|
|
407
352
|
|
|
408
353
|
<!-- ## Dispose
|
|
409
354
|
|
|
410
355
|
To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
|
|
411
356
|
|
|
412
357
|
|
|
358
|
+
<!--
|
|
413
359
|
## v9 changes
|
|
414
360
|
|
|
415
361
|
* No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
|
|
@@ -422,13 +368,13 @@ To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. --
|
|
|
422
368
|
* Async props / events are not supported, pass async functions via state.
|
|
423
369
|
* Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
|
|
424
370
|
* Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
|
|
425
|
-
|
|
371
|
+
-->
|
|
426
372
|
|
|
427
373
|
## Justification
|
|
428
374
|
|
|
429
|
-
[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)
|
|
375
|
+
[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).
|
|
430
376
|
|
|
431
|
-
_Sprae_ holds
|
|
377
|
+
_Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with _signals_.
|
|
432
378
|
|
|
433
379
|
<!--
|
|
434
380
|
| | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
|
|
@@ -474,18 +420,15 @@ npm ci
|
|
|
474
420
|
npm run build-prod
|
|
475
421
|
|
|
476
422
|
# bench
|
|
477
|
-
cd
|
|
478
|
-
cd webdriver-ts
|
|
423
|
+
cd ../../../webdriver-ts
|
|
479
424
|
npm ci
|
|
480
425
|
npm run compile
|
|
481
426
|
npm run bench keyed/sprae
|
|
482
427
|
|
|
483
428
|
# show results
|
|
484
|
-
cd
|
|
485
|
-
cd webdriver-ts-results
|
|
429
|
+
cd ../webdriver-ts-results
|
|
486
430
|
npm ci
|
|
487
|
-
cd
|
|
488
|
-
cd webdriver-ts
|
|
431
|
+
cd ../webdriver-ts
|
|
489
432
|
npm run results
|
|
490
433
|
```
|
|
491
434
|
</details>
|
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'
|