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

equiljs

v0.1.0

Published

EquilJS — Deterministic, signal-based UI runtime. No VDOM, no hidden re-renders, no lifecycle surprises.

Downloads

25

Readme


What is EquilJS?

A zero-dependency UI framework built on fine-grained signals, transactional commits, and explicit scopes. No virtual DOM, no hidden re-renders, no lifecycle surprises.

import { signal, derived, createScope, createEffect } from "equiljs";

const count  = signal(0);
const double = derived(() => count.get() * 2);

const scope = createScope();
createEffect(scope, () => {
  console.log(`${count.get()} × 2 = ${double.get()}`);
});

count.set(5); // logs: 5 × 2 = 10

Install

npm install equiljs

Import paths

// Everything
import { signal, derived, h, render, Router } from "equiljs";

// Only what you need
import { signal, derived, transaction } from "equiljs/core";
import { h, Fragment, render }           from "equiljs/dom";
import { Router, Route, Link, navigate } from "equiljs/router";

TypeScript + JSX setup

Add these compiler options to your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment",
    "moduleResolution": "node"
  }
}

Then import h and Fragment in any .tsx file:

import { h, Fragment } from "equiljs/dom";

60-Second Tutorial

1. Create a signal

import { signal } from "equiljs/core";

const name = signal("Alice");
name.get();        // "Alice"
name.set("Bob");   // updates all subscribers

2. Derive computed values

import { signal, derived } from "equiljs/core";

const count  = signal(3);
const double = derived(() => count.get() * 2);

double.get(); // 6 — recomputed lazily when count changes

3. React to changes with effects

import { signal, createScope, createEffect } from "equiljs/core";

const scope = createScope();
const temp  = signal(72);

createEffect(scope, () => {
  console.log(`Temperature: ${temp.get()}°F`);
});
// logs immediately: Temperature: 72°F

temp.set(85);
// logs: Temperature: 85°F

4. Batch updates in a transaction

import { signal, transaction } from "equiljs/core";

const x = signal(0);
const y = signal(0);

transaction(() => {
  x.set(10);
  y.set(20);
});
// effects run ONCE after both values are committed

5. Render to the DOM

import { h, render } from "equiljs/dom";
import { signal } from "equiljs/core";

function Counter() {
  const count = signal(0);
  return (
    <div>
      <p>Count: {() => count.get()}</p>
      <button onClick={() => count.set(count.get() + 1)}>+1</button>
    </div>
  );
}

render(() => <Counter />, document.getElementById("root")!);

API Reference

Core (equiljs/core)

| Export | Description | |---|---| | signal(init) | Create a reactive state holder. .get() reads, .set(v) writes. Same-value sets are no-ops. | | derived(fn) | Create a lazily-evaluated computed value. Dependencies tracked automatically. | | transaction(fn) | Batch multiple .set() calls — effects run once after commit. | | createScope() | Create a lifecycle scope that owns effects and cleanup callbacks. | | createEffect(scope, fn) | Register a reactive side-effect. Re-runs when dependencies change. Return a function for cleanup. | | onMount(scope, fn) | Run fn once after scope creation (async, via microtask). | | onCleanup(scope, fn) | Register a teardown callback. Runs in LIFO order on disposeScope(). | | disposeScope(scope) | Dispose the scope: stop all effects, run cleanups in LIFO order. |

DOM (equiljs/dom)

| Export | Description | |---|---| | h(tag, props, ...children) | JSX factory. Creates DOM elements with reactive props and children. | | Fragment | JSX fragment — groups children without a wrapper element. | | render(factory, container) | Mount a component tree into a DOM container. |

Router (equiljs/router)

| Export | Description | |---|---| | Router | Root component that provides routing context. | | Route | Renders its component when the path matches. | | Switch | Renders the first matching route from an array. | | Link | Anchor that navigates without full page reload. | | navigate(path) | Programmatic navigation. | | useRouter() | Access the current path signal and params. |


JSX & DOM

Components

Any function that accepts props and returns a Node:

function Badge({ label, color }: { label: string; color: string }) {
  return <span style={{ backgroundColor: color }}>{label}</span>;
}

Reactive children

Wrap signal reads in a function to make them reactive:

<p>{() => count.get()}</p>        // updates when count changes
<p>{"static text"}</p>            // never updates

Conditional rendering

<div>{() => isLoggedIn.get() ? <Dashboard /> : <Login />}</div>

Lists

<ul>
  {() => items.get().map(item => <li>{item.name}</li>)}
</ul>

Event handlers & refs

<input
  onInput={(e) => query.set(e.target.value)}
  ref={(el) => el.focus()}
/>

Router

import { h, render } from "equiljs/dom";
import { Router, Switch, Link, navigate } from "equiljs/router";

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      <Switch routes={[
        { path: "/",          component: Home },
        { path: "/user/:id",  component: UserProfile },
        { path: "*",          component: NotFound },
      ]} />
    </Router>
  );
}

render(() => <App />, document.getElementById("root")!);

Patterns: /user/:id (named param), /docs/* (wildcard), * (catch-all)

Programmatic navigation:

navigate("/user/42");

Guides

Reactivity model

Signals are the single source of truth. derived values recompute lazily on read. Effects re-run synchronously when their dependencies change (or after a transaction commits).

signal.set() → mark derived dirty → schedule effects → run effects (registration order)

Transaction model

Inside transaction(), signal writes are deferred. After the function returns:

  1. Apply all dirty signal values
  2. Mark dependent deriveds dirty
  3. Run effects in registration order — each effect sees the fully committed state

Nested transactions delegate to the outermost one.

Scope lifecycle

const scope = createScope();

createEffect(scope, () => { /* tracked */ });
onMount(scope, () => { /* runs once, async */ });
onCleanup(scope, () => { /* runs on dispose, LIFO */ });

disposeScope(scope); // stops effects, runs cleanups

Error safety

| Error | When | |---|---| | PureRenderViolationError | signal.set() called inside a derived computation | | CircularDependencyError | A derived value depends on itself (directly or indirectly) |

Both are thrown synchronously and immediately — no silent failures.


Why EquilJS?

| | EquilJS | React | Solid | Svelte | |---|---|---|---|---| | Reactivity | Fine-grained signals | VDOM diffing | Fine-grained signals | Compiler-based | | Update granularity | Exact DOM node | Component tree | Exact DOM node | Component tree | | State mutations | Explicit .set() | setState / reducer | Explicit .set() | Assignment (=) | | Render purity | Runtime-enforced | Convention only | Convention only | N/A | | Bundle size | ~3 KB | ~40 KB | ~7 KB | ~2 KB (compiled) | | Dependencies | 0 | 0 | 0 | 0 (runtime) |


Project structure

equiljs/
├── src/
│   ├── core/          # Reactive engine (platform-agnostic)
│   ├── dom/           # DOM renderer + JSX factory
│   ├── router/        # Client-side router
│   ├── types/         # JSX type declarations
│   └── index.ts       # Main entry — re-exports everything
├── tests/             # 15 tests across 7 files
├── examples/          # Runnable demos
└── tools/             # Dev server (esbuild)

Contributing

git clone https://github.com/ash/equiljs.git
cd equiljs
npm install
npm test             # 15 tests, zero dependencies
npm run demo         # counter + clock demo
npm run serve        # dev server at localhost:3000

License

MIT