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

grainbox

v1.1.1

Published

A minimal reactivity solution sans compiling or bundling, allowing SPAs to be built without compilers, bundlers or even NPM.

Downloads

199

Readme

grainbox

A fine-grained reactivity solution sans compiling or bundling, allowing SPAs to be built without compilers, bundlers or even NPM. (30 kB)

There are multiple pieces needed to build an SPA, but at the core of grainbox is the reactivity:

  1. Wrap an object with reactive() to make it an observable.
  2. Wrap a function with reactive() to make it recompute whenever an observable changes.

Inner Workings

grainbox uses the built-in Proxy object to box values. Functions that make calls to a proxy's getter are observers of that proxy, and calling a proxy's setter causes its observers to recompute.

Overview

grainbox is a collection of pieces necessary to make a single page app (SPA) without compilers or bundlers, and possibly without NPM.

| Sub-Package | Description | |---|---| | grainbox/reactivity | Reactive state management similar to mobx. | | grainbox/history | Reactive history. | | grainbox/routing | Reactive routing. | | grainbox/hyperscript | A custom implementation of hyperscript with support for es modules, and it adds support for these props: ref, onmount, unmount, disabled, checked, class. | | grainbox/html-tag | If you are not using JSX (maybe because you are avoiding compilers or bundlers), you can use this html template tag literal which was made by combining grainbox/hyperscript with standard htm. |

Import

From node_modules

Most of the functions are exported from a single place:

import grainbox from 'grainbox'
import {
  reactive,
  history,
  registerRoute,
  html,
  h,
} from 'grainbox'

There are sub-packages which have additional exports. They can be imported using a subpath or a direct path to the file in either one of the dist/esm or dist/cjs folders.

// subpath imports:
import * as reactivity from 'grainbox/reactivity'
import * as history from 'grainbox/history'
import * as routing from 'grainbox/routing'
import * as hyperscript from 'grainbox/hyperscript'
import * as htmlTag from 'grainbox/html-tag'

// direct imports:
import * as reactivity from 'grainbox/dist/esm/reactivity.mjs'
import * as reactivity from 'grainbox/dist/cjs/reactivity.js'

From CDN

Using a CDN, NPM isn't needed anymore in order to build an SPA. It all just works, out of the box, thanks to ES Modules.

import grainbox from 'https://unpkg.com/grainbox'

Some points to make about delivery:

  • import grainbox is ~30 kB. It is not currently minified.
  • Instead of using a CDN, grainbox can be used with web-imports to reliably serve node_modules to the client.

Usage

grainbox should be easy to pick up if you are familiar with observable-observer mechanisms. Here is a comparison against an example from mobx:

mobx

import {observable, computed} from 'mobx'

class Proto {
  @observable value = 0
  @computed get valueAsString() {
    return value.toString()
  }
}

const obj = new Proto()

autorun(() => {
  console.log(obj.valueAsString)
})

obj.value++

When obj.value++ runs, the autorun will log it to the console.

grainbox

This how the mobx example above would be implemented using grainbox's reactive():

import {reactive} from 'grainbox'

const obj = reactive({
  value: 0 
})

const valueAsString = reactive(() => {
  return obj.value.toString()
})

reactive(() => {
  console.log(valueAsString())
})

obj.value++

Examples

Creating Observables

// The only things that can be wrapped are objects and functions:
const ro = reactive({})
const rf = reactive(() => {})

Listening to Observables

const ro = reactive({value: 0})
const rf = reactive(() => {
  // Calling the getter causes it to become linked.
  return ro.value
})
reactive(() => {
  // Calling a function will also cause it to become linked.
  rf()
})

Fine grained DOM updates

<body>
  <script type="module">
    import {reactive} from 'https://unpkg.com/grainbox'
  
    const valueSpan = document.getElementById('value')
  
    const store = reactive({value: 0})
    reactive(() => {
      valueSpan.innerHTML = store.value.toString()
    })
  
    window.add = () => {
      store.value++
    }
  
    window.sub = () => {
      store.value--
    }
  </script>
  
  <span id="value">0</span>
  <button onclick="sub()">-</button>
  <button onclick="add()">+</button>
</body>

Run the code above: https://unpkg.com/grainbox/examples/fine-grained-reactivity.html

Reactive JSX Components

<body>
  <script type="module">
    import {reactive, html} from 'https://unpkg.com/grainbox'

    let count = reactive({
      counter: 0
    })

    // If the wrapped function's return value is a DOM element,
    // reactive use its .replaceWith method to cause this DOM element to update.
    const View = reactive(() => html`<span>Count: ${count.counter}</span>`)

    const App = () => html`
      <div>
        <h2>counter using reactive</h2>
        <${View}/>
        <button
          onclick=${() => {
            console.log('increment')
            count.counter++
          }}
        >
          Click
        </button>
      </div>
    `

    document.body.appendChild(App())
  </script>
</body>

Run the code above: https://unpkg.com/grainbox/examples/using-components.html

Reactivity

In addition to reactive, there are additional functions which are exported from grainbox/reactivity:

export {
  reactive, // converts input into a proxy
  isReactive, // checks if something was wrapped with reactive
  fromPromise, // allows reactive functions to react to promises
  hasDependent, // a isDependent on b
  getDependents, // list of reactive objects and functions
  getCreationContext // useful for checking identity   
};

History and Routing

Usually, the reactivity solution is tied into history and routing. Included in grainbox are solutions for these.

JSX

Supporting JSX currently requires compilation, however, browser may support it one day.

If you would like to use JSX instead of html template tag literals, you can do so using the jsx-to-hyperscript package. Then, h must be present in any file which has JSX. This is similar to how React has to be present in any file which has JSX.

// These imports are analogous to each other with respect to JSX being present in the file.  
import {React} from 'react'
import {h} from 'grainbox'

// `jsx-to-hyperscript` will transforms this into: const element = h('div')
const element = <div/>

Notes

Package Exports

  • unpkg.com uses the unpkg field.
  • esm.run uses the exports field, using the default conditional.
  • jsdelivr uses the main field.