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

@ryanmorr/avalon

v1.0.1

Published

JavaScript micro-framework for building single page web applications

Downloads

65

Readme

avalon

Version Badge License Build Status

JavaScript micro-framework for building single page web applications

Install

Download the CJS, ESM, UMD versions or install via NPM:

npm install @ryanmorr/avalon

Usage

Avalon is an all-in-one solution to manage state, routing, and views for web apps:

import avalon from '@ryanmorr/avalon';

const app = avalon({
    count: 0
});

app.mutation('increment', ({count}) => {
    return {
        count: count + 1
    };
});

app.action('handleClick', ({commit}) => {
    commit('increment');
});

app.view(parent, (html, {count}, dispatch) => html`
    <div>
        <p>Count: ${count}</p>
        <button onclick=${dispatch('handleClick')}>Increment</button>
    </div>
`);

Check out the TodoMVC example.

API

avalon(state?)

Create an application instance with an initial state as a plain key/value object. The title property is reserved for the current document title, changing it will automatically update the document title:

const app = avalon({
    title: 'Hello World',
    foo: 1,
    bar: 2
});

mutation(name, callback)

Define a mutation by providing a name and a callback function that synchronously changes state by returning a partial state object that will be merged into the application state:

app.mutation('foo', (state, payload) => {
    return {
        foo: payload
    };
});

commit(name, payload?)

Call a mutation to update application state by providing its name and an optional payload, returns the partial state that resulted from the mutation:

app.mutation('foo', (state, n) => ({foo: n + 10}));

app.commit('foo', 10); //=> {foo: 20}

action(name, callback)

Define an action by providing a name and a callback function that can be used to respond to DOM event listeners, perform async operations, dispatch other actions, commit mutations, etc. The action callback function is provided an object of relevant data and convenience functions as the first parameter:

app.action('foo', ({state, params, event, dispatch, commit, navigate, redirect, emit}) => {
    /**
     * state - the current state of the app
     * params - key/value object provided to the dispatcher or null if not provided
     * event - the event object of user triggered DOM events or null if not applicable
     * commit - function for calling mutations
     * dispatch - function for dispatching actions or routes
     * navigate - function for navigating to a URL path and dispatching a route
     * redirect - function for redirecting to a URL path and dispatching a route
     * emit - function for emitting a custom event
     */
});

To better support async operations, define a second parameter for resolving a call (and optionally a third parameter for rejecting a call) as part of the action callback function's signature. Dispatching an async action will automatically return a promise. Here's an example of how you might implement an async action to fetch data from the server:

app.action('load', ({params, commit}, resolve, reject) => {
    commit('isLoading', true);
    request('/get', params).then((data) => {
        commit('isLoading', false);
        commit('setData', data);
        resolve(data);
    }).catch((error) => {
        commit('isLoading', false);
        commit('setError', error);
        reject(error);
    });
});

app.dispatch('load', {id: 'foo'}).then((data) => {
    // Handle data
}).catch((error) => {
    // Handle error
});

route(path, callback)

Routes work exactly like actions, except they also respond to changes in the URL path, such as user-triggered click events and form submits, programmatic calls to the navigate and redirect methods, and moving forwards and backwards in the session history stack. A route must be defined with a leading forward slash:

app.route('/', ({state, path, params, event, dispatch, commit, navigate, redirect, emit}) => {
    /**
     * state - the current state of the app
     * path - the URL path that matched the route
     * params - key/value object extracted from a route's parameters or null if static path
     * event - the event object of user triggered DOM events or null if not applicable
     * commit - function for calling mutations
     * dispatch - function for dispatching actions or routes
     * navigate - function for navigating to a URL path and dispatching a route
     * redirect - function for redirecting to a URL path and dispatching a route
     * emit - function for emitting a custom event
     */
});

Routes support parameters, optional parameters, and wildcards:

// Matches routes like "/a/b/c" and "/x/y/z"
app.route('/:foo/:bar/:baz', ({params: {foo, bar, baz}}) => {
    // Do something
});

