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

solid-html-cache

v0.0.8

Published

A structural cache wrapper for Solid 2 tagged HTML, with optional normalized built-template persistence through solid-html-cachable.

Downloads

326

Readme

solid-html-cache

solid-html-cache is a content-aware cache wrapper for Solid 2's tagged HTML API (@solidjs/html).

Solid already caches parsed templates by TemplateStringsArray identity. That works very well when the same tagged-template callsite executes repeatedly. This package adds a bounded structural cache in front of Solid so different template identities with the same static structure can reuse one canonical identity.

import html from "solid-html-cache";

const first = html`<article class="card">${a}</article>`;
const second = html`<article class="card">${b}</article>`;

Those literals are different JavaScript callsites, but their static template structure is equivalent. solid-html-cache canonicalizes them before calling @solidjs/html, allowing Solid's own internal cache to reuse the parsed/built template.

The default package entry stays on Solid's public tagged-HTML API and does not import or copy Solid's private parser/template internals. The explicit solid-html-cache/cachable subpath enables deeper normalized built-template persistence through the separately published solid-html-cachable engine.

0.0.x is a pre-1.0 release line. The stock entry remains compatible with official Solid; the /cachable entry is the experimental deep-persistence backend and may evolve between minor pre-1.0 releases.

Install

npm install solid-html-cache @solidjs/html

or:

bun add solid-html-cache @solidjs/html

@solidjs/html is a peer dependency.

Quick start

Use the default export as a drop-in replacement for Solid's html tag:

import html from "solid-html-cache";

const view = html`
  <section class="profile">
    <h2>${() => user.name}</h2>
    <p>${() => user.bio}</p>
  </section>
`;

The default tag uses an in-memory bounded LRU and no persistent backend.

Create an independently configured tag when you need a different policy:

import { createSolidCachedHtml } from "solid-html-cache";

const html = createSolidCachedHtml({
  maxEntries: 512,
  maxMetadataEntries: 4096,
  admission: "second-hit",
  maxTemplateChars: 128_000,
  ttlMs: 30 * 60_000
});

When it helps

Solid's built-in cache is already ideal for repeated execution of one callsite:

function row(value: string) {
  return html`<li>${value}</li>`;
}

row("a");
row("b");
row("c");

JavaScript reuses the same TemplateStringsArray identity for that callsite, so Solid can reuse its cached parsed template directly.

solid-html-cache is most useful when equivalent static templates arrive through different identities, for example:

const one = html`<li class="item">${a}</li>`;
const two = html`<li class="item">${b}</li>`;

It can also help in systems that dynamically load modules/routes containing duplicated template shapes.

The tradeoff is important: if a workload continuously produces unique template structures that are never reused, structural keying adds overhead without a reuse benefit. The cache is configurable so those workloads can use stricter admission policies or smaller limits.

Configuration

createSolidCachedHtml(options) and HtmlCacheController accept the following options:

| Option | Default | Description | | --- | --- | --- | | maxEntries | 256 | Maximum canonical template identities held strongly by the runtime LRU. 0 disables runtime canonical retention. | | maxMetadataEntries | 2048 | Maximum lightweight metadata records held in memory. | | maxTemplateChars | unlimited | Templates above this cooked+raw character count bypass canonical caching. | | ttlMs | none | Optional lifetime for runtime canonical entries. | | persistentMaxAgeMs | none | Delete persisted metadata records not seen within this age when pruning runs. | | persistentMaxEntries | none | Maximum persisted metadata records retained by pruning. | | persistentPruneOnFlush | auto when a persistent limit is set | Schedule metadata pruning after write-behind flushes. | | persistentAutoPruneIntervalMs | 300000 | Minimum interval between automatic metadata-prune runs. | | persistentPruneIdleTimeoutMs | 2000 | Maximum browser-idle wait before scheduled metadata pruning runs. | | admission | "always" | "always", "second-hit", or a custom function. | | backend | none | Optional portable metadata backend. | | keyer | built-in exact keyer | Custom structural key/snapshot implementation. | | onError | none | Receives backend/keyer/policy errors without intentionally breaking rendering. | | now | Date.now | Optional clock injection. | | debug | false | true logs cache hit/miss/timing events; an object can disable logging and receive onEvent callbacks. |

Example:

import {
  createSolidCachedHtml,
  LocalStorageBackend
} from "solid-html-cache";

