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

@xdstriker/pulsedom

v0.2.0

Published

Tiny tree-shakable DOM render library with an optional .pl component compiler.

Downloads

596

Readme

PulseDOM

Tiny ESM render library focused on extreme DOM update efficiency, minimal bundle size, and tree-shakable imports.

The runtime is intentionally small. The optional .pl compiler is a build-time tool and is exposed through separate package paths so it does not enter the render bundle unless you import it.

Install

npm install @xdstriker/pulsedom

With Bun:

bun add @xdstriker/pulsedom

Why it exists

PulseDOM is built around a simple rule: render only what is needed, ship only what is imported.

  • Scoped updates through boundary nodes.
  • Reused event listeners where possible.
  • Text templates update only when their referenced keys change.
  • Runtime helpers are split into subpath exports for tree-shaking.
  • The .pl syntax compiler is optional and separate from the runtime.

This package currently ships TypeScript source as ESM. It is designed for Bun or modern bundlers that can consume TS/ESM package exports.

Basic Usage

import render, { objTree, setObjTree } from "@xdstriker/pulsedom";
import { button, component, div } from "@xdstriker/pulsedom/virtual-node";

let count = 0;

function Counter() {
  return component("section", {
    className: "counter",
    children: [
      div({ text: "Count: {count:0}" }),
      button({
        text: "+",
        events: {
          click: (_event, node) => {
            count += 1;
            render(objTree(), node, "update", { count });
          }
        }
      })
    ]
  });
}

setObjTree(Counter());
render(objTree(), document.getElementById("app")!);

component(tag, props) creates an update boundary. When an event calls render(objTree(), node, "update", state), PulseDOM finds the nearest boundary and updates that scope.

Runtime Exports

import render, { objTree, setObjTree } from "@xdstriker/pulsedom";
import { component, div, button, h1, fragment, island, svg, ns, t } from "@xdstriker/pulsedom/virtual-node";
import { template } from "@xdstriker/pulsedom/template";
import tinyStore from "@xdstriker/pulsedom/store";

Available package paths:

  • @xdstriker/pulsedom: render function and root tree store.
  • @xdstriker/pulsedom/virtual-node: VNode creation helpers.
  • @xdstriker/pulsedom/template: small {key:fallback} text template helper.
  • @xdstriker/pulsedom/store: tiny state helpers.
  • @xdstriker/pulsedom/compiler: optional .pl compiler.
  • @xdstriker/pulsedom/pl-plugin: optional Bun build plugin for direct .pl imports.

The package includes .d.ts files for strict TypeScript projects.

Text Templates

Text can reference state with an optional fallback:

div({ text: "Echo: {echo:empty}" });

If echo is missing, the rendered text is Echo: empty. On update, PulseDOM only recomputes template text when the changed state keys are referenced by that text.

Effects

Effects run after create and update. A node can receive one effect or multiple effects.

component("section", {
  text: "Status: {status:idle}",
  effect: [
    (node, state, action) => {
      (node as HTMLElement).dataset.action = action;
      (node as HTMLElement).dataset.status = String(state.status ?? "idle");
    },
    (node) => {
      const el = node as HTMLElement;
      el.title = "Managed by PulseDOM";
      return () => {
        el.removeAttribute("title");
      };
    }
  ]
});

Returning a function registers cleanup for the next effect run.

Dynamic Islands

PulseDOM does not ship a general list reconciler. For lists and conditionals, use small boundaries as replaceable islands:

import { replaceBoundary } from "@xdstriker/pulsedom";
import { button, div, island } from "@xdstriker/pulsedom/virtual-node";

function FilteredList(items: string[]) {
  return island("section", {
    children: [
      button({
        text: "Show active",
        events: {
          click: (_event, node) => {
            replaceBoundary(node, FilteredList(["Ada", "Grace"]));
          }
        }
      }),
      ...items.map((item) => div({ text: item }))
    ]
  });
}

This replaces the nearest boundary as a unit. It is intentionally explicit, so dynamic lists stay isolated without pulling a reconciler into the runtime.

