0.0.0 • Published 2 years ago

sporae v0.0.0

Weekly downloads
-
License
ISC
Repository
github
Last release
2 years ago

∴ 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 an element subtree with passed data.
  • 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.