const html = createSolidCachedHtml({
  maxEntries: 512,
  maxMetadataEntries: 4096,
  maxTemplateChars: 64_000,
  admission: "second-hit",
  backend: new LocalStorageBackend({ namespace: "app-v1" }),
  onError(error) {
    console.warn("solid-html-cache backend error", error);
  }
});

Admission policies

Admission controls when a structurally observed template is promoted to the runtime LRU.

always

const html = createSolidCachedHtml({ admission: "always" });

The first eligible occurrence is admitted. This is the default and is a good fit when structural reuse is expected.

second-hit

const html = createSolidCachedHtml({ admission: "second-hit" });

The first observation records metadata but does not consume a runtime LRU slot. A later observation of the same structure is admitted. This can reduce pollution when many templates are one-offs.

If persistent metadata has been hydrated, previous-session history can satisfy the previous-hit requirement.

Custom admission

const html = createSolidCachedHtml({
  admission({ seen, persistedUses, charLength }) {
    return (seen >= 2 || persistedUses >= 2) && charLength <= 32_000;
  }
});

The callback receives:

interface CacheAdmissionContext {
  key: string;
  strings: TemplateStringsArray;
  seen: number;
  charLength: number;
  persistedUses: number;
}

Persistent backends

The default/official backend's persistent backends store portable template snapshots and usage metadata. They do not serialize Solid's private parsed DOM/template roots.

That distinction matters:

runtime cache
  canonical TemplateStringsArray identity
        ↓
  Solid's in-memory parsed-template cache

persistent backend
  structural snapshot + counts + timestamps

After a full page refresh, JavaScript object identities and Solid's private parsed templates are gone. With the default official-Solid entry, a persistent backend can restore metadata/admission history, but the new document still needs Solid to parse/build each unique structure at least once. The experimental solid-html-cache/cachable entry described below adds a separate normalized built-template store that can bypass that work.

Writes are write-behind so rendering remains synchronous. Built-in backends also implement batch operations where appropriate.

MemoryBackend

import { MemoryBackend, createSolidCachedHtml } from "solid-html-cache";

const html = createSolidCachedHtml({
  backend: new MemoryBackend()
});

This backend stores portable records in memory. It is useful for sharing metadata in one realm/process and for custom orchestration.

LocalStorageBackend

import {
  LocalStorageBackend,
  createSolidCachedHtml
} from "solid-html-cache";

const html = createSolidCachedHtml({
  admission: "second-hit",
  backend: new LocalStorageBackend({ namespace: "app-v1" })
});

await html.cache.hydrate();

Options:

interface LocalStorageBackendOptions {
  namespace?: string;
  storage?: Storage;
}

Use namespace to isolate records belonging to different applications, versions, or cache strategies.

IndexedDbBackend

import {
  IndexedDbBackend,
  createSolidCachedHtml
} from "solid-html-cache";

const backend = new IndexedDbBackend({ namespace: "app-v1" });

const html = createSolidCachedHtml({
  admission: "second-hit",
  backend
});

await html.cache.hydrate();

Options:

interface IndexedDbBackendOptions {
  namespace?: string;
  databaseName?: string;
  storeName?: string;
  databaseVersion?: number;
  indexedDB?: IDBFactory;
}

The IndexedDB backend batches multi-record writes into one transaction when the controller flushes multiple dirty records.

For a long-lived custom backend instance, close its database connection when appropriate:

await backend.close();

Choosing a persistent backend

  • Use localStorage when the metadata set is small and synchronous startup hydration is acceptable.
  • Use IndexedDB when you need a larger asynchronous store or want to avoid localStorage size constraints.
  • Avoid blocking first paint on a large IndexedDB hydration unless your application has measured that the restored metadata is worth the startup cost.

Cache lifecycle

Every cached tag exposes its controller as html.cache.

Inspect statistics

const stats = html.cache.stats();

Statistics include calls, key computations, runtime hits/misses, inserts, evictions, expirations, backend writes/errors, hydrated records, and current cache sizes.

Reset counters without clearing cached data:

html.cache.resetStats();

Inspect runtime and metadata state

const keys = html.cache.runtimeKeys();
const records = html.cache.metadata();

Hydrate persistent metadata

const result = await html.cache.hydrate();
console.log(result.loaded, result.ignored);

Call hydrate() before initial rendering only if persisted history should influence the first admission decisions. Otherwise it can be done later.

Flush queued writes

await html.cache.flush();

Normal rendering queues write-behind persistence automatically. flush() is useful before teardown, tests, navigation boundaries, or when you explicitly need persistence to be durable before continuing.

