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

imperative

v2.0.3

Published

Structured UI Programming

Downloads

42

Readme

Imperative: Stateful Components 20x smaller than React with no VDOM

Imperative.js uses javascript generators to reproduce the advantages of React - reusable, stateful components - without the complexity or code size. It is an almost trivial library of only 1.5kb (minified + gzipped) but no less general than React, and actually more ergonomic in some cases, such as waiting for a fetch call.

An imperative component is an ordinary javascript generator. It can be run by calling run. The optional second argument to run will set the root of the application (defaults to document.body):

let { run } = require("imperative");
run(function*() { yield* H('div', "Hello, world"); });

Instead of JSX, Imperative uses ordinary javascript functions. These can be easily remembered with the mnemonic HASTE. H - HTML element, A - Attribute, S - Style, T - text, E - events.

function* example() {
    // the first argument to H is an element name, after that pass 
    // any number of components or arrays of components.
    yield* H('div',
        // all the HASTE functions return components. This one will set a style
        // on the parent div.
        S('backgroundColor', 'black'),
        // alternative style syntax with objects
        S({'color': 'white'}),
        function*() {
            // the E component will wait for the given event, then return the
            // event object.
            let ev = yield* E('click');
            yield* S('border', '1px solid green');
        },
        T('Example Div'));
}

Imperative uses normal generator control flow. The children of an H call will run in parallel until one of them returns, the return value of that child will return from H.

function*() {
    let color = yield* H('div',
        H('button', T('Red'), function*() { yield* E('click'); return 'Red'; }),
        H('button', T('Blue'), function*() { yield* E('click'); return 'Blue'; }));
    yield* H('div', `You chose the ${color} pill`);
}

Sometimes you want the parallel execution without introducing a DOM parent element. This can be accomplished with multi.

function*() {
    let fetchResult = yield* multi(
        // Fetch = fetch wrapped into an imperative component
        Fetch('/api'),
        H('div', T('Waiting for api...')));
    let jsonResult = yield* multi(
        fetchResult.json(),
        H('div', T('Waiting for response body...')));
    yield* H('div', T(JSON.stringify(jsonResult)));
}

Fetch simply wraps fetch into an imperative generator, adding auto-cancellation. The json and text methods of the response are also wrapped. wait is a similar wrapper for setTimeout, and waitFrame for requestAnimationFrame.

Many UI problems can be solved using a combination of control flow and parallel execution. Sometimes we do need a mechanism to communicate between different pieces of the UI. For this purpose we can use Var. Var has methods:

get - get current value
set - set a new value
next - component, will return with next value
fmap - create a new component with the provided function, each time the value changes
function*() {
    let color = Var('red');
    yield* H('div',
        H('button', T('red'), function*() { 
            while(true) { 
                yield* E('click'); 
                color.set('red'); 
            } 
        }),
        H('button', T('blue'), function*() { 
            while(true) { 
                yield* E('click'); 
                color.set('blue'); 
            } 
        }),
        H('div', color.fmap(current => T(`you chose the ${current} pill`))));
}

These functions - run, HASTE, Var, multi, Fetch, wait - are the high-level API of imperative. For low-level operations you need to understand what the generators are doing. An imperative component is a generator that yields functions of the form {H, cleanup} => Promise. The parent will wait for the promise, then return its result to the generator. cleanup allows you to register cleanup functions which will run when the generator finishes, or when it is cut off by a parallel generator finishing. H in this context is different from the main H, in a yield function the H has signature DomElement => DomElement and allows you to access the parent dom element directly.

Here is an example of using the low-level API to implement intersection observers.

function* visible(threshold) {
    return yield ({H, cleanup}) => new Promise(resolve => {
        H(elem => {
            let ob = new IntersectionObserver(([entry]) => {
                if(entry.isIntersecting) {
                    resolve(entry);
                }
            }, {threshold}).observe(elem);
            cleanup(() => ob.disconnect());
        })
    });
}

Another low-level function is local. local allows you to change the set of options being passed down into each generator. The HTML DOM-specific API of imperative is implemented on top of multi and local, and it is equally easy to use any other type of persistent UI tree, e.g. within a canvas element.