Config-Driven Lazy Components

For production builds, components can be marked as dynamic without writing import() at each call site. Declare the exports in pulsedom.config.json:

{
  "dynamicComponents": [
    {
      "module": "./src/components/CapabilityComponents.ts",
      "exports": ["CompiledServerDataComponent"],
      "placeholder": {
        "tagName": "section",
        "className": "capability capability-loading",
        "text": "Loading..."
      }
    }
  ]
}

build.ts generates tiny wrappers that keep the same named exports, then rewrites imports of the configured module during the Bun build. Each configured export becomes a lazy boundary backed by a generated import() chunk.

SVG

Use svg(...) for SVG roots. Children inherit the SVG namespace, and ns(...) can be used for explicit namespaced elements:

import { ns, svg } from "@xdstriker/pulsedom/virtual-node";

const searchIcon = svg({
  viewBox: "0 0 16 16",
  children: [
    ns("path", { d: "M7 1a6 6 0 1 0 0 12" })
  ]
});

Cleanup

For tests, route changes, or remounting a demo, reset the internal registry:

import { resetRender, unmount } from "@xdstriker/pulsedom";

resetRender();
unmount(document.getElementById("app"));

Optional .pl Compiler

.pl is a friendlier component syntax that compiles into the current VNode structure. The compiler is independent from the final render runtime.

component CounterActions {
  fragment {
    button("+", on:click=increase)
    button("-", on:click=decrease)
  }
}

component CounterCard {
  div(isBoundary, className="counter") {
    h2("Compiled counter")
    div("Count: {count:0}")
    CounterActions(increase=increase, decrease=decrease)
  }
}

Handlers can be declared inside the component:

component EchoCard {
  handler updateMessage {
    update { echo: event.target.value }
  }

  div(isBoundary, className="echo") {
    input(placeholder="type here", on:input=updateMessage)
    div("Echo: {echo:}")
  }
}

update { ... } compiles to a scoped render update for the current boundary. Handlers can also use regular JavaScript and await.

handler loadUsers {
  update { serverStatus: "loading" }
  const response = await fetch("/api/mock/users");
  const payload = await response.json();
  update { serverStatus: "loaded", userCount: payload.users.length }
}

Effects are also supported:

component OperationsWorkspace {
  effect syncStatusStyle {
    const element = node as HTMLElement;
    element.dataset.status = String(state.workflowStatus ?? "idle");
  }

  effect mirrorTitle {
    const element = node as HTMLElement;
    element.title = `status: ${state.workflowStatus ?? "idle"}`;
  }

  div(isBoundary) {
    div("Status: {workflowStatus:idle}")
  }
}

Compile a .pl file in this repository:

bun run compile:pl compiler/examples/Counter.pl generated/Counter.ts

Use the compiler from code:

import { compilePL } from "@xdstriker/pulsedom/compiler";

const output = compilePL(source);

Use direct .pl imports in a Bun build:

import { plPlugin } from "@xdstriker/pulsedom/pl-plugin";

await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./build",
  plugins: [plPlugin()]
});

Then TypeScript can import compiled .pl components at build time:

import { CounterCard, EchoCard } from "./components/Counter.pl";

Local Demo

Install dependencies:

bun install

Run the demo server:

bun dev

bun dev runs build.ts, compiles the .pl examples into generated/pl, builds the demo bundle, and starts index.js.

Run tests:

bun test

Publishing Checklist

Before publishing to npm:

bun install
bun test
bun run compile:pl compiler/examples/Counter.pl generated/Counter.ts
npm pack --dry-run
npm publish --access public

Use npm pack --dry-run to verify that the package contains only the runtime source, compiler source, examples, docs, license, and demo files expected by the files field in package.json.

Notes For Maintainers

The performance and bundle goals are part of the design contract. When adding features, prefer small exported helpers, optional imports, scoped DOM work, and compiler output that stays close to the runtime VNode structure.

Implementation notes for future LLM/code agents live in AGENTS.md.