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

tachyon-dom

v0.4.0

Published

A small TypeScript UI runtime and HTML-first compiler.

Readme

Tachyon DOM

Tachyon DOM is an experimental HTML-first compiler that turns static templates into direct DOM updates, using a small fine-grained runtime shared with SSR and streaming targets.

Experimental status: the compiler and runtime are ready for evaluation, examples, and incremental adoption, but public APIs may change before a stable release.

Why Tachyon DOM?

  • HTML stays recognizable while dynamic fields become explicit DOM paths.
  • Fine-grained updates write to cached text, attribute, class, and list targets without a virtual DOM.
  • One parsed template feeds client, buffered SSR, and streaming server targets.
  • Browser-safe runtime imports keep compiler and server dependencies out of client bundles.

Quick Example

This .td component combines a signal-backed counter with a keyed list:

<script setup lang="ts">
  const count = createSignal(0);
  const rows = createSignal([
    { id: 1, label: "Alpha" },
    { id: 2, label: "Beta" },
  ]);
  const increment = (): void => count.update((value) => value + 1);
</script>

<main>
  <button on:click={increment}>{count}</button>
  <ul>
    <for each={rows} key={row.id}>
      <li>{row.label}</li>
    </for>
  </ul>
</main>

The server targets escape interpolated text. The client target records direct paths to the button text and list container, then updates only those targets when their signals change.

What the Compiler Emits

The real output uses modular runtime imports and explicit cleanup ownership. Abbreviated, the hot path looks like this:

const countText = __tachyonTextAt(root, [0, 0]);
cleanups.push(__tachyonEffect(() => __tachyonSetText(countText, __tachyonRead(scope.count))));

cleanups.push(__tachyonEffect(() => __tachyonMountTextKeyedList(listRoot, [], __tachyonRead(scope.rows), listOptions)));
cleanups.push(() => __tachyonCleanupTextKeyedList(listRoot, []));

Static markup remains in a reusable template. Events are attached once per created target, keyed rows retain their DOM nodes while moving, and the generated binding root disposes effects, resources, events, and controls together.

See the syntax specification for supported expressions and exact target behavior.

Predictable Cost

The compiler picks the smallest runtime path a region needs: text-only rows use runtime/list-text, rows with attributes or events use runtime/list, and simple branches use runtime/conditional-core instead of the generic conditional. The lightweight and generic paths differ in size only, never in observable behavior. To see which path a template got and why, ask the compiler:

tachyon-dom explain src/routes/index/page.td
<for> under root.1 uses tachyon-dom/runtime/list.
  The lightweight module was not chosen because:
    - a row uses bind:value={row.label}

Runtime imports: tachyon-dom/runtime/text, tachyon-dom/runtime/event, tachyon-dom/runtime/list, tachyon-dom/runtime/signal

The same data is available as explainCompiledTemplate() from tachyon-dom/compiler and as tachyon-dom explain --json.

Measured Size

import { createSignal } from "tachyon-dom" bundles to 1243 bytes minified in the current esbuild contract.

Quick example client bundle: 17503 bytes minified, 6002 bytes Brotli, including the counter, text-only keyed-list binding code, and browser runtime. The generated list's cached DOM targets and lower-allocation update path increased this bundle by 816 minified and 236 Brotli bytes from the earlier measurement while improving representative warm updates. The development-only Inspector trace changed minifier output; the quick example is 32 minified and 7 Brotli bytes smaller than the previous baseline, while the minimal signal entry is 6 minified bytes larger. CI requires the minified baseline exactly, permits 1% Brotli variance across Node/zlib patch versions, enforces absolute 18000-byte minified and 6250-byte Brotli budgets, and rejects TypeScript, parse5, compiler, server, app, language-server, and runtime diagnostics modules from both browser metafiles. pnpm check:client-bundle-attribution records ten smaller fixtures, including a one-template and a thirty-template page whose difference is the per-template descriptor and glue cost, and a varied page of eight structurally different templates (forms, dynamic attributes, nested control flow, refs, richer expressions) that shows the per-template cost the repeated page understates, with the same build settings and keeps their esbuild contribution data and provenance in a unique ignored artifact.

pnpm build
pnpm check:browser-entry
pnpm check:quick-example-size

These numbers are produced from repository scripts, not estimated from source files. Subpath size budgets remain available through pnpm check:size.

Keyed List Benchmark

On this machine, Tachyon DOM performed within 3% of the comparison implementations across nine keyed-row operations.

| Compared with | Tachyon DOM result | | -------------------- | -----------------: | | vanillajs-lite-keyed | 1.8% faster | | solid-keyed | 0.9% faster | | marko-keyed | 2.5% slower |

Results are the geometric mean of nine operations: row creation, replacement, partial updates, selection, swapping, removal, append, clear, and creation of many rows. Lower execution time is better.

The recorded run used:

  • AMD Ryzen 9 9950X
  • Chromium 149.0.7827.55
  • Production Vite builds
  • 2 warmup runs and 7 measured runs per operation
  • 20% trimmed means to reduce outlier influence

The complete JSON artifact includes raw samples, variability, dependency versions, and clean Git provenance.

Reproduce the same benchmark contract locally:

pnpm bench:local

This is a repository-local comparison based on the js-framework-benchmark row-operation model. It is not an official upstream result or a general ranking of framework performance. See the benchmark methodology for details.