Clear cache state

await html.cache.clear();

Or clear selected layers:

await html.cache.clear({
  runtime: true,
  metadata: false,
  backend: false
});

Clear only the runtime canonical identities:

html.cache.clearRuntime();

Invalidate one template

await html.cache.invalidate(strings);

By default this removes the template from runtime identity reuse, in-memory metadata, and the configured metadata backend. Selected layers can be retained:

await html.cache.invalidate(strings, { backend: false });

Prune persisted metadata

const result = await html.cache.prune({
  maxAgeMs: 30 * 24 * 60 * 60 * 1000,
  maxEntries: 512
});

Configured persistentMaxAgeMs / persistentMaxEntries are also applied after write-behind flushes unless persistentPruneOnFlush is disabled. This cleanup removes old backend records without evicting currently live runtime identities.

Debug mode

Debug instrumentation is opt-in so normal hot paths do not pay timing/logging overhead:

const html = createSolidCachedHtml({ debug: true });

Example console events:

[solid-html-cache][structural] miss-insert 0.041ms chars=128
[solid-html-cache][structural] runtime-hit 0.006ms chars=128
[solid-html-cache][structural] callsite-hit 0.003ms chars=128

Programmatic events are also supported without console logging:

const events = [];
const html = createSolidCachedHtml({
  debug: {
    log: false,
    onEvent(event) { events.push(event); }
  }
});

Structural timing totals are available from html.cache.stats() as debugTimingSamples, totalCanonicalizeMs, and averageCanonicalizeMs. Template keys are omitted from debug output by default because exact keys contain template source; use debug: { includeKeys: true } only when that is acceptable.

Solid component registries and .define()

The cached tag preserves Solid's .define() behavior and type shape.

import html from "solid-html-cache";

const appHtml = html.define({
  Greeting(props: { name: string }) {
    return html`<strong>Hello ${props.name}</strong>`;
  }
});

Tags returned by .define() share the same HtmlCacheController, so equivalent templates can be canonicalized across related component registries.

Advanced wrapping

The root package includes helpers for wrapping Solid's default tag. Lower-level utilities are also available from solid-html-cache/core.

import solidHtml from "@solidjs/html";
import {
  HtmlCacheController,
  wrapWithCache
} from "solid-html-cache/core";

const controller = new HtmlCacheController({ maxEntries: 1024 });

const htmlA = wrapWithCache(solidHtml, controller);
const htmlB = wrapWithCache(solidHtml, controller);

Both wrappers share one canonical runtime cache.

You can also wrap a compatible custom tagged-template implementation:

import { createCachedHtml } from "solid-html-cache/core";

const cachedTag = createCachedHtml(myCompatibleTag, {
  maxEntries: 128
});

A compatible tag must be callable as:

(strings: TemplateStringsArray, ...values: any[]) => any

Custom backends

Implement CacheBackend when metadata should be stored somewhere else:

import type {
  CacheBackend,
  TemplateRecord
} from "solid-html-cache/core";

class CustomBackend implements CacheBackend {
  readonly kind = "custom";
  readonly mode = "async";

  async get(key: string): Promise<TemplateRecord | undefined> {
    // ...
    return undefined;
  }

  async set(record: TemplateRecord): Promise<void> {
    // ...
  }

  async delete(key: string): Promise<void> {
    // ...
  }

  async clear(): Promise<void> {
    // ...
  }

  async entries(): Promise<readonly TemplateRecord[]> {
    return [];
  }

  async setMany(records: readonly TemplateRecord[]): Promise<void> {
    await Promise.all(records.map(record => this.set(record)));
  }
}

Optional batch methods are:

getMany?(keys: readonly string[]): MaybePromise<readonly (TemplateRecord | undefined)[]>;
setMany?(records: readonly TemplateRecord[]): MaybePromise<void>;
deleteMany?(keys: readonly string[]): MaybePromise<void>;

The controller uses them when available.

Custom structural keying

The built-in keyer compares both cooked and raw static template segments and stores a portable snapshot for equality checks.

Advanced users can provide a custom TemplateKeyer:

interface TemplateKeyer {
  key(strings: TemplateStringsArray): string;
  snapshot(strings: TemplateStringsArray): TemplateSnapshot;
  equals(snapshot: TemplateSnapshot, strings: TemplateStringsArray): boolean;
  charLength(strings: TemplateStringsArray): number;
}

