npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

sprae

v10.0.1

Published

DOM microhydration.

Downloads

152

Readme

∴ spræ tests size npm

DOM tree microhydration

Sprae is open & minimalistic progressive enhancement framework. Perfect for small-scale websites, static pages, landings, prototypes, or lightweight UI.

Usage

<div id="container" :if="user">
  Hello <span :text="user.name">World</span>.
</div>

<script type="module">
  import sprae from 'sprae'

  // init
  const state = sprae(container, { user: { name: 'Kitty' } })

  // update
  state.user.name = 'Dolly'
</script>

Sprae evaluates :-directives and evaporates them, returning reactive state.

Directives

:if="condition", :else

Control flow of elements.

<span :if="foo">foo</span>
<span :else :if="bar">bar</span>
<span :else>baz</span>

<!-- fragment -->
<template :if="foo">foo <span>bar</span> baz</template>

:each="item, index? in items"

Multiply element.

<ul><li :each="item in items" :text="item"/></ul>

<!-- cases -->
<li :each="item, idx in array" />
<li :each="value, key in object" />
<li :each="count, idx in number" />

<!-- fragment -->
<template :each="item in items">
  <dt :text="item.term"/>
  <dd :text="item.definition"/>
</template>

<!-- prevent FOUC -->
<style>[:each] {visibility: hidden}</style>

:text="value"

Set text content of an element.

Welcome, <span :text="user.name">Guest</span>.

<!-- fragment -->
Welcome, <template :text="user.name" />.

:class="value"

Set class value.

<!-- appends class -->
<div class="foo" :class="bar"></div>

<!-- interpolation -->
<div :class="'foo $<bar>'"></div>

<!-- array/object, a-la clsx -->
<div :class="[foo && 'foo', {bar: bar}]"></div>

:style="value"

Set style value.

<!-- extends style -->
<div style="foo: bar" :style="'baz: qux'">

<!-- interpolation -->
<div :style="'foo: $<bar>'"></div>

<!-- object -->
<div :style="{foo: 'bar'}"></div>

<!-- CSS variable -->
<div :style="{'--baz': qux}"></div>

:value="value"

Set value of an input, textarea or select.

<input :value="value" />
<textarea :value="value" />

<!-- selects right option & handles selected attr -->
<select :value="selected">
  <option :each="i in 5" :value="i" :text="i"></option>
</select>

<!-- handles checked attr -->
<input type="checkbox" :value="checked" />

:[prop]="value", :="values"

Set any attribute(s).

<label :for="name" :text="name" />

<!-- multiple attributes -->
<input :id:name="name" />

<!-- spread attributes -->
<input :="{ id: name, name, type: 'text', value }" />

:with="values"

Define values for a subtree.

<x :with="{ foo: signal('bar') }">
  <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
</x>

:ref="name"

Expose element with name.

<textarea :ref="text" placeholder="Enter text..."></textarea>

<!-- iterable items -->
<li :each="item in items" :ref="item">
  <input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
</li>

:fx="code"

Run effect, not changing any attribute.

<div :fx="a.value ? foo() : bar()" />

<!-- cleanup function -->
<div :fx="id = setInterval(tick, interval), () => clearInterval(tick)" />

:on[event]="handler"

Attach event(s) listener with optional modifiers.

<input type="checkbox" :onchange="e => isChecked = e.target.value">

<!-- multiple events -->
<input :value="text" :oninput:onchange="e => text = e.target.value">

<!-- events sequence -->
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">

<!-- modifiers -->
<button :onclick.throttle-500="handler">Not too often</button>
Modifiers:
  • .once, .passive, .capture – listener options.
  • .prevent, .stop – prevent default or stop propagation.
  • .window, .document, .outside, .self – specify event target.
  • .throttle-<ms>, .debounce-<ms> – defer function call with one of the methods.
  • .ctrl, .shift, .alt, .meta, .arrow, .enter, .escape, .tab, .space, .backspace, .delete, .digit, .letter, .character – filter by event.key.
  • .ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key> – key combinations, eg. .ctrl-alt-delete or .meta-x.
  • .* – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).

:html="element" 🔌

Include as import 'sprae/directive/html'.

Set html content of an element or instantiate a template.

Hello, <span :html="userElement">Guest</span>.

<!-- fragment -->
Hello, <template :html="user.name">Guest</template>.

<!-- instantiate template -->
<template :ref="tpl"><span :text="foo"></span></template>
<div :html="tpl" :with="{foo:'bar'}">...inserted here...</div>

:data="values" 🔌

Include as import 'sprae/directive/data'.

Set data-* attributes. CamelCase is converted to dash-case.

<input :data="{foo: 1, barBaz: true}" />
<!-- <input data-foo="1" data-bar-baz /> -->

:aria="values" 🔌

Include as import 'sprae/directive/aria'.

Set aria-* attributes. Boolean values are stringified.

<input role="combobox" :aria="{
  controls: 'joketypes',
  autocomplete: 'list',
  expanded: false,
  activeOption: 'item1',
  activedescendant: ''
}" />
<!--
<input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant>
-->

Signals

Sprae can take signal values. Signals provider can be switched to any preact-flavored implementation:

import sprae from 'sprae';
import { signal, computed, effect, batch, untracked } from 'sprae/signal';
import * as signals from '@preact/signals-core';

// switch provider to @preact/signals-core
sprae.use(signals);

// use signal as state value
const name = signal('Kitty')
sprae(el, { name });

// update state
name.value = 'Dolly';

Provider | Size | Feature :---|:---|:--- ulive (default) | 350b | Minimal implementation, basic performance, good for small states. @webreflection/signal | 531b | Class-based, better performance, good for small-medium states. usignal | 850b | Class-based with optimizations, good for medium states. @preact/signals-core | 1.47kb | Best performance, good for any states, industry standard. signal-polyfill | 2.5kb | Proposal signals. Use via adapter.

Evaluator

Expressions use new Function as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP. To make eval stricter & safer, as well as sandbox expressions, an alternative evaluator can be used, eg. justin:

import sprae from 'sprae'
import justin from 'subscript/justin'

sprae.use({compile: justin}) // set up justin as default compiler

Justin is minimal JS subset that avoids "unsafe-eval" CSP and provides sandboxing.

Operators:

++ -- ! - + ** * / % && || ?? = < <= > >= == != === !== << >> & ^ | ~ ?: . ?. [] ()=>{} in

Primitives:

[] {} "" '' 1 2.34 -5e6 0x7a true false null undefined NaN

Custom Build

Sprae can be tailored to project needs via sprae/core:

// sprae.custom.js
import sprae, { directive } from 'sprae/core'
import { effect } from 'sprae/signal'
import * as signals from '@preact/signals'
import compile from 'subscript'

// include directives
import 'sprae/directive/if.js'
import 'sprae/directive/text.js'

// custom directive :id="expression"
directive.id = (el, evaluate, state) => {
  effect(() => el.id = evaluate(state))
}

// configure signals
sprae.use(signals)

// configure compiler
sprae.use({ compile })

Justification

Template-parts is stuck with native HTML quirks (parsing table, SVG attributes, liquid syntax conflict etc). Alpine / petite-vue / lucia escape native HTML quirks, but have excessive API (:, x-, {}, @, $) and tend to self-encapsulate.

Sprae holds open & minimalistic philosophy, combining :-directives with emerging signals.

Examples