// Matches routes like "/a/b" and "/a"
app.route('/:foo/:bar?', ({params: {foo, bar}}) => {
    // Do something
});

// Matches routes like "/", "/a", and "/a/b/c"
app.route('/*', ({params: {wildcard}}) => {
    // Do something
});

dispatch(name?, params?)

Dispatch an action with optional parameters or a route. If no arguments are provided the current URL path is used by default. Returns the return value of the action/route callback function or a promise if it's an async action/route.

// Dispatch an action with parameters
app.dispatch('foo', {foo: 1, bar: 2});

// Dispatch the first matching route (parameters are extracted from the URL path)
app.dispatch('/foo/bar/baz');

// Dispatching an async action/route returns a promise
app.dispatch('load').then((data) => {
    // Do something
})

view(element, callback)

Define a view to be immediately rendered and automatically updated via virtual DOM when the state changes. The view callback function is provided a virtual DOM builder via tagged templates, the current state, and a convenient dispatch function for dispatching actions and routes as the result of a DOM event listener with optional parameters as a key/value object:

app.view(parentElement, (html, state, dispatch) => html`
    <div>
        <p>Name: ${state.name}</p>
        <button onclick=${dispatch('handleClick', {foo: 1, bar: 2})}>Increment</button>
    </div>
`);

Views support attributes/properties, CSS styles as a string or object, event listeners indicated by a prefix of "on", keyed nodes for efficient list diffs, and stateless functional components:

const Item = (html, props, dispatch) => html`
    <li key=${props.id} onclick=${dispatch('handleClick', {id: props.id})}>
        ${props.children}
    </li>
`;

app.view(parentElement, (html, state) => html`
    <ul class="list">
        ${state.items.map((item) => html`
            <${Item} id=${item.id}>${item.name}<//>
        `)}
    </ul>
`);

navigate(path)

Pushes a new entry onto the history stack with the provided URL path and dispatches the first matching route. Returns the return value of the route callback function or a promise if it's an async route:

app.route('/foo', () => 'bar');

app.path(); //=> "/"
history.length; //=> 0

app.navigate('/foo'); //=> "bar"

app.path(); //=> "/foo"
history.length; //=> 1

redirect(path)

Replaces the current history entry with the provided URL path and dispatches the first matching route. Returns the return value of the route callback function or a promise if it's an async route:

app.route('/foo', () => 'bar');

app.path(); //=> "/"
history.length; //=> 0

app.redirect('/foo'); //=> "bar"

app.path(); //=> "/foo"
history.length; //=> 0

on(name, callback)

Subscribe to application events, returns a function to remove that specific listener:

// Listen for state changes
app.on('mutation', (name, nextState, prevState, partialState) => {
    // Do something
});

// Listen for when an action/route is dispatched
app.on('dispatch', (type, state, name, params, event, returnValue) => {
    // Do something
});

// Listen for when the URL path changes
app.on('pathchange', (path) => {
    // Do something
});

// Listen for when a view has been rendered
app.on('render', (parentElement) => {
    // Do something
});

// Define your own custom event with parameters
const stop = app.on('foo', (a, b, c, d) => {
    // Do something
});

// Stop listening for custom event
stop();

emit(name, ...args?)

Trigger a custom event with optional arguments:

app.on('foo', (a, b, c, d) => {
    // Do something
});

app.emit('foo', 1, 2, 3, 4);

state()

Get the current state object:

const app = avalon({
    title: 'Hello World'
    foo: 1,
    bar: 2
});

app.state(); //=> {title: "Hello World", foo: 1, bar: 2}

path()

Get the current URL path:

app.navigate('/foo');

app.path(); //=> "/foo"

use(plugin)

Add a plugin by providing a callback function that is immediately invoked with the application instance and the current state. Returns the return value of the plugin callback function:

// A simple logging plugin
const log = app.use((app, state) => {
    const events = [
        'mutation',
        'dispatch',
        'pathchange',
        'render'
    ];
    events.forEach((name) => app.on(name, console.log.bind(console, name)));
    return console.log.bind(console, 'avalon');
});

License

This project is dedicated to the public domain as described by the Unlicense.