The key() value can be compact or hashed, but equals() must still protect correctness against key collisions.

Entry points

solid-html-cache

The normal Solid-facing API:

import html, {
  createSolidCachedHtml,
  HtmlCacheController,
  MemoryBackend,
  LocalStorageBackend,
  IndexedDbBackend
} from "solid-html-cache";

solid-html-cache/core

Lower-level cache primitives without importing the default Solid tag:

import {
  createCachedHtml,
  wrapWithCache,
  HtmlCacheController,
  exactTemplateKeyer,
  LruMap
} from "solid-html-cache/core";

solid-html-cache/backends

Backend-only imports:

import {
  MemoryBackend,
  LocalStorageBackend,
  IndexedDbBackend
} from "solid-html-cache/backends";

Browser and SSR notes

The cache controller and memory backend are environment-agnostic. Browser persistence backends require their corresponding browser APIs:

  • LocalStorageBackend requires Storage unless one is injected.
  • IndexedDbBackend requires indexedDB unless an IDBFactory is injected.

@solidjs/html itself is a client/DOM-oriented runtime. Do not invoke client-only tagged HTML rendering through a server build of @solidjs/web.

For SSR applications, create/use the cached tagged-HTML instance only in the client environment where Solid's DOM renderer is available.

Performance considerations

This package is designed to keep the common same-callsite path very small while adding value when static template structures repeat across distinct identities.

The expected shape is:

  • same callsite: close to Solid's existing hot-path behavior;
  • equivalent identities: potentially substantial savings because repeated parse/build work can be avoided;
  • larger equivalent templates: greater potential benefit because more parsing/building work is reused;
  • unique one-off structures: additional structural-keying/cache overhead with no reuse payoff.

For the default official-Solid entry, persistent storage should be evaluated separately from runtime caching: LocalStorageBackend and IndexedDbBackend restore metadata/history, not Solid's compiled template objects. The experimental /cachable entry has a separate synchronous compiled-template backend that can restore normalized built roots across documents.

Design guarantees and limitations

Bounded runtime retention

Canonical template identities are held by a bounded LRU. Callsite fast-path handles are invalidated on eviction so they do not keep evicted canonical templates alive.

Collision safety

The built-in structural key is backed by an exact snapshot/equality check. Two non-equivalent templates are not treated as equivalent merely because a compact backend/storage key collides.

Synchronous render path

html\...`` remains synchronous. Asynchronous storage is handled outside the tagged-template return path.

Stock persistence is metadata persistence

Through official @solidjs/html's public API, the default entry cannot serialize or restore Solid's private built template root. After a full realm/document restart, each structure still needs one Solid parse/build. The experimental /cachable entry is different: its patched engine exposes a normalized post-build representation and can restore it without rerunning buildTemplate().

Bounded caches are not free

Structural hashing/keying, metadata, and LRU maintenance consume CPU and memory. Configure limits and admission policies according to the workload instead of assuming every application benefits from the largest possible cache.

Experimental deep persistence with solid-html-cachable

The default export remains built on official @solidjs/html. A separate experimental entry can use the solid-html-cachable engine to persist a normalized post-build template representation across page refreshes.

Install the cacheable engine explicitly:

bun add solid-html-cache solid-html-cachable @solidjs/html

Then opt into the cachable entry:

import {
  createSolidCachableHtml,
  LocalStorageCompiledTemplateBackend
} from "solid-html-cache/cachable";

const html = createSolidCachableHtml({
  compiled: {
    backend: new LocalStorageCompiledTemplateBackend({ namespace: "app-v1" })
  }
});

const view = html`<article class="card">${() => value()}</article>`;

// Cold misses write behind; call this when durability must be guaranteed.
await html.compiledCache.flush();

This path uses a fused cache stack:

L1  solid-html-cachable TemplateStringsArray WeakMap -> built root
L2  solid-html-cache structural key -> live BuiltRoot LRU
L3  persistent structural key -> normalized built root -> rehydrate template.innerHTML
L4  cold compile/build

Starting in 0.0.8 the cachable path no longer manufactures a canonical TemplateStringsArray before calling Solid. Different source callsites with the same structure reuse the BuiltRoot directly at L2, while repeated calls from the same source callsite stay entirely on the engine's native L1 WeakMap. The stock solid-html-cache entry keeps its original canonical-identity architecture.

On an L3 hit the patched compiler does not rerun buildTemplate(). It rehydrates fresh HTMLTemplateElement instances in the current document and instantiates them with the current invocation values. The serialized format is versioned by solid-html-cachable and is invalidated on engine/format mismatch.

