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

sigmx

v0.2.0

Published

htmx on steroids in a runtime smaller than htmx: server-driven HTML, streaming patches, signals and a state-preserving morph from a 4 KB core. Zero dependencies.

Readme

sigmx

CI

htmx on steroids, in a runtime smaller than htmx.

Status: an experiment. sigmx is a personal exploration of what a hypermedia library looks like when it starts from a tiny, plugin-based core. It is inspired by htmx (the server sends HTML, the browser swaps it in), Alpine.js (declarative data-* attributes with a little reactivity) and Datastar (which fused the two and whose attribute syntax sigmx keeps for compatibility). The code is original, the APIs may still change, and it has not been used in production. Try it, break it, and open an issue with what you find.

Your server renders HTML, as it always has. The browser asks for more of it with @get and @post, and sigmx merges the response into the page with a morph that keeps focus, typed input and scroll position. When you want live updates, the same request can return a stream of patches instead. Signals, two-way binding and reactive attributes cover the client-side state. You write data-* attributes and register only the plugins you use.

  • Tiny. 4.5 KB core, 9.0 KB with the essentials, 11.5 KB with all 50 plugins (brotli). Zero runtime dependencies.
  • Server-driven. Return plain HTML from any endpoint and it is morphed into the page by id, or targeted with a header. Return JSON to merge signals, or an event stream to push many patches over one request, with Last-Event-ID reconnects.
  • Reactive where it matters. Signals declared in markup; bind, show, class, computed values and effects keep the page in step. No component model, no virtual DOM.
  • Pay for what you use. Every directive and function is an opt-in plugin, and auto mode scans your source to bundle exactly the set it finds.
  • Batteries from the ecosystem. The Alpine plugins (collapse, mask, teleport, …) and htmx extensions (boost, ws, remove-me, …) people bolt on are built in as plugins.
  • Datastar-compatible markup. Existing Datastar templates run unchanged.
<div data-signals="{ count: 0, name: '' }">
  <input data-bind:name placeholder="Your name" />
  <p data-show="$name">Hello, <span data-text="$name"></span>!</p>
  <button data-on:click="$count++">Clicked <b data-text="$count"></b> times</button>
  <button data-on:click="@post('/save')" data-indicator:saving data-attr:disabled="$saving">Save</button>
</div>

Install

npm install sigmx
import { createSigmx } from 'sigmx';
import { essentials } from 'sigmx/presets/essentials';

createSigmx({ plugins: essentials });

Or pick plugins by hand (sigmx/plugins is tree-shakeable), use sigmx/presets/minimal or sigmx/presets/all, let auto mode choose, or drop in the script-tag build:

<script type="module" src="/sigmx.standalone.js"></script>

Astro users: npm install sigmx @sigmx/astro and add sigmx() to the integrations; see sdks/astro. Hono users: npm install sigmx @sigmx/hono, then app.use(sigmx()) gives every handler c.var.sigmx; see sdks/hono. Everyone else: sigmx/server has the same event builders, responses, streams and signal reading as plain Fetch-API code.

How it works

Attributes are data-<plugin>[:key][__modifier.arg]="expression". Expressions are plain JavaScript in which $name is a signal:

<button data-on:click__debounce.300ms="@get('/search')">Search</button>
<li data-class:active="$page === 3"></li>
<input data-bind:user.email />
<p data-text="`${$items.length} items, ${@intl('NumberFormat', $total, { style: 'currency', currency: 'EUR' })}`"></p>

State is one JSON-like store addressed by dotted paths and patched with merge-patch semantics: objects merge, null removes, arrays notify on push. Requests send the store (minus _-prefixed paths) and apply whatever comes back, decided by the response's content type:

| the server returns | sigmx does | | ------------------- | ------------------------------------------------------------------------------------------------------------------- | | text/html | morphs it into the page: top-level elements by id, or a target from the sigmx-selector and sigmx-mode headers | | application/json | merges it into the signals | | text/event-stream | applies each event as it arrives: patch-elements, patch-signals, or your own handlers |

The everyday case is the first row: render a partial with whatever templating you already use and return it. The morph keeps every element whose id appears on both sides, so a re-rendered form keeps what the user typed. Streams are there for progress, chat and dashboards, where one request should keep delivering:

event: sigmx-patch-signals
data: signals {"progress": 40}

event: sigmx-patch-elements
data: selector #log
data: mode append
data: elements <li>Validating…</li>

Plugins

| kind | plugins | | --------- | -------------------------------------------------------------------------------------------------------------- | | state | signals computed effect ref persist query-string | | rendering | text html show class style attr animate transition collapse cloak | | forms | bind custom-validity mask | | events | on init on-interval on-intersect on-resize on-raf on-signal-patch match-media | | layout | teleport scroll-into-view remove-me | | server | @get @post @put @patch @delete @ws boost indicator patch-elements patch-signals | | utilities | @peek @setAll @toggleAll @fit @clipboard @intl @dispatch @confirm json-signals replace-url |

Every plugin has a page in the docs with a live example and its measured size.

Sizes

Brotli, measured from the real source by npm run size and npm run compare (nothing is quoted from memory):

| | brotli | | ----------------------------------------------------------- | ------: | | sigmx core | 4.5 KB | | sigmx essentials (state, rendering, forms, requests, morph) | 9.0 KB | | sigmx everything, 50 plugins | 11.5 KB | | htmx 2.0.10 | 14.6 KB | | Datastar 1.0.3 (23 plugins) | 11.8 KB | | Alpine.js 3.17.1 | 17.6 KB |

Precompiling expressions at build time removes the runtime compiler and any use of new Function, taking the core to 3.8 KB and making strict CSP trivial.

Auto mode

Like a CSS framework scanning for class names, sigmx can scan your source for the directives and functions you use and bundle only those, with an allowlist and support for your own plugins:

// vite.config.js
import { sigmxAuto } from 'sigmx/vite';
export default { plugins: [sigmxAuto({ always: ['signals'], custom: { upper: 'src/plugins/upper.ts' } })] };
import { createSigmx } from 'sigmx';
import { plugins } from 'virtual:sigmx-plugins';
createSigmx({ plugins });

Without Vite: npx sigmx scan src --out src/sigmx-plugins.js. In Astro: sigmx({ plugins: 'auto' }).

Writing a plugin

import { attribute } from 'sigmx';

export const upper = attribute({
  name: 'upper',
  value: 'required',
  mount({ el, evaluate, effect }) {
    effect(() => {
      el.textContent = String(evaluate()).toUpperCase();
    });
  },
});

Plugins are plain objects; nothing registers on import. The mount context gives you evaluate, effect, listen, cleanup, the store and the runtime, and everything registered through it is torn down when the attribute or element goes away. Functions (@name()) and server-event handlers work the same way with action() and handler().

Documentation

The full documentation, guides and live examples live in website/, built with Astro and Starlight:

cd website && npm install && npm run dev

Repository

src/kernel/        reactive graph, store, expression compiler, runtime (DOM-free core)
src/server.ts      sigmx/server: event builders, sse/sseStream/html/json responses, readSignals
src/plugins/       directives, functions, server-event handlers, the morph
src/presets/       minimal, essentials, all
sdks/astro/        @sigmx/astro: integration, server helpers, auto mode, precompilation
sdks/hono/         @sigmx/hono: middleware with signals(), html(), events(), stream(); serveClient()
examples/          standalone Vite, Express, Hono and Astro projects using the published packages
website/           documentation site
scripts/           build, size and comparison measurements, dev server
tests/             unit (node:test), dom (happy-dom) and browser (Playwright) suites
npm install
npm run check    # type-check
npm run lint     # biome: formatting and lint rules
npm run format   # biome: rewrite files in the house style
npm test         # build, then unit tests and happy-dom tests for every plugin
npm run test:browser  # Playwright: transitions, observers, WebSocket, the standalone build
npm run test:all # everything above plus the SDK suites
npm run dev      # dev server with SSE and WebSocket endpoints; open /tests/browser/index.html
npm run size     # bundle sizes for representative plugin sets
npm run compare  # sizes of htmx, Datastar and Alpine at pinned versions

Contributions are welcome. Open an issue first for anything larger than a fix, keep plugins as small as their reference-page size table suggests, and run npm run lint and both test suites before a pull request.

Licence

MIT. sigmx is an independent implementation: its attribute syntax is compatible with Datastar's public API so that markup can be migrated, and it contains no Datastar code.