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

@aeroapp/ae

v0.6.0

Published

Tiny attribute-based behavior + reactivity library. HTML is the source of truth: one data-ae attribute, signals, keyed lists, two-way forms, View-Transition animation. No virtual DOM, no hydration, no build step required.

Readme

ae

npm CI license

Tiny attribute-based behavior + reactivity library. HTML is the source of truth — you write real markup, ae attaches behavior to it. No virtual DOM, no hydration, no template syntax, no build step required.

3.9 KB min+gzip · zero dependencies · TypeScript · one data-ae attribute

Install

npm install @aeroapp/ae
yarn add @aeroapp/ae
pnpm add @aeroapp/ae
bun add @aeroapp/ae

Or straight from a CDN, no tooling at all — esm.sh, unpkg, and jsDelivr all serve it the moment you need it:

<script type="module">
  import { ae } from 'https://esm.sh/@aeroapp/ae';
  // or: 'https://unpkg.com/@aeroapp/ae'                  (minified build)
  // or: 'https://cdn.jsdelivr.net/npm/@aeroapp/ae/+esm'
</script>

Pin an exact version in production — https://esm.sh/@aeroapp/[email protected] (see the npm badge above for the current release).

<button data-ae="save">Save</button>
<span data-ae="status"></span>
import { ae } from '@aeroapp/ae';

const count = ae.signal(0);

ae('status').render(el => el.textContent = `${count.value} items`);

ae('save')
  .press(() => count.value++)
  .hover(el => el.classList.add('hot'), el => el.classList.remove('hot'));

Handles are live: elements added to the DOM later — by you, by .list(), by anything — get bound automatically via one shared MutationObserver, and cleaned up completely when they leave. No manual unbinding, no leaks.

Feature tour

Signalsae.signal(v), ae.computed(fn), ae.effect(fn). Writes are batched per microtask; computeds are lazy and only notify when their value actually changes.

Rendering.render(fn) auto-tracks every signal read inside and re-runs on change. Sugar helpers .text() / .cls() / .attr() / .show() accept a plain value (applied once), a signal, or a function (both reactive).

Events.press(fn) is semantic activation: click everywhere, plus native-like Enter/Space only for elements the browser doesn't natively activate (keyboard-accessible div[tabindex], [role=button] for free, no double-fire on real buttons). Enter fires on keydown, Space on keyup — exactly like a native button — and keys inside nested form controls or editable text are never hijacked. .hover(enter, leave) and the .on(type, fn, opts) escape hatch round it out — all listeners per element, so non-bubbling events just work.

Keyed lists.list(items, render, key) stamps a native <template> per item with keyed reconciliation: reorders move nodes without remounting, unchanged items don't re-render, removed items are disposed.

<ul data-ae="todos">
  <template><li><b data-ae="title"></b></li></template>
</ul>
ae('todos').list(todos, (li, todo, i) => {
  ae.parts(li).title.textContent = `${i + 1}. ${todo.text}`;
}, todo => todo.id);

ae('remove').press(btn => {                 // which item was clicked?
  const todo = ae.itemOf(btn);              // ae knows — no key stamping
  todos.value = todos.value.filter(t => t.id !== todo.id);
});

Two-way forms.input(signal) wires by field type: strings for text/select, booleans for checkboxes, real numbers for number/range, a group value for radios, and string[] for <select multiple>. Writes are equality-guarded so echoes never move the caret.

Scoped rootsae(name, root) limits a handle to descendants of root: per-list behavior without global name collisions.

Shadow DOMae.observe(shadowRoot) extends the same liveness into a shadow tree: existing marked content mounts immediately, host removal cleans everything up, and the returned disposer undoes it all.

Animationae.transition(fn) runs your signal writes inside a browser View Transition: list enters, exits, and reorders animate in pure CSS, and elements with a view-transition-name morph — even across lists. Falls back to a plain call where unsupported.

Full API and semantics: API.md. Compact API reference for AI agents: llms.txt. Live demos: bun run serve → http://localhost:4242.

Real app: examples/kanban.html — a kanban with drag & drop, dynamic columns, inline editing, undo, filtering, and localStorage persistence in ~170 lines of JS. One-pager with live demos: site/index.html (deployed to GitHub Pages).

Performance

Keyed-list stress numbers, measured end-to-end — signal write through reconciliation, mount pipeline, layout, and paint (headless Chrome, Apple M4 Max; median of 3 runs). Each row carries three live data-ae bindings, two of them event listeners.

| operation | time | |---|---| | create 1,000 rows | 26 ms | | create 10,000 rows | 262 ms | | append 1,000 to 10,000 | 69 ms | | update every 10th of 11,000 | 117 ms | | swap 2 rows of 11,000 | 75 ms | | clear 11,000 rows | 58 ms |

Reproduce: bun run serveexamples/bench.html (add ?auto for the full suite).

Development

bun install
bun run build          # tsc → dist/*.js + declarations (entry: dist/ae.js)
bun run test           # jsdom smoke suite (build first)
bun run test:browser   # Playwright: Chromium, Firefox, WebKit
bun run serve          # demo at http://localhost:4242
bun run min            # dist/ae.min.js

Guarantees (the short version)

  • Batching — signal writes coalesce; each effect runs at most once per flush.
  • Absolute disposal — a disposed effect never runs again, even if already queued.
  • Net-state lifecycle — mount/cleanup reflect the net DOM change per task: moves don't remount, a→b→a renames are no-ops.
  • Fault isolation — one throwing effect/binding/cleanup never takes down the rest.
  • Runaway guard — a self-triggering effect trips a circuit breaker instead of hanging the tab.

License

MIT