The design intentionally keeps storage policy in solid-html-cache; the proposed upstream Solid change is only the compile/normalize/rehydrate boundary. See docs/CACHABLE-BACKEND.md in the source repository for the architecture and upstream transition plan.

License

MIT

Experimental persistent built-template helper

When using solid-html-cachable, the common localStorage setup is one call:

import { createLocalStorageHtml } from "solid-html-cache/cachable";

const html = createLocalStorageHtml({
  namespace: "my-app",
  debug: true,
  compiled: { minTemplateChars: 100 }
});

The helper applies bounded compiled-cache cleanup defaults:

maxAgeMs                       30 days
maxEntries                     256
maxBytes                       2 MiB serialized payload
removeOtherCompilerFingerprints true
autoPruneIntervalMs             5 minutes
pruneIdleTimeoutMs              2 seconds

Override any value under compiled. Automatic cleanup is coalesced and rate-limited (default: at most once every 5 minutes), and uses requestIdleCallback in browsers when available (2-second timeout; short timer fallback elsewhere). It does not run in the synchronous render hot path. Explicit prune() still runs immediately:

const result = html.compiledCache.prune();
console.log(result);

Invalidate one compiled template when you have its TemplateStringsArray:

html.compiledCache.invalidate(strings, html.compiler);

Clear the whole compiled namespace:

await html.compiledCache.clear({ backend: true });

Cold localStorage lookup behavior

The compiled localStorage backend keeps a tiny namespace manifest. It is read once per backend instance and tells the cache which hashed payload buckets can exist. For a brand-new/known-empty namespace, repeated template misses therefore compile directly without performing a payload localStorage.getItem() for every template. On a warm namespace, only keys present in the manifest read their compiled payload. Pre-manifest cache namespaces are not scanned in the template hot path; idle maintenance discovers their legacy buckets and upgrades the manifest. Until that one-time migration, legacy-only entries may take a normal cold compile.

The cachable path now computes the structural descriptor once inside the fused resolver. Its live BuiltRoot LRU is bounded by the familiar top-level maxEntries, ttlMs, admission, and maxTemplateChars options. Persistent normalization thresholds and retention remain under compiled. html.cache is a compatibility/control facade over that live-root L2; it no longer sits in front of every tag call.

With debug: true, the cachable entry reports fused compiled events, including memory-hit, persistent hit, miss, bypass, write, prune, expired, and invalid events. Aggregate compiled timing is exposed through html.compiledCache.stats() (totalLookupMs, totalDeserializeMs, totalCompileMs, totalSerializeMs, totalWriteMs, totalPruneMs, and averageResolveMs).

This keeps the default solid-html-cache entry on official @solidjs/html. Only the /cachable subpath requires the experimental engine.

Debug controls

Debugging is opt-in. With debug omitted/false, no debug reporter is created and cache timing/logging hooks are not executed.

const html = createSolidCachedHtml({
  backend: new LocalStorageBackend({ namespace: "app-v1" }),
  debug: {
    label: "main-html",
    sources: ["structural"],
    events: ["miss-insert", "runtime-hit", "callsite-hit"],
    includeKeys: false,
    includeNamespace: true,
    includeBackend: true,
    timings: true
  }
});

Example output:

[solid-html-cache][structural] miss-insert 0.041ms chars=128 backend=localStorage namespace=app-v1 label=main-html

For the normalized built-template cache:

const html = createLocalStorageHtml({
  namespace: "app-v1",
  debug: {
    label: "compiled-html",
    sources: ["compiled"],
    events: ["hit", "miss", "write", "prune"],
    onEvent(event) {
      // Forward to custom devtools/telemetry if desired.
    }
  }
});

debug.logger(message, event) can replace console.debug, and debug.log: false keeps only onEvent. Use debug.timings: false for hit/miss/event diagnostics without any performance.now() calls. The stock structural layer and the compiled cache report backend kind and namespace when those values exist. On the fused /cachable path, persistence belongs to html.compiledCache, so localStorage backend/namespace diagnostics are emitted as compiled events rather than pretending the L2 memory facade owns a storage backend. You may override the displayed namespace with debug.namespace.

Runtime controls

Cached tags expose lightweight runtime controls directly on the tag:

html.disable();        // bypass solid-html-cache layers
html.enable();
html.isEnabled();
html.enabled;

