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

binyjs

v0.4.7

Published

yet another reactive components library

Readme

BinyJS

It is a micro vanilla Javascript project of 1.4kB to help to write reactive UI based on event-driven programming.

npm bundle size

This project was made to handle arrays and calculate the diff on the data, as demonstrated in the "JS-framework" bench test and the todoMVC. The performance is very close to the dedicated Javascript code written for this test. It uses event-driven programming: you write to the state and this triggers events with callbacks.

It uses an immutable state and computes the diff to render the desired DOM elements.

You write HTML as strings with normal interpolation.

It uses state variables. They have the following properties:

  • .val which is a setter and a getter,
  • .target which sets the DOM element which will receive the event triggered by a state change,
  • .resp which sets the desired rendering.

Instead of writing "event" listeners in your HTML (where "event" can be "click" or "submit" or "change" or "input"), you write a dataset and reference a function. For example, data-change="compute".

It uses the key "stateVariable".resp to set the rendered DOM elements.

It relies on unique keys; you need to use the key attribute in the HTML to identify each element of the rendered DOM array. You need to pass to the state variable the unique identifier you use in your data (eg key: "id"). Both "key" are different.

Limitations: it is not fully reactive in the sense that you need to create a separate state variable for computed state. For example, you have a list of todos, completed or not. Suppose you have a counter on the total completed todos as a state variable. In the action where you change the completion of a todo, you need to modify of the counter state accordingly for the counter to be reactive. The "todoMVC" demonstrates this.

Usage

You get state and Actions from the package. You instantiate your state and action functions.

import B from "binyjs"

const todos = B.state({val: [], key: "id"})
const actions = B.Actions({remove: ()=> ...})

Example Counter

We want to render this HTML string:

app.innerHTML = `<div>
    <h1>Hello biny</h1>
    <div>
      <button id="counter" type="button" data-click="inc">
                                            ^^^
      <span data-change="display" id="count"></span>
               ^^^
      </button>
    </div> 
  </div>
`;

We build the state variable "counter", pass the actions into the "Actions" function and set up the target for this state variable. We render an HTML string when we pass it to the .resp key of the state variable.

const counter = B.state({ val: 0 }),
  actions = B.Actions({
    inc: () => (counter.val += 1),
    display: () => {
      // this action targets the "#count" element
      counter.target = count;
              ^^^
      counter.resp = `<span>binyJS state: ${counter.val}</span>`;
              ^^^
    },
  });
// display the initial state on load.
window.onload = () => actions.display();

Ingredients

The state is required to be immutable. The main ingredients are:

  • [state variables] You need to instantiate them. If say "data" is a state variable in the form of a collection of objects [{id: 1, label: "..."},...], then data.val is a setter and getter. When your data is an array, set the unique identifier used by your data to the key "key".
const todoState = B.state({val: [], key: "id"})
data.val = ...
  • [actions] declared in the function B.Actions. You need to set the target to the state variable used in the action.
const actions = B.Actions({
  removeLi: ()=> {
    // the action targets the element "#ulis"
    todoState.target = ulis;
    ...
  },
  ...
})
  • [key] As a convention, Biny uses the attribute key in the HTML string when you render a collection: you need to declare key="${id}" if your data uses "id" as unique identifier. Note that the "is important for the querySelectors. Use it in your selectors.
const TodoItem = ({ id, label }) =>
  `<li key="${id}">
       ^^  ^     ^ 
  <span style="display:flex;">
  <label style="margin-right:10px;">${label}</label>
  <input type="checkbox"/>
  </span>
  </li>`;
  • [data-event] Biny uses the convention data-event for an "event" listener. This means you use data-click="addItem" when the element emits a click event that should run the "addITem" action declared in your "Actions".

  • You can use global listeners (element.addEventListener).

  • [target] Inside your listener, you must declare the target for each reactive state variable. It looks like data.target=tbody. You can also declare extra dependencies via a dataset if your component requires to read data hardcoded in the DOM and read them.

  • [state.resp] You need to return the data, normally HTML strings in the key ".resp".

  • [data-change] is the default callback. For example, you have a form with a data-submit:

<form id="fm" data-submit="addItem"></form>

This triggers the action below:

addItem: (e) => {
  e.preventDefault();
  todoState.target = ulis;
  todoState.val = [...todoState.val, { id: ++i, label: inputState.val }];
  fm.reset(), (inputState.val = "");
},

Since we send a new state, we want to render this new state. The target element "#ulis" contains a data-change="display":

<ul id="ulis" data-change="display"></ul>

Biny will run the callback "display":

display: () => {
  todoState.target = ulis;
  todoState.resp = todoState.val.map((todo) => TodoItem(todo));
},

Reactivity pattern

We use the simple event loop and a "diffing function" to detect the 6 following changes made to the state: "assign", "append", "clear", "remove", "update" and "swap" (rows).

Test

The performance is close to the Vanilla JS code specific for this test to which we compare the Biny package.

Examples

The code for the examples: