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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@thi.ng/rdom

v1.7.70

Published

Lightweight, reactive, VDOM-less UI/DOM components with async lifecycle and @thi.ng/hiccup compatible

Readme

@thi.ng/rdom

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 211 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Lightweight, reactive, VDOM-less UI/DOM components with async lifecycle and @thi.ng/hiccup compatible.

From hdom to rdom: Reactive UIs without virtual DOMs

In many ways this package is the direct successor of @thi.ng/hdom, which for several years was my preferred way of building UIs. hdom eschewed using a virtual DOM to represent and maintain a dynamic tree of (UI) components and instead only required a previous and current component tree in @thi.ng/hiccup format (aka nested, plain JS arrays w/ optional support for embedded other JS data types, like ES6 iterables, @thi.ng/api interfaces, etc.) to perform its UI updates. Yet, whilst hiccup trees are plain, simple, user defined data structures, which can be very easily composed without any libraries, hdom itself was still heavily influenced by the general vDOM approach and therefore a centralized update cycle and computing differences between the trees were necessary ~~evils~~ core tasks. In short, hdom allowed the illusion of declarative components with reactive state updates, but had to use a complex and recursive diff to realize those updates.

In contrast, @thi.ng/rdom directly supports embedding reactive values/components in the hiccup tree and compiles them in such a way that their value changes directly target underlying DOM nodes without having to resort to any other intermediate processing (no diffing, vDOM updates etc.). @thi.ng/rdom is entirely vDOM-free. It supports declarative component definitions via @thi.ng/hiccup, @thi.ng/rstream, ES6 classes, direct DOM manipulation (incl. provided helpers) and/or any mixture of these approaches.

Targetted, isolated updates