Install

Create a route-local starter:

npm create tachyon-dom@latest my-app
cd my-app
pnpm install
pnpm dev

The equivalent pnpm command is pnpm create tachyon-dom my-app. For an installed package, use tachyon-dom init --template basic --out my-app or select the ssr template.

The normal project shape keeps route markup in src/routes/**/page.td, client code in src/client/main.ts, and generated route registration in src/routes.generated.ts. Start with the getting-started guide.

Compiler and Runtime Boundaries

The package root contains browser-safe reactive and runtime APIs such as createSignal(), createMemo(), createResource(), createRoot(), onCleanup(), and the client router. Heavier tools use explicit subpaths:

import { createClientRouter, createSignal } from "tachyon-dom";
import { compileTemplate } from "tachyon-dom/compiler";
import { defineApp } from "tachyon-dom/app";
import { html } from "tachyon-dom/server/html";
import { tachyonDom } from "tachyon-dom/vite";

This boundary keeps TypeScript, parse5, language-server, app, and server graphs away from a browser consumer that only needs reactivity.

Runtime-only consumers do not install TypeScript, OXC, or language-server tooling. Add the optional peers for the features you use:

pnpm add typescript oxc-parser
pnpm add vscode-languageserver vscode-languageserver-textdocument

TypeScript powers SFC script transformation and OXC parses advanced template expressions. The second command is only needed for tachyon-dom/language-server. Missing peers fail at the relevant feature boundary with the package name and install command; importing the root runtime remains available without them. parse5 remains a normal dependency because the synchronous normalize-tags policy is supported in Workers as well as Node and Lambda.

Documentation

  • Getting started: starter creation, project shape, route files, template types, and testing.
  • Syntax specification: HTML-first syntax, expressions, directives, components, hydration, and streaming constructs.
  • Runtime: signals, ownership, DOM helpers, keyed lists, forms, enhancements, portals, and hydration.
  • Public API layers: stability tiers and the root, runtime, generated, server, and tooling import contracts.
  • App and Vite: app definitions, file routes, plugins, request-scoped SSR, logging, and packaging.
  • Routing: server routes, layouts, loaders, actions, streaming, and client navigation.
  • Server adapters: Workers, Node, Lambda, static assets, origins, and deployment behavior.
  • Security: escaping, trusted HTML, sanitizers, redirects, CSRF, hosts, proxies, and origins.
  • Whitespace migration: compiler versus document policies, legacy mappings, and boundary outcomes.
  • Region markers and reactivity migration: <for> markers, untrack() ownership, memo reads in batch(), <await> targets, and pending.
  • SFC scope optimization: setup scope narrowing, emission policies, transform caching, and size comparisons.
  • Benchmarks: provenance, contracts, reproduction, and interpretation.
  • Releasing: package verification, Trusted Publishing, and release commands.
  • Changelog: release history, breaking changes, fixes, and security notes.

Security

Template interpolation and tachyon-dom/server/html escape text by default. rawHtml(), trustedHtmlChunk(), and route stream() chunks are explicit trust boundaries. Sanitize user-generated markup with a vetted runtime adapter, configure public origins and trusted proxy behavior, and apply method plus CSRF/Origin checks before direct form actions.

See the security guide before deploying server-rendered or streamed applications.

Project Status

The current compiler supports text, class, attribute, style, ref, model, and event bindings; keyed lists and conditionals; local stores and components; hydration boundaries; buffered SSR; and streaming targets. The runtime uses fine-grained reactive ownership so component cleanup stops effects, aborts resources, and detaches DOM bindings.

Tachyon DOM is still experimental. Evaluate compatibility, output, accessibility, security boundaries, and benchmark relevance for your application before production adoption.

Development

pnpm install
pnpm test
pnpm build
pnpm lint
pnpm check:exports
pnpm check:size
pnpm check:browser-entry
pnpm check:browser-feature-budgets
pnpm check:runtime-sizes
pnpm check:client-bundle-attribution
pnpm check:template-types
pnpm check:quick-example-size
pnpm check:sfc-scope-sizes
pnpm test:property
pnpm test:mutation
pnpm test:mutation:full
pnpm test:mutation:hydration
pnpm test:mutation:sfc
pnpm test:mutation:store
pnpm test:mutation:release-readiness

Property tests use a stable fast-check seed; use FAST_CHECK_SEED, FAST_CHECK_PATH, and FAST_CHECK_NUM_RUNS to replay or resize a campaign. Mutation testing remains a local check: the normal command reuses incremental results, the full command retests every in-scope mutant, and the detailed report is written to reports/mutation/index.html. The scoped campaigns narrow both the mutated ranges and the tests run against them: the SFC campaign reports to reports/mutation/sfc/, the release-readiness campaign covers the reactive store plus the SFC and client-target ranges a release touches and reports to reports/mutation/release-readiness/, and the store campaign reports to the terminal.

pnpm check:runtime-sizes compares the recorded per-feature runtime measurements and the generated table in the runtime guide. Re-record both with pnpm update:runtime-sizes on a clean checkout after a change that moves them.

The focused hydration campaign reruns the compiler and conditional-runtime ranges from the hydration review without incremental reuse. See its coverage and survivor ledger for scope, remaining equivalent mutations, and GC test requirements.

Useful examples:

pnpm example:web
pnpm example:full-app
pnpm example:stream

License

MIT