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

bitwrench

v2.1.7

Published

Zero-dependency JavaScript UI library. Describe UI as plain objects (TACO), render to DOM or HTML strings. Theming, components, pub/sub, server-driven UI (bwserve). No build step.

Readme

bitwrench.js

Bitwrench is a UI library that builds interfaces from plain JavaScript objects -- one format for components, styling, state, and server rendering, with no build step and zero dependencies.

// A "TACO" -- Tag, Attributes, Content, Options object can hold a component or even an entire page
var page = {
  t: 'div', a: { class: 'card' },
  c: [
    { t: 'h2', c: 'Hello' },
    { t: 'p',  c: 'UI as native JavaScript objects.' },
    { t: 'button', a: { onclick: function() { alert('clicked'); } }, c: 'Click me' }
  ]
};

bw.mount('#app', page);        // -> live DOM see it rendered now

// or create just html page, css, js and all using 
bw.html(page);                 // -> HTML string (Node.js, emails, SSR).  use server or client side

Each object has four keys: t (tag), a (attributes, including event handlers like onclick), c (content -- a string, array, or nested TACO), and o (options for state and lifecycle). Nest them, loop them, build them with functions -- they are ordinary JavaScript values.

A TACO is already a JavaScript object, so there is nothing to compile or transform. This makes bitwrench a good fit for situations where a build pipeline costs more than it buys: dashboards, internal tools, embedded device UIs, server-driven pages, or anything you want to ship as a single HTML file.

Installation

npm install bitwrench
// ES module
import bw from 'bitwrench';

// CommonJS
const bw = require('bitwrench');

Or include directly in a page:

<script src="https://cdn.jsdelivr.net/npm/bitwrench/dist/bitwrench.umd.min.js"></script>

Getting Started

A complete page -- no build step, no imports, everything is a plain object:

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://cdn.jsdelivr.net/npm/bitwrench/dist/bitwrench.umd.min.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>
    bw.loadStyles();   // structural CSS + design tokens

    bw.mount('#app', {
      t: 'div', a: { class: 'bw_container' },
      c: [
        { t: 'h1', c: 'My App' },
        { t: 'p',  c: 'Built from plain JavaScript objects.' },
        { t: 'button',
          a: { class: 'bw_btn bw_primary', onclick: function() { alert('Hello!'); } },
          c: 'Click me' }
      ]
    });
  </script>
</body>
</html>

Components

A component is a function that returns a TACO. Bitwrench ships ~50 factory functions (bw.makeCard(), bw.makeTable(), bw.makeTabs(), etc. -- see the Component Cheat Sheet). Each is a regular function that returns the same {t, a, c, o} object you could write by hand. Log the return value and look at it.

Your own components work the same way:

function statusChip(label, ok) {
  return { t: 'span', a: { class: 'bw_badge ' + (ok ? 'bw_success' : 'bw_warning') }, c: label };
}

// Built-in and custom components compose identically
bw.mount('#app', {
  t: 'div', a: { class: 'bw_container' },
  c: [
    bw.makeCard({ title: 'Server', content: 'Build 2.1.0' }),
    statusChip('online', true)
  ]
});

State and Updates

Add o.state and o.render to any TACO to make it stateful. The render function receives (el, state), and you call bw.refresh(el) when you want it to re-run:

var counter = {
  t: 'div',
  o: {
    state: { count: 0 },
    render: function(el, state) {
      bw.mount(el, {
        t: 'div', c: [
          { t: 'h3', c: 'Count: ' + state.count },
          bw.makeButton({ text: '+1', onclick: function() {
            state.count++;
            bw.refresh(el);
          }})
        ]
      });
    }
  }
};

bw.mount('#app', counter);

State is also available as el._bw_state from outside the render function -- useful for debugging or direct access from event handlers.

Event handlers go in a: { onclick: fn }, not in o.mounted. Handlers attached via addEventListener in o.mounted are lost when a component re-renders. Place them in a: and bitwrench re-attaches them on every render.

Bitwrench has no reactivity system. Mutating state does not trigger anything -- the DOM changes only when you call an update function. This is a deliberate trade: you give up automatic re-renders, and in exchange every DOM mutation is a function call you wrote, with a cost you chose.

The update functions form a cost ladder:

| Update verb | Cost | What happens | | --- | --- | --- | | el.bw.method() / slot setters | Surgical | Component updates its own DOM directly | | bw.update(ref, data) | Dispatch | Calls el.bw.update(data) -- never rebuilds | | bw.message(ref, action, data) | Dispatch | Calls el.bw[action]() by selector or UUID | | bw.patch(id, content) | Targeted | Replaces one element's content | | bw.refresh(ref) | Full rebuild | Re-runs o.render; children are unmounted and rebuilt |