If a reactive value is used for an element attribute, a value change will trigger an update of only that attribute (there's special handling for event listeners, CSS classes, data attributes and style attribs). If a reactive value is used as (text) body of an element (or an element/component itself), only that body/subtree in the target DOM will be impacted/updated directly...

The package provides an interface IComponent (with a super simple life cycle API), a base component class Component for stubbing and a number of fundamental control constructs & component-wrappers for composing more complex components and to reduce boilerplate for various situations. Whilst targetting a standard JS DOM by default, each component can decide for itself what kind of target data structure (apart from a browser DOM) it manages. rdom components themselves have no mandatory knowledge of a browser DOM. As an example, similar to @thi.ng/hdom-canvas, the @thi.ng/rdom-canvas wrapper provides a component which subscribes to a stream of hiccup-based scene descriptions (trees) and then translates each scene-value into HTML Canvas API draw calls.

Async updates & life cycle methods

Since there's no central coordination in rdom (neither explicitly nor implicitly), each component can (and does) update whenever its state value has changed. Likewise, components are free to directly manipulate the DOM through other means, as hinted at earlier.

The IComponent interface is at the heart of rdom. It defines three lifecycle methods to: .mount(), .unmount() and .update() a component. The first two are always async to allow for more complex component initialization procedures (e.g. preloaders, WASM init, other async ops...). Several of the higher-order controller components/constructs too demand async functions for the same reasons.

Because rdom itself relies for most reactive features, stream composition and reactive value transformations on other packages, i.e. @thi.ng/rstream, @thi.ng/transducers-async and @thi.ng/transducers, please consult the docs for these packages to learn more about the available constructs and patterns. Most of rdom only deals with either subscribing to reactive values, async iterables and/or wrapping/transforming existing subscriptions, either explicitly using the provided control components (e.g. $async()), $sub(), or using $compile() to auto-wrap such values embedded in an hiccup tree.

@thi.ng/atom integration

For the sake of deduplication of functionality and to keep the number of dependencies to a minimum, direct @thi.ng/atom integration has been removed in favor of using relevant @thi.ng/rstream constructs, which can be used as lightweight adapters, i.e.:

DOM creation & mutation

The package provides many functions to simplify the creation of individual or entire trees of DOM elements and to manipulate them at a later time. The single most important function of the package is $compile. It acts as a facade for many of these other functions and creates an actual DOM from a given hiccup component tree. It also automatically wraps any reactive values contained therein.

All of the following functions are also usable, even if you don't intend to use any other package features!

Control structures

For more advanced usage, rdom provides a range of control structures (container components) to simplify the handling of reactive states and reduce boilerplate for the implementation of common UI structures (e.g. item lists of any kind).

The following links lead to the documentation of these wrappers, incl. small code examples:

Event handlers for reactive streams

Currently, reactive rdom components are based on @thi.ng/rstream subscriptions. To create a feedback loop between those reactive state values and their subscribed UI components, input event handlers need to feed any user changes back to those reactive state(s). To reduce boilerplate for these tasks, the following higher order input event handlers are provided:

import { $compile, $input } from "@thi.ng/rdom";
import { reactive, trace } from "@thi.ng/rstream";

// reactive value/state w/ transformation
const name = reactive("").map((x) => x.toUpperCase());

// reactive text field for `name`
$compile(["input", {
    type: "text",
    // any value changes are fed back into `name`, which in return
    // triggers an update of this (and any other) subscription
    oninput: $input(name),
    value: name
}]).mount(document.body);

// addtional subscription for debug console output
name.subscribe(trace("name:"));

Click counter using thi.ng/rstream and thi.ng/transducers:

import { $compile, $inputTrigger } from "@thi.ng/rdom";
import { reactive } from "@thi.ng/rstream";
import { count, scan } from "@thi.ng/transducers";

// reactive value/stream setup
const clicks = reactive(true);

// button component with reactive label showing click count
$compile([
    "button",
    // $inputTrigger merely emits `true` onto the given reactive stream
    { onclick: $inputTrigger(clicks) },
    "clicks: ",
    // using transducers to transform click stream into a counter
    clicks.transform(scan(count(-1))),
]).mount(document.body);

Embedding async iterables

Work is underway to better support built-in AsyncIterables (possibly entirely in-lieu of rstream constructs). Currently, they can only be directly used for simple text or attribute values (also see the rdom-async example):

import { $compile } from "@thi.ng/rdom";
import { range, source } from "@thi.ng/transducers-async";

// infinite 1Hz counter
const counter = range(1000);

// manually updated click counter (an async iterable with extended API)
// see: https://docs.thi.ng/umbrella/transducers-async/functions/source-1.html
const clicks = source(0);

// event handler to update click count
const updateClicks = () => clicks.update((x)=> x + 1);

// compile DOM with embedded async iterables
$compile(
    ["div", {},
        ["div", {}, "counter: ", counter],
        ["button", { onclick: updateClicks }, "clicks: ", clicks]
    ]
).mount(document.body)

Status

STABLE - used in production

Search or submit any issues for this package

Support packages

Related packages

Installation

yarn add @thi.ng/rdom

ESM import:

import * as rdom from "@thi.ng/rdom";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/rdom"></script>

JSDelivr documentation

Package sizes (brotli'd, pre-treeshake): ESM: 4.29 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Usage examples

51 projects in this repo's /examples directory are using this package:

| Screenshot | Description | Live demo | Source | |:----------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|:----------------------------------------------------------|:---------------------------------------------------------------------------------------| | | Large ASCII font text generator using @thi.ng/rdom | Demo | Source | | | Figlet-style bitmap font creation with transducers | Demo | Source | | | Interactive & reactive image blurhash generator | Demo | Source | | | Self-modifying, animated typographic grid with emergent complex patterns | Demo | Source | | | Tool to interactively compute & visualize color contrasts against WCAG threshold | Demo | Source | | | Probabilistic color theme generator | Demo | Source | | | CSP channel-based event handling, async transducers & reactive UI components | Demo | Source | | | Color palette generation via dominant color extraction from uploaded images | Demo | Source | | | Interactive visualization of closest points on ellipses | Demo | Source | | | Fiber-based cooperative multitasking basics | Demo | Source | | | Fisheye menu list component for thi.ng/rdom | Demo | Source | | | Randomized space-filling, nested grid layout generator | Demo | Source | | | Browser REPL for a Lispy S-expression based mini language | Demo | Source | | | Mastodon API feed reader with support for different media types, fullscreen media modal, HTML rewriting | Demo | Source | | | Basic thi.ng/meta-css usage & testbed | Demo | Source | | | Optical flow analysis of web cam or video inputs | Demo | Source | | | Parser grammar livecoding editor/playground & codegen | Demo | Source | | | Matrix-based image color adjustments | Demo | Source | | | Randomized 4-point 2D color gradient image generator | Demo | Source | | | Interactive pixel sorting tool using thi.ng/color & thi.ng/pixel | Demo | Source | | | RGB waveform image analysis | Demo | Source | | | Live coding playground for 2D geometry generation using @thi.ng/pointfree-lang | Demo | Source | | | Procedural stochastic text generation via custom DSL, parse grammar & AST transformation | Demo | Source | | | Scroll-based, reactive, multi-param CSS animation basics | Demo | Source | | | Basic & barebones usage of async iterables in thi.ng/rdom | Demo | Source | | | Demonstates various rdom usage patterns | Demo | Source | | | Dynamically loaded images w/ preloader state | Demo | Source | | | rdom drag & drop example | Demo | Source | | | Basic usage of the declarative rdom-forms generator | Demo | Source | | | rstream & transducer-based FSM for converting key event sequences into high-level commands | Demo | Source | | | Basic usage of thi.ng/rdom keyed list component wrapper | Demo | Source | | | Lazy loading components via @thi.ng/rdom | Demo | Source | | | rdom & hiccup-canvas interop test | Demo | Source | | | Animated SVG elements with reactive attributes | Demo | Source | | | Basic thi.ng/router usage with thi.ng/rdom components | Demo | Source | | | Full umbrella repo doc string search w/ paginated results | Demo | Source | | | rdom powered SVG graph with draggable nodes | Demo | Source | | | Defining & using basic Web Components (with shadow DOM) via @thi.ng/rdom & @thi.ng/meta-css | Demo | Source | | | Responsive image gallery with tag-based Jaccard similarity ranking | Demo | Source | | | Generative audio synth offline renderer and WAV file export | Demo | Source | | | Minimal rstream sync() example using rdom | Demo | Source | | | Declarative component-based system with central rstream-based pubsub event bus | Demo | Source | | | Responsive & reactively computed stacked column layout | Demo | Source | | | SVG path parsing & dynamic resampling | Demo | Source | | | Tree-based UI to find & explore thi.ng projects via their associated keywords | Demo | Source | | | thi.ng/rdom & thi.ng/rstream based quiz to guess thi.ng package names | Demo | Source | | | Multi-layer vectorization & dithering of bitmap images | Demo | Source | | | Animated t-SNE visualization of 4D data | Demo | Source | | | Interactive ridge-line plot | Demo | Source | | | Interactive scatter & line plot of low-discrepancy samples | Demo | Source | | | rdom & WebGL-based image channel editor | Demo | Source |

API

Generated API docs

TODO

Currently, documentation only exists in the form of small examples and various doc strings (incomplete). I'm working to alleviate this situation ASAP... In that respect, PRs are welcome as well!

Basic usage

import { $compile } from "@thi.ng/rdom";
import { reactive } from "@thi.ng/rstream";
import { cycle, map } from "@thi.ng/transducers";

// reactive value
const bg = reactive("gray");

// color options (infinite iterable)
const colors = cycle(["magenta", "yellow", "cyan"]);

// event handler
const nextColor = () => bg.next(<string>colors.next().value);

// define component tree in hiccup syntax, compile & mount component.
// each time `bg` value changes, only subscribed bits will be updated
// i.e. title, the button's `style.background` and its label

// Note: instead of direct hiccup syntax, you could also use the
// element functions provided by https://thi.ng/hiccup-html
$compile([
    "div",
    {},
    // transformed color as title (aka derived view)
    ["h1", {}, bg.map((col) => `Hello, ${col}!`)],
    [
        // tag with Emmet-style ID & classes
        "button#foo.w4.pa3.bn",
        {
            // reactive CSS background property
            style: { background: bg },
            onclick: nextColor,
        },
        // reactive button label
        bg,
    ],
]).mount(document.body);

Lists

See $list and $klist docs for further information...

import { $klist } from "@thi.ng/rdom";
import { reactive } from "@thi.ng/rstream";

const items = reactive([
    { id: "a", val: 1 },
    { id: "b", val: 2 },
    { id: "c", val: 3 },
]);

$klist(
    // reactive data source (any rstream subscribable)
    items,
    // outer list element & attribs
    "ul",
    { class: "list red" },
    // list item component constructor
    (x) => ["li", {}, x.id, ` (${x.val})`],
    // key function (includes)
    (x) => `${x.id}-${x.val}`
).mount(document.body);

// update list:
// - item a will be removed
// - item b is unchanged
// - item d will be newly inserted
// - item c will be updated (due to new value)
setTimeout(
    () => {
        items.next([
            { id: "b", val: 2 },
            { id: "d", val: 4 },
            { id: "c", val: 30 },
        ]);
    },
    1000
);

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-rdom,
  title = "@thi.ng/rdom",
  author = "Karsten Schmidt",
  note = "https://thi.ng/rdom",
  year = 2020
}

License

© 2020 - 2025 Karsten Schmidt // Apache License 2.0