Reactive aspect-oriented web-framework.
▶ Run
Spect is minimalistic aspect-oriented web framework with smooth DX, enabling compact UI code and efficient DOM manipulations with 3 essential functions − $, h and v, successors of jquery, hyperscript and observable.
💎 Separation of cross-cutting concerns with aspects in CSS-like style.
🌳 Native first − semantic clean tree, vanilla js.
📲 Organic progressive enhancement.
🐤 Low entry barrier.
💫 0 bundling, 0 server, 0 boilerplate.
Low-profile − can be used as utility.
⛳ Reasonable performance / size balance.
<script type="module">
import { $, h, v } from 'https://unpkg.com/spect?module'
</script>Available from CDN: unpkg, pika.
import { $, h, v } from 'spect'Spect plays well with snowpack, but any other bundler will do.
<div class="user">Loading...</div>
<script type="module">
import { $, h, v } from 'spect'
$('.user', async el => {
// create user state
const user = v({})
// render element content, map user state
h`<${el}>Hello, ${ user.map(u => u.name || 'guest') }!</>`
// load data & set user
user((await fetch('/user')).json())
})
</script><time id="timer"></time>
<script type="module">
import { $, v, h } from 'spect'
$('#timer', timer => {
const count = v(0),
id = setInterval(() => count(c => c + 1), 1000)
h`<${timer}>${ count }</>`
return () => clearInterval(id)
})
</script><output id="count">0</output>
<button id="inc">+</button><button id="dec">-</button>
<script type="module">
import { $, h, v } from 'spect'
const count = v(0)
$('#count', el => count.subscribe(c => el.value = c))
$('#inc', el => el.onclick = e => count(c => c+1))
$('#dec', el => el.onclick = e => count(c => c-1))
</script><form class="todo">
<label for="add-todo">
<span>Add Todo</span>
<input name="text" required>
</label>
<button type="submit">Add</button>
<ul class="todo-list"><ul>
</form>
<script type="module">
import { $, h, v } from 'spect'
const todos = v([])
$('.todo-list', el => h`<${el}>${ todos.map(items =>
items.map(item => h`<li>${ item.text }</li>`)
) }</>`)
$('.todo-form', form => form.addEventListener('submit', e => {
e.preventDefault()
if (!form.checkValidity()) return
// push data, update state
todos().push({ text: form.text.value })
todos(todos())
form.reset()
}))
</script><form></form>
<script type="module">
import { $, h, v } from 'spect'
const isValidEmail = s => /.+@.+\..+/i.test(s);
$('form', form => {
const valid = v(false)
h`<${form}>
<label for="email">Please enter an email address:</label>
<input#email onchange=${ e => valid(isValidEmail(e.target.value)) }/>
The address is ${ valid.map(b => b ? "valid" : "invalid") }
</>`
})
</script>$( scope? , selector , aspect? )
Observe selector, trigger aspect callback for elements matching the selector.
selectoris a valid CSS selector.scopeis optional HTMLElement or a list of elements to narrow down observation scope.aspectis a function with(element) => teardown?signature.
import { $, v, h } from 'spect'
let foos = $('.foo', el => {
console.log('active')
return () => console.log('inactive')
})
let foo = h`<div.foo/>`
document.body.append(foo)
// ... "active"
foo.remove()
// ... "inactive"
// dispose
foos[Symbol.dispose]()el = h`...content`
Hyperscript with observables. Can be used as template literal with htm syntax or as JSX.
import { h, v } from 'spect'
const text = v('foo')
// create <baz>
const foo = h`<a>${ text }</a>`
foo // <a>foo</a>
// update content
text('bar')
foo // <a>bar</a>
// fragment
const frag = h`<a>1</a><a>2</a>`
// hydrate
h`<${foo} ...${props}>${ children }</>`
// observables
h`<a>${ rxSubject } - ${ asyncIterable } - ${ promise }</a>`
/* jsx h */
const bar = <a>{ text }</a>
// dispose
bar[Symbol.dispose]()value = v( init ? )
Simple observable state, tiny replacement for useState. Creates a getter/setter function with observable interface.
import { v } from 'spect'
let v1 = v(0)
// get
v1() // 0
// set
v1(1)
// subscribe
v1.subscribe(value => {
console.log(value)
return () => console.log('teardown', value)
})
// transform
let v2 = v(v1).map(v1 => v1 * 2)
v2() // 2
// initialize
let v3 = v(() => 3)
v3() // 3
// set with fn
v3(v => v + 1)
v3() // 4
// async iterator
for await (const value of v3) console.log(value)
// dispose
v3[Symbol.dispose]()Existing solutions for the functions were considered:
- $: fast-on-load, selector-set, insertionQuery, selector-observer, reuse, aspect-oriended-programming libraries and others.
- h: lit-html, htm, htl, hyperscript, incremental-dom, snabbdom, nanomorph, uhtml and others.
- v: rxjs, observable, react hooks, observable proposal, observ, mutant, iron, icaro, introspected, augmentor and others.
Spect has long story of research, at v13.0 it had repository reset. See changelog.
- element-props − unified access to element props with observable support. Comes handy for organizing components.
- strui − collection of UI streams, such as router, storage etc. Comes handy for building complex reactive web-apps (spect, rxjs etc).
MIT
ॐ