sporae v0.0.0
∴ sporae
Reactive directives with expressions for DOM microtemplating.
<div :if="user">
Logged in as <span :text="user.displayName">Guest.</span>
</div>
<script>
import spores from 'sporae';
const state = spores(document.body, { user: { displayName: 'Dmitry Ivanov' } });
// update value
state.user.displayName = 'dy'
</script>
A lightweight alternative to alpine, petite-vue and templize with better ergonomics*.
Usage
<script src="./sporae.js" defer init></script>
<div :scope="{ count: 0 }">
<span :text="count">
<button :on="{ click: e => count++ }">inc</button>
</div>
:scope
marks regions on the tree that should be controlled by sporae.init
attribute tells sporae to automatically initialize all elements that have:scope
.
Manual init
The more direct case is initializing spores via JS.
<div id="element"></div>
<script type="module">
import sporae from './sporae.js';
const [state, update] = sporae(element, data);
</script>
sporae
evaluates directives within anelement
subtree with passeddata
.state
is proxy reflecting used values, changing any of its props updates directives.update
can be used for bulk-updating multiple props.data
is the initial state to render the template. It can include reactive values, see reactivity.
Directives
:scope="data"
– sporae subtree data for autoinit.:if="condition"
,:else-if="condition"
,:else
- controls flow of elements.:each="item, idx? in list"
- map list to instances of an element.:text="value"
- set text content of an element.:value="value"
– bind value to input or textarea.:id
,:name
,:for
,:type
,:hidden
,:disabled
,:href
,:src
– common attributes setters.:class="[ foo, 'bar' ]"
– set element class from an array, object or a string.:style="{ top:1, position:'absolute' }"
– set element style from a string or an object.:prop="{ alt:'foo', title:'bar' }"
– set any attribute / property.:on="{ click:e=>{}, touch:e=>{} }"
– add event listeners.:data="{ foo:1, bar:2 }"
– set data-* attributes.:aria="{ role:'progressbar' }"
– set aria-role attributes.:item="{ id: 1 }"
– set item* microdata attribute.
Adding directives
Directives can be added by registering them via directive(name, onCreate, onUpdate)
:
import init, { directive } from 'sporae'
directive(':html',
(el, expr, state) => {
el._eval = parseExpression(expr)
},
(el, expr, state) => {
el.innerHTML = el._eval(state)
}
)
Reactivity
Directive expressions are naturally reactive, ie. data may contain any async/reactive values, such as:
- Promise / Thenable
- Observable / Subject / Subscribable
- AsyncIterable
- observ-*
- etc., see sube for the full list.
This way, for example, @preact/signals or rxjs can be connected directly bypassing subscription or reading value.
Update happens when any value changes:
<div id="done" :text="loading ? 'loading' : result">...</div>
<script>
import spores from 'sporae';
import { signals } from '@preact/signals';
// <div id="done">...</div>
const loading = signal(true),
result = signal(false);
spores(done, { loading, result })
// <div id="done">loading</div>
setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
// ... 1s after
// <div id="done">done</div>
</script>
Note: observers don't require disposal, since they're connected in weak fashion. Once element is disposed, observables are disconnected.
Justification
- Template-parts / templize is progressive, but is stuck with native HTML quirks (parsing table case, svg attributes, conflict with liquid syntax etc). Besides ergonomics of
attr="{{}}"
is inferior to:attr=""
since it creates flash of uninitialized values. - Alpine / vue / lit escapes native HTML quirks, but the syntax is a bit scattered:
:attr
,v-*
,x-*
,@evt
,{{}}
can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence. - preact with HTML as JSX is a nice way to wire JS to templates, but it doesn't really support reactive fields (needs render call). Also migrating HTML to JS is an extreme with unwanted side-effects.
Sporae takes elegant syntax convention of alpine and method of templize to connect any reactive values (like @preact/signals or observables) to static HTML.
- It doesn't break nor introduce template values to static html markup.
- It provides falls back to element content if uninitialized.
- It provides means for island hydration.
- It doesn't introduce syntax noise.
- It supports simple expressions with exposed reactive data types.
2 years ago