Choosing where you sit on this ladder is the programming model. The full bw.refresh() re-render shown above is the simplest but most expensive option. The next section introduces slots and handles, which sit at the top of the ladder.

Component API

After mounting, the DOM element is the component. The TACO is consumed at mount time -- there is no virtual DOM and no retained tree. State lives on the element (el._bw_state), and so does its public API (el.bw).

Slots map CSS selectors to setter/getter pairs. Handles define named methods. Both are attached to el.bw at mount time:

var card = bw.mount('#stats', {
  t: 'div', a: { class: 'stats-card' },
  c: [
    { t: 'h3', a: { class: 'card-title' }, c: 'Revenue' },
    { t: 'span', a: { class: 'card-value' }, c: '$50,000' }
  ],
  o: {
    slots: { title: '.card-title', value: '.card-value' },
    handle: {
      update: function(el, data) { el.bw.setValue('$' + data.value.toLocaleString()); }
    }
  }
});

card.bw.setTitle('Profit');          // slot setter -- updates one text node
card.bw.update({ value: 120000 });   // handle method -- runs your logic
bw.update(card, { value: 99000 });   // same call, dispatched by element or UUID

slots: { title: '.card-title' } generates el.bw.setTitle() and el.bw.getTitle() automatically. handle methods are attached as-is to el.bw. Neither causes a re-render -- they update the DOM directly.

Because everything lives on the element, debugging needs no extension: select a component in the browser's Elements panel and type $0._bw_state or $0.bw.

A component's lifecycle is four explicit calls:

| Phase | You call | Opt-in hook | | --- | --- | --- | | Define | a function that returns a TACO | -- | | Mount | bw.mount('#app', taco) | o.mounted(el) | | Update | el.bw.method() / bw.refresh(el) | -- | | Unmount | bw.remove(el) | o.unmount(el) |

The Component Lifecycle Walkthrough takes one card through all four phases. The State Management guide covers the full component model.

Cross-Component Communication

Components communicate through pub/sub. bw.sub() returns an unsubscribe function. Wildcard topics match any suffix after the colon:

bw.sub('item-added', function(detail) { console.log('New:', detail.name); });
bw.pub('item-added', { name: 'Widget' });
bw.sub('item:*', function(detail, topic) { /* matches item:added, item:removed, etc. */ });

Pass an element as the third argument to tie the subscription's lifetime to that element -- when the element is removed from the DOM, the subscription is automatically cleaned up:

bw.sub('cart:updated', function(data) {
  el._bw_state.count = data.count;
  bw.refresh(el);
}, el);

CSS from JavaScript

bw.css() generates CSS strings from objects. bw.injectCSS() inserts a CSS string into the document as a <style> tag. bw.s() composes inline styles. bw.responsive() generates @media rules from a breakpoint map. These are generation functions -- they return strings, so you can use them anywhere:

// Generate and inject a stylesheet
bw.injectCSS(bw.css({
  '.my-card': { padding: '1rem', borderRadius: '8px' }
}));

// Compose inline styles from reusable objects
{ t: 'div', a: { style: bw.s({ display: 'flex' }, { gap: '1rem' }, { padding: '1rem' }) } }

// Responsive breakpoints
bw.responsive('.hero', {
  base: { fontSize: '1.5rem' },
  md:   { fontSize: '2.5rem' }
});

Bitwrench does not own your CSS. You can use external stylesheets, Tailwind, or plain CSS alongside any of the above.

Theming

bw.loadStyles() derives a complete design system -- buttons, alerts, badges, cards, forms, tables, hover states, focus rings -- from two seed colors. Call it with no arguments for structural CSS only, or pass a config to generate a full theme. bw.toggleThemeMode() switches between primary and alternate palettes:

bw.loadStyles({
  primary: '#336699',
  secondary: '#cc6633'
});

bw.toggleThemeMode();  // switch to alternate palette

Styles can be scoped to DOM subtrees, so different parts of a page can use different themes. See the Theming guide for presets, palette structure, and scoping.

Server-Driven UI

Because TACOs are plain objects, they serialize as JSON. This means a backend in any language can push UI updates to the browser.

Bitwrench includes bwserve, a protocol that sends TACO objects and patches over SSE. Button clicks come back as actions, client.inspect() reads DOM state, and client.screenshot() captures the live page as a PNG. The browser becomes a display and input device; the application logic lives wherever you want it.

