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>:scopemarks regions on the tree that should be controlled by sporae.initattribute 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>sporaeevaluates directives within anelementsubtree with passeddata.stateis proxy reflecting used values, changing any of its props updates directives.updatecan be used for bulk-updating multiple props.datais 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.
3 years ago