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 🙏

© 2025 – Pkg Stats / Ryan Hefner

mainapp

v5.1.0

Published

Mainapp is a micro web application framework

Readme

You are now in control

mainapp is a micro web application framework, where the control flow starts and is never lost. mainapp implements a state container that you actually want to use. Update state without the ceremony.

Getting started

Here is a simple counter application.

import {h, App} from 'mainapp'

App({
  count: 0,

  down ({count}, value) {
    return {
      count: count - value
    }
  },

  up ({count}, value) {
    return {
      count: count + value
    }
  },

  view ({count, down, up}) {
    return <div>
      <h1>{count}</h1>
      <button onclick={() => down(1)}>-</button>
      <button onclick={() => up(1)}>+</button>
    </div>
  }
}, document.getElementById('mainapp-entry'))

mainapp is compatible with picostyle.

Main loop

All webapps have the same main loop. Let's learn how mainapp implements the main loop.

State tree

The state tree is the whole application. The state tree has both the data and code (views and actions) in the same place. This mean the application can rewrite itself, which is useful for code splitting.

Views

When state is updated, views functions are used to render the application. They return DOM fragments based on the current state tree.

Actions

Actions are functions on the state tree that implement application logic and update the state tree. They are triggered by events or other actions. Actions take the current state tree and return an update. Actions can be asynchronous.

Updating the state tree

Let's take a closer look at the counter sample and show how the state tree changes.

The initial state tree is...

{
  count: 0,
  down ({count}, value) {
    return {
      count: count - value
    }
  },
  up ({count}, value) {
    return {
      count: count + value
    }
  },
  view ({count, down, up}) {
    return <div>
      <h1>{count}</h1>
      <button onclick={() => down(1)}>-</button>
      <button onclick={() => up(1)}>+</button>
    </div>
  }
}

mainapp uses view to render user interface. view receives the current state tree as the first argument into the function. We destructure it to get the current count and two actions. The initial DOM would be...

<div>
  <h1>0</h1>
  <button onclick="() => down(1)">-</button>
  <button onclick="() => up(1)">+</button>
</div>

Let's say the user clicks on the + button. First of all, notice how mainapp uses the native onclick event handler and how its bound to the up action. Looking at the implementation of the up action, we notice similar to the view, the first argument is the current state tree. But wait, in the event handler, we don't pass in the state tree? This is the magic the makes mainapp easy to use. When defining actions we get access to the state tree, but when calling the action, we don't need to worry about it. This is an example of partial application in action.

When the up action is called, it returns {count: 0 + 1}. We don't need to return the whole state tree, we just return what we want to update, in this case the count. This will update the state tree to be...

{
  count: 1,
  ...rest of state tree is unchanged
}

mainapp automatically renders the user interface after the state tree is updated.

Components

mainapp supports components as substate trees. For example, we can extract Counter as a component...

import {h, App} from 'mainapp'

const Counter = {
  count: 0,
  down ({count}, value) {
    return {
      count: count - value
    }
  },
  up ({count}, value) {
    return {
      count: count + value
    }
  },
  view ({count, down, up}) {
    return <div>
      <h1>{count}</h1>
      <button onclick={() => down(1)}>-</button>
      <button onclick={() => up(1)}>+</button>
    </div>
  }
}

App({
  Counter,
  view({Counter}) {
    return <Counter/>
  }
}, document.getElementById('mainapp-entry'))

Each component has their own state. We use the Counter component multiple times and each will have their own count. Notice how the root view renders the Counter using <Counter/>.

Lifecycles

Components can declare lifecycle actions didMount and willUnmount.

  • didMount(state, parentKey): an action on a child component which is called once after mounted on the parent. The parentKey is the key on the parent where the child component was mounted. Use this lifecycle to do one time setup with external APIs.
  • willUnmount(state, parentKey): an action on a child component which is called once before unmounted from the parent. The parentKey is the key on the parent where the child component was mounted. Use this lifecycle to clean up things setup with didMount. willUnmount will be called transitively the entire component sub-tree if ancestor is umounted.

Here is an example on how to use didMount and willUnmount to attach and clean up event listeners.

const {mountOnline, unmountOnline} = App({
  mountOnline() {
    return {
      Online: {
        online: 'unknown',
        wentOnline() {
          return {
            online: 'yes'
          }
        },
        didMount({wentOnline}) {
          window.addEventListener('online', wentOnline)
        },
        willUnmount({wentOnline}) {
          window.removeEventListener('online', wentOnline)
        },
        view({online}) {
          return <p>Online: {online}</p>
        }
      },
    }
  },
  unmountOnline() {
    return {
      Online: undefined
    }
  },
  view({Online}) {
    return {Online && <Online/>}
  }
}, document.getElementById('mainapp-entry'))

await mountOnline()
// didMount fires

await unmountOnline()
// willUnmount fires

Inversion of control

Applications are simpler when the control flow is clear. When the control flow is spread all over the application, it is much harder to change. mainapp allows you to keep control at the root state tree.

While its easy for parent nodes in the state tree to call actions on children nodes, we need a way for child nodes to call actions on their parent or even on the top of the state tree. While this seems dangerous, in practise all webapps need to do this.

Parent and global access

All components have access to their parent state tree via $parent and the global state tree via $global.

import {h, App} from 'mainapp'

const Grandchild = {
  name: 'grandchild',
  view({$global: {name: rootName}, $parent: {name: parentName}, name}) {
    return <div>
      <p>Root name: {rootName}</p>
      <p>Parent name: {parentName}</p>
      <p>Own name: {name}</p>
    </div>
  }
}

const Child = {
  name: 'child',
  Grandchild,
  view({Grandchild}) {
    return <Grandchild/>
  }
}

App({
  name: 'root',
  Child,
  view({Child}) {
    return <Child/>
  }
}, document.getElementById('mainapp-entry'))

This will render...

<div>
  <p>Root name: root</p>
  <p>Parent name: child</p>
  <p>Own name: grandchild</p>
</div>

Component actions can also update $global and $parent.

More samples