Here is a C program on an ESP32 pushing a sensor reading to the browser:

char msg[96], frame[128];
BW_PATCH(msg, "office-temp", "23.5");
BW_SSE_FRAME(frame, msg);
events.send(frame, NULL, millis());    // the browser updates

The same protocol works from Python, Go, Rust, or a shell script with curl. See the bwserve docs for the full protocol, and the ESP32 tutorial for a complete embedded walkthrough.

The library is ~165KB on disk (~45KB gzipped). A lean build without the component library (BCCL) is ~128KB (~35KB gzipped). Both work entirely self-hosted from a microcontroller's flash -- no CDN and no internet required.

CLI

bwcli converts files to styled standalone pages:

# Convert Markdown to a self-contained HTML page
bwcli README.md -o index.html --standalone

# Apply a theme preset
bwcli doc.md -o doc.html --standalone --theme ocean

# Custom colors
bwcli doc.md -o doc.html --standalone --theme "#336699,#cc6633"

Flags: --output/-o, --standalone/-s, --cdn, --theme/-t, --css/-c, --title, --favicon/-f, --highlight, --verbose/-v

Pipe Server

bwcli serve turns any language into a bwserve backend -- send JSON protocol messages via HTTP POST or stdin, and connected browsers update in real time:

bwcli serve --port 8080 --input-port 9000
curl -X POST http://localhost:9000 -d '{"type":"patch","ref":"temp","content":"23.5 C"}'

Dev Server & Debugging

bwcli serve doubles as a dev server with remote debugging. Attach a REPL, inspect DOM state, and capture screenshots -- all from the terminal:

bwcli serve --allow-screenshot          # start with screenshot support
bw> /inspect #app 2                     # DOM tree summary (depth 2)
bw> /screenshot body page.png           # full-page capture
bw> /screenshot .my-card card.png       # element-level capture
bw> /tree                               # full DOM tree as JSON

No browser extensions needed -- bwcli serve injects a lightweight client script that handles inspect, screenshot, and live patching over SSE. See bw-attach docs for the full REPL command reference.

Coming from Other Frameworks

| You're using | For | Bitwrench equivalent | | --- | --- | --- | | React / Vue / Svelte | Components | {t, a, c, o} objects + o.state + o.render | | JSX / templates | Markup-in-JS | Native JS objects -- no compiler | | Tailwind / CSS-in-JS | Styling | bw.css(), bw.s() | | Sass / PostCSS | CSS generation | bw.css() from JS objects (supports @media, @keyframes) | | ThemeProvider / CSS vars | Theming | bw.loadStyles() / bw.makeStyles() from seed colors | | Streamlit / Gradio | Server-driven UI | bwserve SSE -- from any language | | Redux / Zustand / Pinia | State management | o.state + bw.refresh() + bw.pub()/sub() | | Vite / webpack / Babel | Build tooling | Not needed -- open the HTML file | | DefinitelyTyped / @types | Type declarations | Ships dist/bitwrench.d.ts |

See the Framework Translation Table for side-by-side code comparisons across 22 operations.

Core API

| Function | Description | | --- | --- | | bw.html(obj) | Convert a TACO to an HTML string | | bw.mount(selector, obj) | Mount a TACO into a DOM element; returns the root element | | bw.DOM(selector, obj) | Alias of bw.mount() | | bw.create(taco) | Create a detached DOM element from a TACO (not inserted into the page) | | bw.el(selector, apply?) | Find an element; optionally apply text, TACO, or function to it | | bw.$(selector) | querySelectorAll as an array | | bw.raw(str) | Mark a string as pre-escaped HTML (no double-escaping) | | bw.css(rules) | Generate CSS from a JS object | | bw.injectCSS(css, opts?) | Insert a CSS string into the document as a style tag | | bw.s(...objs) | Compose inline style objects into a style string | | bw.responsive(sel, breakpoints) | Generate @media CSS rules from a breakpoint map | | bw.loadStyles(config?) | Structural CSS (no args) or generate + apply a theme from seed colors | | bw.makeStyles(config) | Generate a theme from seed colors (returns styles object) | | bw.applyStyles(styles) | Inject a generated styles object into the document | | bw.toggleThemeMode(scope?) | Switch between primary and alternate palettes | | bw.clearStyles() | Remove injected theme styles | | bw.patch(id, content) | Update a specific element by id or UUID | | bw.refresh(el) | Re-render a stateful component via its o.render function | | bw.update(el, data) | Dispatch to el.bw.update(data) | | bw.message(target, action, data) | Dispatch to el.bwaction by selector or UUID | | bw.pub(topic, detail) | Publish to subscribers (exact + wildcard matches) | | bw.sub(topic, handler, el?) | Subscribe to a topic (supports wildcard 'ns:*'); returns unsub function | | bw.once(topic, handler, el?) | One-shot subscribe; auto-unsub after first fire | | bw.remove(el) | Unmount a component (fires o.unmount hook) | | bw.inspect(target, depth) | Introspect a DOM subtree with bitwrench metadata | | bw.apply(msg) | Apply a bwserve protocol message to the DOM |

