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 🙏

© 2026 – Pkg Stats / Ryan Hefner

o1framework

v1.0.2

Published

simple reactive DOM micro-framework

Readme

o1

Reactive DOM micro-framework. No virtual DOM, no build step required, no JSX. Just HTML attributes and plain JavaScript.

Built with LiveScript + prelude-ls. Bundles to ~7 KB gzipped.

Quick start

<span o-mark="count">0</span>
<button onclick="data.count++">+</button>

<script src="dist/o1.min.js"></script>
<script>
  const en = O1.default;
  const data = en.init();
  data.count = 0;
</script>

Or via npm:

npm install o1
import { default as en } from 'o1';

const data = en.init();
data.count = 0;

How it works

Assign a property on the reactive data object — every DOM element bound via o-* attributes updates automatically. The reactivity engine uses Proxy to intercept mutations and an O(1) registry to find bound elements without querying the DOM.

Directives

o-mark — text binding

<span o-mark="user.name">placeholder</span>

Sets textContent when the bound value changes. Objects are serialized to JSON.

o-model — two-way binding

<input type="text" o-model="name" />
<input type="checkbox" o-model="agreed" />
<select o-model="country">
  <option value="pl">Poland</option>
  <option value="us">USA</option>
</select>

Supports text, number, checkbox, radio, select, and textarea. Updates flow in both directions: data to DOM and DOM to data.

o-if / o-ifnot — conditional rendering

<template o-if="loggedIn">
  <p>Welcome back!</p>
</template>

<template o-ifnot="loading">
  <p>Content loaded.</p>
</template>

Wraps content in a <template> and inserts/removes children based on a truthy/falsy value.

o-show — toggle visibility

<span o-show="isVisible">Now you see me</span>

Sets display: none when the value is falsy, clears the inline display when truthy. Lighter than o-if — the element stays in the DOM.

o-bind — dynamic attributes

<a o-bind="url:href">Link</a>
<button o-bind="isDisabled:disabled">Click</button>
<img o-bind="imageUrl:src" />

Parametric directive — the part after : is the attribute name. Boolean handling: true sets the attribute with an empty value, false/null/undefined removes it.

o-on — declarative events

<button o-on="handleClick:click">Click me</button>
data.handleClick = en.fn(() => { data.count++; });

Parametric directive — the part after : is the event name. Use en.fn() to store event handlers in reactive data without them being treated as computed values.

Arrays

Use # as an index placeholder:

<ul>
  <li o-mark="items.#">
    <span o-mark="items.#.name"></span>
  </li>
</ul>
data.items = [{ name: 'Alice' }, { name: 'Bob' }];

The framework clones the template element for each array item and rewrites # to the actual index. On mutation it only touches affected elements.

Array mutating methods work reactively — push, pop, shift, unshift, splice, sort, reverse all trigger DOM updates automatically:

data.items.push({ name: 'Charlie' });
data.items.splice(1, 1);
data.items.sort();
data.items.reverse();

Custom directives

en.directive('color', ({ el, value }) => {
  el.style.color = String(value);
});
<span o-color="themeColor">Hello</span>

Parametric directives accept an inline argument after a colon:

en.directive('style', ({ el, value, param }) => {
  el.style.setProperty(param, String(value));
}, true);
<div o-style="bg:background-color"></div>

Computed values

data.firstName = 'Jan';
data.lastName = 'Kowalski';
data.fullName = en.computed(() => `${data.firstName} ${data.lastName}`);

Dependencies are tracked automatically. Async functions are supported — stale results from earlier calls are discarded.

data.post = en.computed(async () => {
  const res = await fetch(`/api/posts/${data.postId}`);
  return res.json();
});

Watchers

en.watch('count', value => console.log('count changed to', value));

// Remove a specific watcher
en.unwatch('count', myCallback);

// Remove all watchers for a key
en.unwatch('count');

// Remove all watchers
en.unwatch();

Ancestor watchers fire too — watching user triggers when user.name changes.

Batching

en.batch(() => {
  data.x = 1;
  data.y = 2;
  data.z = 3;
});
// Single DOM update cycle for all three changes

Multiple instances

import { createInstance } from 'o1';

const en1 = createInstance();
const data1 = en1.init();

const en2 = createInstance();
en2.prefix('app');        // uses app-mark, app-model, etc.
const data2 = en2.init();

Each instance has its own reactive context, watchers, directives, and registry.

Components

Define reusable Web Components via named templates:

<template name="user-card">
  <style>
    .card { border: 1px solid #ccc; padding: 1rem; }
  </style>
  <div class="card">
    <slot name="name">Unknown</slot>
  </div>
</template>

<user-card>
  <span slot="name" o-mark="userName">Alice</span>
</user-card>

Templates are auto-registered as custom elements with Shadow DOM. External templates can be loaded:

await en.load('components/card.html');

API reference

| Method | Description | |--------|-------------| | en.init() | Initialize reactive root, scan DOM, return reactive data object | | en.computed(fn) | Create a computed value with automatic dependency tracking | | en.watch(key, fn) | Watch a reactive key for changes | | en.unwatch([key], [fn]) | Remove watchers (specific, by key, or all) | | en.directive(name, cb, isParametric?) | Register a custom directive | | en.prefix(value?) | Set the attribute prefix (default: o-) | | en.batch(fn) | Batch multiple updates into one DOM cycle | | en.load(files) | Load external template files (returns Promise) | | en.register(root?) | Register component templates from HTML string or DOM element | | en.fn(f) | Wrap a function for use as event handler in reactive data | | en.nextTick() | Returns a Promise that resolves after the current DOM update cycle | | en.destroy() | Tear down instance: remove listeners, clear state |

Building

npm run build      # compile LiveScript + bundle with esbuild
npm test           # run vitest suite

Produces three bundles:

  • dist/index.mjs — ES modules
  • dist/index.cjs — CommonJS
  • dist/o1.min.js — IIFE (exposes window.O1)

Examples

See the examples/ directory for working demos: counter, todo list, async computed, components, routing, model binding, and more.

License

MIT