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/core.js +129 -0
- package/directive/aria.js +10 -0
- package/directive/class.js +17 -0
- package/directive/data.js +10 -0
- package/directive/default.js +148 -0
- package/directive/each.js +61 -0
- package/directive/fx.js +6 -0
- package/directive/html.js +11 -0
- package/directive/if.js +40 -0
- package/directive/ref.js +10 -0
- package/directive/scope.js +11 -0
- package/directive/style.js +16 -0
- package/directive/text.js +12 -0
- package/directive/value.js +31 -0
- package/dist/sprae.js +636 -860
- package/dist/sprae.min.js +1 -1
- package/package.json +19 -13
- package/readme.md +274 -175
- package/sprae.js +14 -0
- package/dist/sprae.auto.js +0 -976
- package/dist/sprae.auto.min.js +0 -1
- package/src/core.js +0 -82
- package/src/directives.js +0 -447
- package/src/domdiff.js +0 -71
- package/src/index.js +0 -6
- package/src/state.proxy.js +0 -150
- package/src/state.signals-proxy.js +0 -151
- package/src/state.signals.js +0 -82
- package/src/util.js +0 -13
package/readme.md
CHANGED
|
@@ -1,73 +1,33 @@
|
|
|
1
1
|
# ∴ spræ [](https://github.com/dy/sprae/actions/workflows/node.js.yml) [](https://bundlephobia.com/result?p=sprae) [](https://npmjs.org/sprae)
|
|
2
2
|
|
|
3
|
-
> DOM tree
|
|
3
|
+
> DOM tree microhydration
|
|
4
4
|
|
|
5
|
-
_Sprae_ is compact ergonomic progressive enhancement framework.<br/>
|
|
6
|
-
It provides
|
|
7
|
-
Perfect for small-scale websites, prototypes or UI
|
|
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
|
-
|
|
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
|
-
|
|
14
|
+
Hello <span :text="user.name">World</span>.
|
|
34
15
|
</div>
|
|
35
16
|
|
|
36
17
|
<script type="module">
|
|
37
|
-
|
|
38
|
-
import sprae from './path/to/sprae.js';
|
|
18
|
+
import sprae, { signal } from 'sprae'
|
|
39
19
|
|
|
40
|
-
const
|
|
41
|
-
|
|
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 `:`-
|
|
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
|
-
|
|
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"
|
|
51
|
+
<ul><li :each="item in items" :text="item"/></ul>
|
|
88
52
|
|
|
89
|
-
<!--
|
|
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
|
-
<!--
|
|
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.
|
|
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
|
|
85
|
+
Set class value, extends existing `class`.
|
|
110
86
|
|
|
111
87
|
```html
|
|
112
|
-
<!--
|
|
113
|
-
<div :class="
|
|
88
|
+
<!-- string with interpolation -->
|
|
89
|
+
<div :class="'foo $<bar>'"></div>
|
|
114
90
|
|
|
115
|
-
<!--
|
|
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
|
|
97
|
+
Set style value, extends existing `style`.
|
|
125
98
|
|
|
126
99
|
```html
|
|
127
|
-
<!--
|
|
128
|
-
<div :style="
|
|
100
|
+
<!-- string with interpolation -->
|
|
101
|
+
<div :style="'foo: $<bar>'"></div>
|
|
129
102
|
|
|
130
|
-
<!--
|
|
103
|
+
<!-- object -->
|
|
131
104
|
<div :style="{foo: 'bar'}"></div>
|
|
132
105
|
|
|
133
|
-
<!--
|
|
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
|
-
####
|
|
124
|
+
#### `:*="value"`, `:="values"`
|
|
153
125
|
|
|
154
|
-
|
|
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
|
-
<!--
|
|
131
|
+
<!-- multiple attributes -->
|
|
178
132
|
<input :id:name="name" />
|
|
179
133
|
|
|
180
|
-
<!--
|
|
181
|
-
<
|
|
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
|
-
####
|
|
138
|
+
#### `:scope="data"`
|
|
188
139
|
|
|
189
|
-
|
|
140
|
+
Define or extend data scope for a subtree.
|
|
190
141
|
|
|
191
142
|
```html
|
|
192
|
-
<
|
|
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="
|
|
149
|
+
#### `:ref="name"`
|
|
196
150
|
|
|
197
|
-
Expose element to current
|
|
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
|
-
<
|
|
206
|
-
<
|
|
207
|
-
|
|
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
|
-
#### `:
|
|
162
|
+
#### `:fx="code"`
|
|
213
163
|
|
|
214
|
-
|
|
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
|
-
|
|
218
|
-
<template :ref="foo"><span :text="foo"></span></template>
|
|
167
|
+
<div :fx="a.value ? foo() : bar()" />
|
|
219
168
|
|
|
220
|
-
<!--
|
|
221
|
-
<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
|
-
|
|
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
|
-
|
|
234
|
-
<input type="checkbox" @change="isChecked = event.target.value">
|
|
178
|
+
<input type="checkbox" :onchange="e => isChecked = e.target.value">
|
|
235
179
|
|
|
236
|
-
<!--
|
|
237
|
-
<input :value="text"
|
|
180
|
+
<!-- multiple events -->
|
|
181
|
+
<input :value="text" :oninput:onchange="e => text = e.target.value">
|
|
238
182
|
|
|
239
|
-
<!--
|
|
240
|
-
<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
|
-
#####
|
|
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
|
-
|
|
202
|
+
> Include as `import 'sprae/directive/html'`.
|
|
255
203
|
|
|
256
|
-
|
|
204
|
+
Set html content of an element or instantiate a template.
|
|
257
205
|
|
|
258
206
|
```html
|
|
259
|
-
<
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
_setTimeout_, _setInterval_, _requestAnimationFrame_.
|
|
217
|
+
#### `:data="values"` 🔌
|
|
218
|
+
|
|
219
|
+
> Include as `import 'sprae/directive/data'`.
|
|
267
220
|
|
|
268
|
-
|
|
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
|
-
|
|
228
|
+
#### `:aria="values"` 🔌
|
|
271
229
|
|
|
272
|
-
|
|
230
|
+
> Include as `import 'sprae/directive/aria'`.
|
|
231
|
+
|
|
232
|
+
Set `aria-*` attributes. Boolean values are stringified.
|
|
273
233
|
|
|
274
234
|
```html
|
|
275
|
-
<
|
|
276
|
-
|
|
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
|
-
|
|
247
|
+
<!--
|
|
248
|
+
#### `:onvisible..oninvisible="e => e => {}"`
|
|
280
249
|
|
|
281
|
-
|
|
250
|
+
Trigger when element is in/out of the screen.
|
|
282
251
|
|
|
283
|
-
|
|
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
|
-
|
|
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>
|
|
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
|

|
|
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
|
-
##
|
|
430
|
+
<!-- ## See also -->
|
|
325
431
|
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
+
## Examples
|
|
338
443
|
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
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
|
-
|
|
449
|
+
<!--
|
|
450
|
+
## See Also
|
|
348
451
|
|
|
349
|
-
|
|
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'
|