The update functions (bw.patch, bw.refresh, bw.update, bw.message) form a cost ladder -- see State and Updates. Full API Reference.

Build Formats

| Format | File | Use case | | --- | --- | --- | | UMD | bitwrench.umd.min.js | Browsers and Node.js | | ESM | bitwrench.esm.min.js | Modern bundlers (Vite, webpack, etc.) | | CJS | bitwrench.min.cjs | Node.js require() | | ES5 | bitwrench.es5.min.js | Legacy browsers (IE11) |

All formats include source maps. A separate CSS file (bitwrench.css) is also available for use without JavaScript.

Every release is gated at 46KB gzipped for the UMD and ESM builds, measured against the pre-compressed .gz that ships. (The gate was 45KB through 2.1.6; it was raised once, deliberately, in 2.1.7 -- and the core/BCCL CSS split planned for 2.2 is expected to give the byte back and then some.) If you are working on bundle size, Bundle size findings documents how to measure it correctly, which optimizations were measured and rejected (string interning makes gzipped output larger), and where the remaining headroom is.

Documentation

Start here:

  • Quick Start -- annotated 100-line tutorial covering the full lifecycle
  • Thinking in Bitwrench -- the complete guide: TACO format, styling, composition, events, the component model, bwserve, and common patterns
  • LLM Guide -- compact single-file reference with all APIs, patterns, and rules

Reference guides (in docs/):

Tutorials:

Interactive demos (live site):

Example apps (in examples/):

FAQ

Is this a framework? -- No. It is a library (165KB on disk, 45KB gzipped). No lifecycle ceremony, no project structure. Import it, call functions, done. Lifecycle hooks (o.mounted, o.unmount) are opt-in.

How does bitwrench compare to React/Vue? -- They solve different problems at different scales. React and Vue provide a component model, virtual DOM, and ecosystem for large team-built SPAs. Bitwrench provides rendering and state primitives in a single file with no build step, aimed at single-page tools, dashboards, embedded devices, and server-driven UIs. They coexist fine.

How does CSS work? -- Bitwrench does not own your CSS. Use any external stylesheet, Tailwind, or CSS file you want. On top of that, bw.css() generates CSS from JS objects (with @media, @keyframes, pseudo-classes), bw.s() composes inline style objects, and bw.loadStyles() derives a complete design system from seed colors. Use all three or none.

What's the difference between bw.mount() and bw.html()? -- Same TACO input, two outputs. bw.mount('#app', taco) mounts live DOM elements in a browser. bw.html(taco) returns an HTML string for Node.js scripts, email generators, static site builds, or anywhere you need markup without a browser. (bw.DOM() is an alias for bw.mount().)

What is bwserve? -- A protocol that turns the browser into a display and input device for a program running anywhere. The server pushes TACO objects and patches over SSE; button clicks come back as actions; client.inspect() returns DOM state; client.screenshot() returns a PNG. Language-agnostic: Python, Go, Rust, C, or a shell script with curl. See the bwserve docs.

Can I use bitwrench on embedded devices? -- Yes. The device serves one HTML page plus the library from flash, no CDN required. Build the UI as TACOs in whatever language the device speaks (C, C++, MicroPython), push updates over SSE, and get button presses back the same way. C macros ship in embedded_c/. See the ESP32 tutorial and the Pico W example.

Can I use it with TypeScript? -- Yes. Type declarations ship with the package (dist/bitwrench.d.ts). See the TypeScript Usage Guide.

What about accessibility? -- BCCL components emit semantic HTML with ARIA attributes where applicable. You can add any aria-* attribute via a: { 'aria-label': '...' }.

Development

npm install          # install dev dependencies
npm run build        # build all dist formats (UMD, ESM, CJS, ES5)
npm test             # run unit tests
npm run test:cli     # run CLI tests
npm run test:e2e     # run Playwright browser tests
npm run lint         # run ESLint
npm run cleanbuild   # full production build with SRI hashes

License

BSD-2-Clause -- (c) M. A. Chatterjee / deftio -- use it in your own projects or commercially.