await html.flush();
await html.prune();
await html.clear();
await html.clear({ persistent: true });

console.log(html.stats());
html.resetStats();

disable() is stronger than maxEntries: 0: it skips structural key generation, metadata/admission bookkeeping, and package persistence work. It does not and cannot disable @solidjs/html / solid-html-cachable's own native per-callsite identity cache. Existing stored cache entries are retained for a later enable(); use clear({ persistent: true }) when you also want to remove persisted data.

For solid-html-cache/cachable, both package-owned layers live in the fused controller but retain independent public controls for compatibility:

  • html.cache.disable() bypasses only the live structural BuiltRoot L2;
  • html.compiledCache.disable() bypasses persistent L3 lookup/write while L2 can continue to serve cross-callsite reuse;
  • tag-level html.disable() disables both L2 and L3.

html.cache.flush() is a no-op on the fused path because L2 has no separate portable metadata backend. Use html.flush() or html.compiledCache.flush() when pending normalized-root writes must be durable. Its stats() returns:

{
  structural: html.cache.stats(),
  compiled: html.compiledCache.stats()
}

Optional synchronous tag-call measurement

Tag-call measurement is intentionally a separate opt-in wrapper so normal cached tags pay no timer or measurement-wrapper cost.

import html from "solid-html-cache";
import { measureHtmlTag } from "solid-html-cache/measure";

const measuredHtml = measureHtmlTag(html, { label: "app-html" });
const view = measuredHtml`<section>${value}</section>`;

console.table(measuredHtml.measurement.stats());

With createLocalStorageHtml(), backend/namespace metadata is inferred from the cache controller when available:

import { createLocalStorageHtml } from "solid-html-cache/cachable";
import { measureHtmlTag } from "solid-html-cache/measure";

const html = createLocalStorageHtml({ namespace: "app-v1" });
const measured = measureHtmlTag(html);

measured`<b>${123}</b>`;
// [solid-html-cache][tag] call 1.234ms chars=7 values=1 backend=localStorage namespace=app-v1

The timer starts immediately before the underlying tag function is invoked and stops immediately after it returns. It includes synchronous cache lookup/restore/compile and Solid tagged-HTML work performed inside the call. It does not include later DOM insertion by a parent, layout, paint, FCP/LCP, later effects, or asynchronous work. Use the Journey benchmark for those end-to-end browser metrics. Access measured.unmeasured to use the exact original tag again.

Variance-controlled fused-cache benchmarks

The internal benchmark harness includes a paired/interleaved comparison for the old 0.0.6 cachable-memory canonicalization path versus the 0.0.8 fused BuiltRoot L2. Each Chromium round alternates AB/BA execution order, preserves every raw pair, and reports median/mean paired deltas, p25/p75, sample standard deviation, fused win count, and a two-sided 95% Student-t confidence interval for the mean paired delta. Positive paired deltas mean the fused candidate was faster.

npm run bench:journey:paired:quick
npm run bench:journey:paired

The paired browser run measures only the cold step and applies measureHtmlTag() to both implementations symmetrically. The JSON/HTML artifacts are written to artifacts/journey-paired*.{json,html}. If the 95% interval spans zero, treat the observed mean difference as indistinguishable from run-to-run noise at that sample size.

The 0.0.8 finalization run on Chromium 153 (30 paired rounds) was mostly statistically neutral versus the legacy cachable-memory path. One case, large / dynamic-heavy measured synchronous tag time, showed a significant regression for fused caching (mean paired delta -2.07 ms, 95% CI -3.56 to -0.59 ms; positive means fused faster). Accordingly, 0.0.8 does not claim a universal cold-path speedup. The fused design is retained for the simpler direct BuiltRoot layering and the separately demonstrated persistent reload benefit.

A separate controller microbenchmark runs the same deterministic first-callsite input stream through both architectures without DOM/layout/paint work. The old path uses HtmlCacheController.canonicalize() plus a simulated native WeakMap; the fused path uses CompiledTemplateCacheController.resolve() plus the same simulated native WeakMap boundary. This isolates structural-key/LRU/controller overhead.

npm run bench:micro:node
npm run bench:micro:bun
npm run bench:micro:all

For a complete variance pass (Chromium paired benchmark followed by Node and Bun microbenchmarks):

npm run bench:variance:all

The real-browser Journey remains authoritative for user-facing performance. Node/Bun microbenchmarks are explanatory diagnostics for controller-level cost and runtime-engine variation.