+
diff --git a/src/lib/sample.ts b/src/lib/sample.ts
deleted file mode 100644
index 429c922..0000000
--- a/src/lib/sample.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const sample = 'TODO - publish tsv!';
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 1e580bb..6411db4 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -4,13 +4,13 @@
import './style.css';
import ThemeRoot from '@fuzdev/fuz_ui/ThemeRoot.svelte';
- import {SiteState, site_context} from '@fuzdev/fuz_ui/site.svelte.ts';
- import {logo_tsv} from '@fuzdev/fuz_ui/logos.ts';
- import type {Snippet} from 'svelte';
+ import { SiteState, site_context } from '@fuzdev/fuz_ui/site.svelte.ts';
+ import { logo_tsv } from '@fuzdev/fuz_ui/logos.ts';
+ import type { Snippet } from 'svelte';
import pkg_json from 'virtual:pkg.json';
const {
- children,
+ children
}: {
children: Snippet;
} = $props();
@@ -19,7 +19,7 @@
// and `repo_url` points to the tsv tool repo rather than this website's repo
// (`pkg_json.repository`).
site_context.set(
- new SiteState({icon: logo_tsv, pkg_json, repo_url: 'https://github.com/fuzdev/tsv'}),
+ new SiteState({ icon: logo_tsv, pkg_json, repo_url: 'https://github.com/fuzdev/tsv' })
);
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index e98aa2e..13a24d8 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,10 +1,10 @@
diff --git a/src/routes/docs/+layout.svelte b/src/routes/docs/+layout.svelte
index a161ea3..28a2d09 100644
--- a/src/routes/docs/+layout.svelte
+++ b/src/routes/docs/+layout.svelte
@@ -1,13 +1,13 @@
- tsv is a toolchain for TypeScript/JS, CSS, and Svelte in Rust. Performance is its priority
- after correctness, and the numbers compare favorably to Oxc and Biome for its supported
- languages.
+ tsv is a toolchain for TypeScript/JS, CSS, and Svelte in Rust. After correctness, performance
+ is tsv's next priority. This page shows how it measures up against Prettier, which tsv closely
+ follows, and against Oxc and Biome, which are similar tools with wider language support (tsv
+ doesn't support JSX/TSX/SCSS/etc).
- Rather than supporting many languages, tsv focuses on Svelte/HTML, TypeScript/JS, and CSS.
- This lets it be smaller when it's all you need, a quality that's more relevant when used in
- the browser via wasm:
+ Compared to Oxc and Biome, tsv is faster, smaller, and uses less memory to parse and format
+ its supported languages. This section has a prose summary; skip ahead for charts.
-
+
+ The measurements on this page are single-threaded and in-process, where each tool parses or
+ formats one file at a time (isolating engine speed from multi-core parallelism), measured over
+ a large collection of real-world repos: {corpus_file_count.toLocaleString('en-US')} files including
+ Svelte's official repos (svelte, kit, svelte.dev) and the
+ fuz.dev repos. On that basis:
+
+
+
+ Formatting TypeScript, tsv is ~{format_ts_vs_oxfmt} faster than Oxfmt (native-vs-native), ~{format_ts_vs_prettier}
+ faster than Prettier (native Rust vs Prettier's JS), and ~{format_ts_vs_biome} faster than Biome
+ (wasm-vs-wasm).
+
+
+ Formatting Svelte, tsv is is ~{format_svelte_vs_prettier} faster than Prettier (which Oxfmt's
+ Svelte path delegates to internally) and {format_svelte_vs_biome} faster than Biome.
+
+
+ Formatting CSS, it's ~{format_css_vs_oxfmt} faster than Oxfmt and {format_css_vs_biome} faster
+ than Biome.
+
+
+ Parsing TypeScript to JSON, tsv is ~{parse_ts_vs_oxc} faster than Oxc; Biome doesn't expose its
+ parser to JS. tsv's default AST adds a per-node line/column loc for drop-in Svelte
+ compatibility, with a fast path to reconstruct locs in JS.
+
+
+ A fork of Oxc's official bench-formatter
+ is an end-to-end CLI benchmark with its own corpus. On a real TypeScript repo, tsv formats ~{cli_ts_wall_vs_oxfmt}
+ faster than Oxfmt and ~{cli_ts_wall_vs_biome} faster than Biome using
+ {cli_ts_memory ? format_ratio_range(cli_ts_memory.min, cli_ts_memory.max) : '—'} less memory.
+ Wall-clock measurements are warped by each tool's multi-file parallelism, so they scale with core
+ count and are machine-dependent, and could change with tuning.
+
+
+
+ Each section below has notes that attempt to fairly contextualize its numbers. The charts show
+ numbers using Node v24, and at the end of the page is a cross-runtime comparison.
+
+
+
+
+
+
+
+
+
+
+
+ tsv's formatter is similar to Oxfmt, Biome, and
+ dprint. It formats Svelte, TypeScript,
+ and CSS, plus JS as strict-mode TypeScript:
+
+ {#each format_groups as group (group.language)}
+
+ {/each}
-
-
+
+
+ The parse entries that build a full JS AST are comparable in mechanism: tsv and oxc-parser
+ both serialize the AST to JSON in Rust and deserialize it in JS, native and wasm alike. But
+ the deliverables differ — tsv's default wire (tsv-json /
+ tsv_wasm-json) carries a per-node loc (line/column) object that
+ oxc-parser's default span-only AST omits, and that loc is roughly half the wire
+ bytes and most of its JSON.parse cost. The tsv-json-no-locations /
+ tsv_wasm-json-no-locations entries drop it, emitting the same span-only shape oxc does,
+ so those are the payload-matched, apples-to-apples comparison with oxc-parser (line/column stays
+ derivable from the offsets plus source, so nothing is lost). The tsv-internal and tsv_wasm-internal
+ entries build the native AST but skip JS-side materialization, so they show raw in-engine speed
+ rather than a cross-tool comparison.
+
+ {#each parse_groups as group (group.language)}
+
+ {/each}
+
-
+
- tsv's formatter is similar to Oxfmt
- and Biome. Today it can format Svelte, TypeScript, and CSS,
- plus HTML and JS (as strict-mode TypeScript):
+ Separate from the speed numbers above that use a real-world corpus of code, this section
+ measures parse coverage: how much of a much larger, deliberately hard corpus each
+ parser accepts — Prettier's format-test suites, Svelte's compiler test suite, CSS extracted
+ from
+ web-platform-tests, and
+ test262's expected-valid strict-mode tests.
- {#each format_groups as group (group.language)}
-
- {/each}
+
+
+
+
+
+
+ Rather than supporting many languages, tsv focuses on Svelte/HTML, TypeScript/JS, and CSS.
+ This lets it be smaller when it's all you need, a quality that's more relevant when used in
+ the browser via wasm.
+
+
+
-
+
- The parse rows that build a full JS AST are directly comparable: tsv and oxc-parser both
- serialize the AST to JSON in Rust and deserialize it in JS, native and wasm alike. The
- tsv-internal and tsv_wasm-internal rows are tsv's parse-only numbers - they build the native
- AST but skip JS-side materialization, so they show raw in-engine speed rather than a
- cross-tool comparison.
+ The numbers above measure tsv's engine in-process, one file at a time, using tsv's original
+ benchmarks. This section describes the results from a fork of Oxc's official
+ bench-formatter
+ that
+ adds tsv, timing
+ the whole CLI end-to-end — process spawn, file discovery, I/O, and each tool's default
+ multi-file parallelism — plus peak memory. It's the "what you experience typing the command"
+ measure, run on real repositories. tsv appears in the two JSX-free scenarios (it has no
+ JSX/TSX parser).
- {#each parse_groups as group (group.language)}
-
- {/each}
+
@@ -152,39 +381,53 @@
All numbers are single-threaded: every library formats or parses one file at a time, measured
- sequentially with no cross-file parallelism. These are per-file, single-core latency and
- throughput numbers - not the multi-core batch throughput a CLI gets when it formats many files
- at once, which most of these tools (tsv included) can do.
+ sequentially with no cross-file parallelism. Each row is the total time to process the whole
+ corpus once on a single core — not the multi-core batch throughput a CLI gets when it formats
+ many files at once, which most of these tools (tsv included) can do.
- What's measured: around 5,500 files (~15 MB) of
- .svelte/.html, .ts/.js, and
- .css, from three sources: the fuz.dev libraries and apps, upstream framework
- source (Svelte, SvelteKit, and the svelte.dev site), and formatter conformance fixtures
- (Prettier's and prettier-plugin-svelte's own test suites - deliberately tricky edge cases, not
- typical code, so they skew the corpus toward hard cases).
+ What's measured: {corpus_file_count.toLocaleString('en-US')} files of .svelte,
+ .ts/.js, and .css — real-world code only, from two
+ sources: the author's libraries, apps, and sites (the fuz.dev ecosystem plus personal
+ SvelteKit sites), and upstream framework source (Svelte, SvelteKit, and the svelte.dev site).
+ The CSS set also includes real-authored CSS extracted from those components'
+ <style> blocks, concatenated per repo — standalone CSS files are rare in this
+ ecosystem, and the same bytes appear in the Svelte rows (rows are never summed). Test files count
+ as real code and stay in; fixture files (the formatter test suites that used to be part of this
+ corpus, and fixture subtrees inside the measured repos) are excluded — deliberately tricky edge
+ cases measure conformance, not typical throughput, and are covered by the parse-conformance section
+ above.
-
-
+
+
+
+ The same benchmark harness runs under three JS runtimes — Node, Deno, and Bun. The headline
+ numbers above are the Node run. The native entry differs by runtime: Node and Bun load tsv's
+ N-API addon, while Deno loads its C-FFI library. They share code but cross a different binding
+ boundary, so a per-runtime delta on the same row is a runtime effect — the JS engine or the
+ binding boundary — not a difference in tsv's own engine, which is identical across all three.
+
+
+
+
+
diff --git a/src/routes/docs/benchmarks/BenchmarksBar.svelte b/src/routes/docs/benchmarks/BenchmarksBar.svelte
index 128c7b5..258add9 100644
--- a/src/routes/docs/benchmarks/BenchmarksBar.svelte
+++ b/src/routes/docs/benchmarks/BenchmarksBar.svelte
@@ -3,7 +3,7 @@
category_color,
format_label,
type FormattedUnit,
- type ImplementationCategory,
+ type ImplementationCategory
} from './benchmark_data.ts';
const {
@@ -14,6 +14,9 @@
ratio_text,
ratio_color,
annotation,
+ disabled = false,
+ on_enter,
+ on_leave
}: {
label: string;
bar_fraction: number;
@@ -24,48 +27,96 @@
// optional extra context shown between value and ratio (corpus coverage in
// benchmark groups, gzipped size in binary-size groups); omitted when absent
annotation?: string | undefined;
+ // a grayed-out, inert placeholder (a tool that doesn't run in this group) —
+ // no bar, no value, no ratio, just the label held in its shared slot
+ disabled?: boolean;
+ // hover-to-rebaseline: fired when the pointer enters/leaves the row so the
+ // group can adopt this row as its ratio anchor; omitted on inert placeholders
+ on_enter?: (() => void) | undefined;
+ on_leave?: (() => void) | undefined;
} = $props();
+
+ // the parenthesized binding suffix (`(wasm)`/`(node napi)`) describes how the
+ // tool actually ran in this (Node) report - meaningless for the disabled
+ // placeholders of single-variant tools (biome and dprint each ship only a wasm
+ // build here, so there's no ambiguity to lose). oxc-parser keeps its suffix even
+ // when disabled since it has two placeholder rows (napi and wasm) that would
+ // otherwise become indistinguishable.
+ const display_label = $derived(
+ disabled && (category === 'biome' || category === 'dprint')
+ ? format_label(label).replace(/ \([^)]*\)$/, '')
+ : format_label(label)
+ );
-
+ sweeps/sec — one sweep is a full pass over the group's timed file set (higher is faster); ratios
+ are vs {base} (> 1 = faster than {base}). A
+ fail is an implementation that runtime can't load (see notes above). The
+ native rows load each runtime's idiomatic binding of the same engine — the N-API
+ addon under node and bun (tsv (node napi)), the C-FFI
+ library under deno (tsv (deno ffi)) — so the deno column is
+ a first-class FFI-vs-N-API comparison, not a re-run of the same binding.
+
+
+
+
diff --git a/src/routes/docs/benchmarks/BenchmarksGroup.svelte b/src/routes/docs/benchmarks/BenchmarksGroup.svelte
index d3e2cb8..585348d 100644
--- a/src/routes/docs/benchmarks/BenchmarksGroup.svelte
+++ b/src/routes/docs/benchmarks/BenchmarksGroup.svelte
@@ -1,30 +1,33 @@
@@ -35,25 +38,7 @@
{count_label}
{group.language} files
- {#if has_coverage}files handled / total · speed{/if}
+ total time · speed
-
- {#each group.entries as entry (entry.name)}
-
- {/each}
-
- {#each wasm_display as s (s.label)}
-
- {/each}
-
-
-{/if}
-
-{#if native_display.length > 0}
+{#each size_groups as group (group.capability)}
-
native
-
- {#each native_display as s (s.label)}
-
- {/each}
-
+
{group.heading}
+
-{/if}
+{/each}
diff --git a/src/routes/docs/benchmarks/benchmark_data.ts b/src/routes/docs/benchmarks/benchmark_data.ts
index 9bf0958..4add8af 100644
--- a/src/routes/docs/benchmarks/benchmark_data.ts
+++ b/src/routes/docs/benchmarks/benchmark_data.ts
@@ -1,7 +1,12 @@
-// Raw types matching the tsv bench.ts `Baseline` format
+// Raw types matching the tsv bench's per-runtime report format
+// (`benches/js/results/report..json` — the site's flagship detailed
+// view is the Node report, the N-API native path)
export interface BenchmarkBaseline {
version: number;
+ // The runtime that produced this report (`node` for the flagship view).
+ // Present from report `version` 5 on.
+ runtime?: string;
timestamp: string;
git_commit: string;
corpus: Record;
@@ -11,23 +16,91 @@ export interface BenchmarkBaseline {
// Counts of silenced third-party stderr noise, keyed by message pattern.
// Present from baseline `version` 4 on; not rendered, kept for parity.
suppressed_noise?: Record;
+ // Which corpus/surface produced the report: `perf` (real-world corpus,
+ // format + parse) or `conformance` (the deliberately-hard fixture suites,
+ // disjoint from the perf corpus, parse only). Present from `version` 6 on.
+ corpus_kind?: 'perf' | 'conformance';
+ // Per-entry corpus composition (path + loaded file count) — discloses which
+ // sources were present on the machine that produced the report. Present
+ // from `version` 6 on.
+ corpus_sources?: Array;
+ // The machine that produced the report — CPU model, OS/arch, runtime version.
+ // The throughput numbers are machine-relative, so this is the environment the
+ // meta panel discloses. Present from `version` 7 on (absent on older reports).
+ machine?: Machine;
}
+// The hardware/runtime a report was measured on. Excludes hostname (the reports
+// are published) and volatile fields (free memory, load) that would churn.
+export interface Machine {
+ cpu_model: string;
+ os: string;
+ arch: string;
+ // The producing runtime's own version (`node`/`deno`/`bun` version string).
+ runtime_version: string;
+}
+
+export interface CorpusSource {
+ path: string;
+ files: number;
+ // Per-language split of `files` (svelte/typescript/css counts summing to
+ // `files`). Present on reports whose loader emitted it; older reports carry
+ // only the `files` total, so treat it as optional.
+ by_language?: Partial>;
+ // The source's GitHub origin, git-detected by the bench at report-build time
+ // (URL + commit + subpath). Present from `version` 8 on; absent on older
+ // reports and on sources with no GitHub remote — treat as optional.
+ repo?: CorpusRepoRef;
+}
+
+// A corpus source's GitHub origin — see `CorpusSource.repo` and `corpus_source_url`.
+export interface CorpusRepoRef {
+ // Canonical https GitHub URL, e.g. `https://github.com/sveltejs/svelte`.
+ url: string;
+ // `owner/name` (e.g. `sveltejs/svelte`) — a compact label.
+ slug: string;
+ // The commit the corpus was loaded at (full SHA); `''` for a harvested cache
+ // linked at its canonical upstream root (no pin).
+ commit: string;
+ // Path within the repo to this source (`''` = repo root).
+ subpath: string;
+}
+
+/**
+ * The GitHub URL for a corpus source, pinned to the measured commit + subpath
+ * (`…/tree//`) when detected, or the repo root for a
+ * canonical-upstream cache (empty `commit`). `undefined` when the source has no
+ * detected origin (older reports, or the local `svelte_styles` cache).
+ */
+export const corpus_source_url = (source: CorpusSource): string | undefined => {
+ const repo = source.repo;
+ if (!repo) return undefined;
+ if (!repo.commit) return repo.url;
+ return repo.subpath
+ ? `${repo.url}/tree/${repo.commit}/${repo.subpath}`
+ : `${repo.url}/tree/${repo.commit}`;
+};
+
export interface BaselineEntry {
name: string;
group: string;
- mean_ns: number;
- p50_ns: number;
- p75_ns: number;
- p90_ns: number;
- p95_ns: number;
- p99_ns: number;
- min_ns: number;
- max_ns: number;
- std_dev_ns: number;
- cv: number;
- ops_per_second: number;
- sample_size: number;
+ // Timing stats are `null` on a coverage-only report (the conformance surface
+ // the site refreshes from — parse coverage measured, timed phase skipped). A
+ // perf report always carries real numbers, and only the perf path
+ // (`derive_benchmark_groups`) reads these, so the nulls are unreachable there
+ // but must be expressed for the shared cast to stay sound.
+ mean_ns: number | null;
+ p50_ns: number | null;
+ p75_ns: number | null;
+ p90_ns: number | null;
+ p95_ns: number | null;
+ p99_ns: number | null;
+ min_ns: number | null;
+ max_ns: number | null;
+ std_dev_ns: number | null;
+ cv: number | null;
+ ops_per_second: number | null;
+ sample_size: number | null;
// Per-implementation preflight coverage: files this impl processed / the
// language's total discovered files. Present from baseline `version` 3 on;
// absent (or `null`) in older baselines.
@@ -36,6 +109,9 @@ export interface BaselineEntry {
// Files this impl was actually timed on (the per-group intersection in
// default mode). Present from baseline `version` 4 on.
files_iterated?: number | null;
+ // Present from report `version` 5 on (matches the report's top-level);
+ // not rendered, kept for parity.
+ runtime?: string;
}
export interface BaselineVersions {
@@ -48,6 +124,9 @@ export interface BaselineVersions {
oxc_parser?: string;
oxfmt?: string;
biome?: string;
+ // `@dprint/typescript` — the plugin version (the host `@dprint/formatter` is
+ // just the Wasm loader). Absent on reports produced before the dprint row.
+ dprint?: string;
}
export interface BinarySize {
@@ -68,6 +147,7 @@ export type ImplementationCategory =
| 'tsv_wasm'
| 'tsv_wasm_json'
| 'biome'
+ | 'dprint'
| 'oxc';
export interface BenchmarkGroup {
@@ -84,10 +164,14 @@ export interface BenchmarkDisplayEntry {
name: string;
mean_ns: number;
bar_fraction: number;
- speedup_vs_canonical: number | undefined;
category: ImplementationCategory;
files_processed: number | null;
files_total: number | null;
+ // A placeholder entry mirrored from another language's group for a tool that
+ // doesn't run in this one (e.g. `oxc-parser` under svelte/css parse) — rendered
+ // grayed-out and inert so the parse groups share one entry order. Absent on real,
+ // measured entries.
+ disabled?: boolean;
}
export interface SpeedupRow {
@@ -105,35 +189,29 @@ const CATEGORY_BY_NAME: Record = {
'acorn-typescript': 'canonical',
tsv: 'tsv_native',
'tsv-json': 'tsv_native_json',
+ 'tsv-json-no-locations': 'tsv_native_json',
'tsv-internal': 'tsv_native',
tsv_wasm: 'tsv_wasm',
'tsv_wasm-json': 'tsv_wasm_json',
+ 'tsv_wasm-json-no-locations': 'tsv_wasm_json',
'tsv_wasm-internal': 'tsv_wasm',
'biome-wasm': 'biome',
+ 'dprint-wasm': 'dprint',
'oxc-parser': 'oxc',
'oxc-parser-wasm': 'oxc',
- oxfmt: 'oxc',
+ oxfmt: 'oxc'
};
-const categorize_name = (name: string): ImplementationCategory => CATEGORY_BY_NAME[name] ?? 'oxc';
+export const categorize_name = (name: string): ImplementationCategory =>
+ CATEGORY_BY_NAME[name] ?? 'oxc';
export const categorize_size = (label: string): ImplementationCategory => {
// covers `tsv_wasm` plus the `tsv_format_wasm`/`tsv_parse_wasm` subsets
if (label.startsWith('tsv') && label.includes('wasm')) return 'tsv_wasm';
if (label.startsWith('tsv')) return 'tsv_native';
if (label.startsWith('biome')) return 'biome';
- if (label.includes('oxc') || label.includes('oxfmt')) return 'oxc';
- return 'oxc';
-};
-
-// Canonical entry names per group
-const CANONICAL_BY_GROUP: Record = {
- 'parse/svelte': 'svelte/compiler',
- 'parse/typescript': 'acorn-typescript',
- 'parse/css': 'svelte/compiler',
- 'format/svelte': 'prettier',
- 'format/typescript': 'prettier',
- 'format/css': 'prettier',
+ if (label.startsWith('dprint')) return 'dprint';
+ return 'oxc'; // oxc-parser / oxfmt, and any unrecognized label
};
// Primary tsv entry names for speedup summary (fair comparisons)
@@ -142,6 +220,48 @@ const PRIMARY_WASM_FORMAT = 'tsv_wasm';
// Derivation functions
+// Shared display order for benchmark groups: format before parse, then by
+// language — used by the detailed, conformance, and cross-runtime views alike.
+const OPERATION_ORDER: Record = { format: 0, parse: 1 };
+const LANGUAGE_ORDER: Record = {
+ svelte: 0,
+ typescript: 1,
+ css: 2
+};
+
+/**
+ * Fixed slot for a format/parse row, applied in place of a size-ordered sort so the
+ * rows read in a stable, meaningful sequence across every group: the canonical
+ * reference first (the default 1.0x anchor), then the cross-tool comparisons
+ * (alphabetically: biome, dprint, then oxc), then tsv's JSON-materializing wires (the
+ * span-only `no-locations` wire before the default `loc`-carrying one), then tsv's raw
+ * internal engine.
+ */
+const speed_entry_rank = (entry: BenchmarkDisplayEntry): number => {
+ if (entry.category === 'canonical') return 0;
+ if (entry.category === 'biome') return 1;
+ if (entry.category === 'dprint') return 2;
+ if (entry.category === 'oxc') return 3;
+ if (entry.name.endsWith('-no-locations')) return 4; // tsv json, span-only wire
+ if (entry.name.endsWith('-json')) return 5; // tsv json, loc-carrying wire
+ return 6; // tsv-internal / tsv_wasm-internal — raw in-engine, no JS materialization
+};
+
+/**
+ * Orders format/parse rows by their fixed `speed_entry_rank` slot, then wasm before
+ * native within a tier (the browser-relevant build leads each pairing), then by name
+ * — shared by the initial sort and the re-sort after disabled placeholders are mixed
+ * in, so every group scans identically.
+ */
+const compare_speed_entries = (a: BenchmarkDisplayEntry, b: BenchmarkDisplayEntry): number => {
+ const rank = speed_entry_rank(a) - speed_entry_rank(b);
+ if (rank !== 0) return rank;
+ // wasm before native within a tier
+ const kind = (a.name.includes('wasm') ? 0 : 1) - (b.name.includes('wasm') ? 0 : 1);
+ if (kind !== 0) return kind;
+ return a.name.localeCompare(b.name);
+};
+
export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array => {
const grouped: Map> = new Map();
for (const entry of baseline.entries) {
@@ -159,29 +279,27 @@ export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array e.name === canonical_name);
- const slowest = Math.max(...entries.map((e) => e.mean_ns));
+ // The sort below leads each group with its canonical reference (Prettier for
+ // format, the JS baseline for parse), so the first row is the default 1.0x
+ // anchor; the shared component reads that default off the first row and
+ // recomputes every ratio, re-baselining onto whichever row is hovered. (Size
+ // groups lead with their smallest build; see `derive_size_groups`.)
+ // `?? 0` coerces the coverage-only null (unreachable on a perf report, the
+ // only kind this runs on) so the display entry's `mean_ns` stays a number.
+ const slowest = Math.max(...entries.map((e) => e.mean_ns ?? 0));
const display_entries: Array = entries.map((e) => ({
name: e.name,
- mean_ns: e.mean_ns,
- bar_fraction: slowest > 0 ? e.mean_ns / slowest : 0,
- speedup_vs_canonical:
- canonical_entry_raw && e.name !== canonical_name
- ? canonical_entry_raw.mean_ns / e.mean_ns
- : undefined,
+ mean_ns: e.mean_ns ?? 0,
+ bar_fraction: slowest > 0 ? (e.mean_ns ?? 0) / slowest : 0,
category: categorize_name(e.name),
files_processed: e.files_processed ?? null,
- files_total: e.files_total ?? null,
+ files_total: e.files_total ?? null
}));
- // Sort: canonical first, then by mean_ns descending (slowest first for visual)
- display_entries.sort((a, b) => {
- if (a.category === 'canonical' && b.category !== 'canonical') return -1;
- if (b.category === 'canonical' && a.category !== 'canonical') return 1;
- return b.mean_ns - a.mean_ns;
- });
+ // Fixed order (see `compare_speed_entries`): canonical leads as the default 1.0x
+ // anchor, then biome, oxc, tsv's json wires, then tsv's internal engine
+ display_entries.sort(compare_speed_entries);
const iterated_counts = entries
.map((e) => e.files_iterated)
@@ -191,27 +309,166 @@ export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array e.category === 'canonical'),
- files_iterated: iterated_counts.length > 0 ? Math.max(...iterated_counts) : null,
+ files_iterated: iterated_counts.length > 0 ? Math.max(...iterated_counts) : null
});
}
- // Sort groups: format before parse, then by language
- const LANG_ORDER: Record = {svelte: 0, typescript: 1, css: 2};
- const OP_ORDER: Record = {format: 0, parse: 1};
result.sort(
(a, b) =>
- (OP_ORDER[a.operation] ?? 9) - (OP_ORDER[b.operation] ?? 9) ||
- (LANG_ORDER[a.language] ?? 9) - (LANG_ORDER[b.language] ?? 9),
+ (OPERATION_ORDER[a.operation] ?? 9) - (OPERATION_ORDER[b.operation] ?? 9) ||
+ (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9)
);
+ // Neither `biome` nor (for svelte/css) `oxc-parser` has a real entry in every
+ // parse group. `biome`'s `@biomejs/js-api` never exposes a parser to JS at all
+ // (only formatting and linting), so no parse group has a real biome entry;
+ // `oxc-parser` only parses TypeScript/JS, so the svelte and css parse groups
+ // lack it. Mirror both in as disabled placeholders — biome always, oxc-parser
+ // only where it's missing — then re-sort so they fall into their fixed slots
+ // (biome then oxc, right after the canonical row), giving all three parse groups
+ // one shared entry order.
+ const ts_parse = result.find((g) => g.operation === 'parse' && g.language === 'typescript');
+ const oxc_templates = ts_parse?.entries.filter((e) => e.category === 'oxc') ?? [];
+ for (const group of result) {
+ if (group.operation !== 'parse') continue;
+ const biome_placeholder: BenchmarkDisplayEntry = {
+ name: 'biome-wasm',
+ mean_ns: 0,
+ bar_fraction: 0,
+ category: 'biome',
+ files_processed: null,
+ files_total: null,
+ disabled: true
+ };
+ const needs_oxc =
+ group.language !== 'typescript' && !group.entries.some((e) => e.category === 'oxc');
+ const oxc_placeholders: Array = needs_oxc
+ ? oxc_templates.map((e) => ({
+ ...e,
+ bar_fraction: 0,
+ files_processed: null,
+ files_total: null,
+ disabled: true
+ }))
+ : [];
+ group.entries.push(biome_placeholder, ...oxc_placeholders);
+ group.entries.sort(compare_speed_entries);
+ }
+
+ // The format-side analogue: `dprint` formats TypeScript/JS only — its
+ // `@dprint/typescript` plugin rejects CSS and Svelte outright (dprint's CSS and
+ // HTML plugins are separate Wasm plugins the bench doesn't load) — so the svelte
+ // and css FORMAT groups have no real dprint entry. Mirror it in as a disabled
+ // placeholder so all three format groups share one entry order, exactly as
+ // oxc-parser is mirrored into the svelte/css parse groups above. Guarded on the
+ // template existing, so a report predating the dprint row renders unchanged.
+ const ts_format = result.find((g) => g.operation === 'format' && g.language === 'typescript');
+ const dprint_templates = ts_format?.entries.filter((e) => e.category === 'dprint') ?? [];
+ if (dprint_templates.length > 0) {
+ for (const group of result) {
+ if (group.operation !== 'format') continue;
+ if (group.entries.some((e) => e.category === 'dprint')) continue;
+ group.entries.push(
+ ...dprint_templates.map((e) => ({
+ ...e,
+ bar_fraction: 0,
+ files_processed: null,
+ files_total: null,
+ disabled: true
+ }))
+ );
+ group.entries.sort(compare_speed_entries);
+ }
+ }
+
+ return result;
+};
+
+// Parse-conformance coverage (the conformance report's headline metric)
+
+export interface ConformanceRow {
+ name: string;
+ files_processed: number;
+ files_total: number;
+ // files_processed / files_total, rendered as the coverage percentage
+ coverage_fraction: number;
+}
+
+export interface ConformanceGroup {
+ language: string;
+ // the language's total discovered files (every row shares it)
+ files_total: number;
+ rows: Array;
+}
+
+/**
+ * One coverage row per ENGINE, not per binding: the conformance headline is
+ * "which files does this parser accept," which is identical across a tool's
+ * native/wasm/internal variants — so the `-wasm` and `-internal` duplicates
+ * are dropped and `tsv-json` stands in for tsv (relabeled plainly, since the
+ * JSON-materialization qualifier is a speed concern, not a coverage one).
+ */
+const CONFORMANCE_ENGINE_NAMES: Record = {
+ 'svelte/compiler': 'svelte/compiler',
+ 'acorn-typescript': 'acorn-typescript',
+ 'tsv-json': 'tsv',
+ 'oxc-parser': 'oxc-parser'
+};
+
+/**
+ * Derives per-language parse-coverage groups from a conformance report
+ * (`corpus_kind: 'conformance'` — parse groups only). Rows are ordered by coverage,
+ * highest first; entries without coverage data are dropped.
+ */
+export const derive_conformance_groups = (baseline: BenchmarkBaseline): Array => {
+ const by_language: Map> = new Map();
+ for (const entry of baseline.entries) {
+ const [operation, language] = entry.group.split('/');
+ if (operation !== 'parse' || !language) continue;
+ const display_name = CONFORMANCE_ENGINE_NAMES[entry.name];
+ if (!display_name) continue;
+ if (entry.files_processed == null || entry.files_total == null) continue;
+ let rows = by_language.get(language);
+ if (!rows) {
+ rows = [];
+ by_language.set(language, rows);
+ }
+ rows.push({
+ name: display_name,
+ files_processed: entry.files_processed,
+ files_total: entry.files_total,
+ coverage_fraction: entry.files_total > 0 ? entry.files_processed / entry.files_total : 0
+ });
+ }
+
+ const result: Array = [];
+ for (const [language, rows] of by_language) {
+ // coverage descending (highest acceptance first), name as a stable tiebreak
+ rows.sort((a, b) => b.coverage_fraction - a.coverage_fraction || a.name.localeCompare(b.name));
+ result.push({
+ language,
+ files_total: Math.max(0, ...rows.map((r) => r.files_total)),
+ rows
+ });
+ }
+ result.sort((a, b) => (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9));
return result;
};
+/**
+ * Formats a coverage fraction as a percentage with two decimals (`99.85%`),
+ * FLOORED rather than rounded — rounding would render e.g. 44219/44220 as
+ * `100.00%` next to a visibly non-total count. Only exact totality reads 100%
+ * (matching the harness's own `coverage_pct` convention in tsv's report.ts).
+ */
+export const format_coverage_percent = (fraction: number): string =>
+ `${(Math.floor(fraction * 10_000) / 100).toFixed(2)}%`;
+
export const derive_speedup_summary = (groups: Array): Array => {
const find_speedup = (
operation: string,
language: string,
- primary_name: string,
+ primary_name: string
): number | undefined => {
const group = groups.find((g) => g.operation === operation && g.language === language);
if (!group?.canonical_entry) return undefined;
@@ -225,17 +482,277 @@ export const derive_speedup_summary = (groups: Array): Array {
+ const has_parse = label.includes('parse');
+ const has_format = label.includes('format') || label.includes('fmt');
+ // a build that does both is a full toolchain (e.g. the combined oxc-parser + oxfmt entry)
+ if (has_parse && has_format) return 'full';
+ if (has_parse) return 'parser'; // tsv parse (ffi), tsv_parse_wasm, oxc-parser
+ if (has_format) return 'formatter'; // tsv format, oxfmt
+ return 'full'; // tsv (napi/ffi), tsv_wasm, biome
+};
+
+export interface SizeDisplayEntry extends BinarySize {
+ bar_fraction: number;
+ category: ImplementationCategory;
+ // a grayed-out, inert placeholder for a build that doesn't exist (e.g. oxfmt's
+ // absent wasm build) — no bar, no size, just the label held in its slot. Absent
+ // on real, measured entries.
+ disabled?: boolean;
+}
+
+export interface SizeCapabilityGroup {
+ capability: SizeCapability;
+ heading: string;
+ // sorted smallest-first, so the leading entry is the default ratio anchor (1.0x)
+ entries: Array;
+}
+
+const SIZE_CAPABILITY_ORDER: ReadonlyArray<{
+ capability: SizeCapability;
+ heading: string;
+}> = [
+ { capability: 'full', heading: 'Full toolchain (parse + format)' },
+ { capability: 'formatter', heading: 'Formatter' },
+ { capability: 'parser', heading: 'Parser' }
+];
+
+/** Display label for the synthesized combined oxc full-toolchain build. */
+export const OXC_FULL_LABEL = 'oxc-parser + oxfmt (napi)';
+
+/** Display label for oxfmt's absent wasm build placeholder — see `derive_size_groups`. */
+export const OXFMT_WASM_LABEL = 'oxfmt (wasm)';
+
+/**
+ * Synthesizes oxc's full-toolchain native build by summing its separately-shipped
+ * parser (`oxc-parser (napi)`) and formatter (`oxfmt (napi)`) packages — together
+ * they're the closest equivalent to tsv's single parse+format build, so the entry
+ * stands beside `tsv (napi)` in the full-toolchain group. Returns `undefined` when
+ * either half is missing (older baselines), and sums gzip only when both carry it.
+ */
+const synthesize_oxc_full = (sizes: Array): BinarySize | undefined => {
+ const parser = sizes.find((s) => s.label === 'oxc-parser (napi)');
+ const formatter = sizes.find((s) => s.label === 'oxfmt (napi)');
+ if (!parser || !formatter) return undefined;
+ return {
+ label: OXC_FULL_LABEL,
+ bytes: parser.bytes + formatter.bytes,
+ kind: 'native',
+ gzip_bytes:
+ parser.gzip_bytes != null && formatter.gzip_bytes != null
+ ? parser.gzip_bytes + formatter.gzip_bytes
+ : null
+ };
+};
+
+/**
+ * Groups the binary sizes by capability (full / formatter / parser), each group
+ * mixing wasm and native builds sorted smallest-first. Bars scale to the group's
+ * largest build; the `vs` ratio anchors on the group's single smallest build, so
+ * exactly one entry reads 1.0x and every other is a multiple of it. (In the current
+ * data that smallest build is always one of tsv's, so tsv reads 1.0x and the heavier
+ * competitors read >1.0x.) A combined `oxc-parser + oxfmt` entry is
+ * synthesized into the full-toolchain group, since oxc ships parse and format apart.
+ * oxfmt has no wasm build, so the formatter group gets a disabled `oxfmt (wasm)`
+ * placeholder slotted just above its real `oxfmt (napi)` entry, holding the slot
+ * rather than omitting it.
+ */
+export const derive_size_groups = (sizes: Array): Array => {
+ const oxc_full = synthesize_oxc_full(sizes);
+ const all_sizes = oxc_full ? [...sizes, oxc_full] : sizes;
+ const groups: Array = [];
+ for (const { capability, heading } of SIZE_CAPABILITY_ORDER) {
+ const items = all_sizes.filter((s) => categorize_size_capability(s.label) === capability);
+ if (items.length === 0) continue;
+ const sorted = items.toSorted((a, b) => a.bytes - b.bytes);
+ const max = Math.max(0, ...items.map((s) => s.bytes));
+ // `sorted` is ascending, so the smallest build leads the group — its single
+ // default ratio anchor (1.0x), one baseline whether or not the group mixes wasm
+ // and native (rather than the confusing pair a per-kind anchor produced). The
+ // shared component reads every ratio from that leading row.
+ const entries: Array = sorted.map((s) => ({
+ ...s,
+ bar_fraction: max > 0 ? s.bytes / max : 0,
+ category: categorize_size(s.label)
+ }));
+ if (capability === 'formatter' && !entries.some((e) => e.label === OXFMT_WASM_LABEL)) {
+ const native_index = entries.findIndex((e) => e.label === 'oxfmt (napi)');
+ const placeholder: SizeDisplayEntry = {
+ label: OXFMT_WASM_LABEL,
+ bytes: 0,
+ kind: 'wasm',
+ gzip_bytes: null,
+ bar_fraction: 0,
+ category: categorize_size(OXFMT_WASM_LABEL),
+ disabled: true
+ };
+ entries.splice(native_index === -1 ? entries.length : native_index, 0, placeholder);
+ }
+ groups.push({ capability, heading, entries });
+ }
+ return groups;
+};
+
+// Cross-runtime combined report (the bench composer's `report.json`, `kind: 'combined'`)
+
+export type BenchmarkRuntime = 'deno' | 'node' | 'bun';
+
+export interface CrossRuntimeRow {
+ group: string;
+ name: string;
+ ops_per_second: Partial>;
+ mean_ns: Partial>;
+ files_iterated: Partial>;
+}
+
+export interface CrossRuntimeReport {
+ version: number;
+ kind: 'combined';
+ generated: string;
+ runtimes: Array;
+ // The sibling reports came from different commits/versions — ratios are
+ // unreliable until every runtime is re-run. Present from combined
+ // `version` 6 on.
+ mixed_vintage?: boolean;
+ // The sibling reports were produced on different hardware — ratios are not
+ // comparable. Present from combined `version` 7 on.
+ mixed_machine?: boolean;
+ sources: Array<{
+ runtime: BenchmarkRuntime;
+ timestamp: string;
+ git_commit: string | null;
+ tsv: string | null;
+ // The producing box's machine block; present from combined `version` 7 on.
+ machine?: Machine | null;
+ }>;
+ rows: Array;
+}
+
+export interface CrossRuntimeDisplayRow {
+ name: string;
+ category: ImplementationCategory;
+ ops_per_second: Partial>;
+ // ratio of each runtime vs the base (first present) runtime; `> 1` = faster
+ ratio_vs_base: Partial>;
+}
+
+export interface CrossRuntimeGroup {
+ group: string;
+ operation: string;
+ language: string;
+ rows: Array;
+}
+
+// The combined report stores runtimes deno-first (matching the bench's
+// `report.md`); the site presents them node-first (the flagship N-API runtime),
+// then deno, then bun.
+const CROSS_RUNTIME_DISPLAY_ORDER: Array = ['node', 'deno', 'bun'];
+
+/**
+ * The report's runtimes in the site's display order — node (the flagship, the
+ * ratio anchor) first, then deno, then bun. Shared by `derive_cross_runtime_groups`
+ * and the table headers so the anchor can't drift between them.
+ */
+export const order_cross_runtime_runtimes = (
+ runtimes: Array
+): Array => CROSS_RUNTIME_DISPLAY_ORDER.filter((r) => runtimes.includes(r));
+
+/** One runtime's own version string (`node 24.14.1`), for the cross-runtime section. */
+export interface RuntimeVersion {
+ runtime: BenchmarkRuntime;
+ version: string;
+}
+
+/**
+ * The per-runtime version strings from a cross-runtime report, in the site's
+ * display order (node first). Drops any runtime whose source carries no machine
+ * block (reports predating combined `version` 7). The cross-runtime section
+ * renders these so the three-runtime tables disclose which node/deno/bun version
+ * each column was measured under; the environment panel above stays scoped to the
+ * flagship Node baseline. The shared hardware identity (CPU/OS/arch) isn't
+ * repeated here — it lives in the environment panel, and a per-runtime hardware
+ * mismatch is the report's `mixed_machine` flag's concern.
+ */
+export const derive_runtime_versions = (report: CrossRuntimeReport): Array => {
+ const by_runtime = new Map(report.sources.map((source) => [source.runtime, source]));
+ const result: Array = [];
+ for (const runtime of order_cross_runtime_runtimes(report.runtimes)) {
+ const version = by_runtime.get(runtime)?.machine?.runtime_version;
+ if (version) result.push({ runtime, version });
+ }
+ return result;
+};
+
+/**
+ * Groups the combined report's rows by benchmark group, in the same display
+ * order as `derive_benchmark_groups` (format before parse, then svelte /
+ * typescript / css). The ratio base is the first runtime in display order
+ * (node when present — the flagship N-API path), regardless of the report's
+ * own deno-first storage order.
+ */
+export const derive_cross_runtime_groups = (
+ report: CrossRuntimeReport
+): Array => {
+ const runtimes = order_cross_runtime_runtimes(report.runtimes);
+ const base = runtimes[0];
+ const grouped: Map> = new Map();
+ for (const row of report.rows) {
+ let rows = grouped.get(row.group);
+ if (!rows) {
+ rows = [];
+ grouped.set(row.group, rows);
+ }
+ const base_ops = base ? row.ops_per_second[base] : undefined;
+ const ratio_vs_base: Partial> = {};
+ for (const runtime of runtimes) {
+ const ops = row.ops_per_second[runtime];
+ if (ops != null && base_ops != null && base_ops > 0) {
+ ratio_vs_base[runtime] = ops / base_ops;
+ }
+ }
+ rows.push({
+ name: row.name,
+ category: categorize_name(row.name),
+ ops_per_second: row.ops_per_second,
+ ratio_vs_base
+ });
+ }
+
+ const result: Array = [];
+ for (const [group, rows] of grouped) {
+ const [operation, language] = group.split('/');
+ result.push({ group, operation: operation!, language: language!, rows });
+ }
+ result.sort(
+ (a, b) =>
+ (OPERATION_ORDER[a.operation] ?? 9) - (OPERATION_ORDER[b.operation] ?? 9) ||
+ (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9)
+ );
+ return result;
+};
+
// Formatting utilities
export interface FormattedUnit {
@@ -244,32 +761,109 @@ export interface FormattedUnit {
}
export const format_ns = (ns: number): FormattedUnit => {
- if (ns < 1_000) return {value: `${Math.round(ns)}`, unit: 'ns'};
+ if (ns < 1_000) return { value: `${Math.round(ns)}`, unit: 'ns' };
if (ns < 1_000_000)
return {
value: (ns / 1_000).toFixed(ns < 10_000 ? 2 : ns < 100_000 ? 1 : 0),
- unit: 'µs',
+ unit: 'µs'
};
const ms = Math.round(ns / 1_000_000);
- return {value: ms.toLocaleString('en-US'), unit: 'ms'};
+ return { value: ms.toLocaleString('en-US'), unit: 'ms' };
};
export const format_bytes = (bytes: number): FormattedUnit => {
- if (bytes < 1_024) return {value: `${bytes}`, unit: 'B'};
- if (bytes < 1_048_576) return {value: (bytes / 1_024).toFixed(0), unit: 'KB'};
- return {value: (bytes / 1_048_576).toFixed(1), unit: 'MB'};
+ if (bytes < 1_024) return { value: `${bytes}`, unit: 'B' };
+ if (bytes < 1_048_576) return { value: (bytes / 1_024).toFixed(0), unit: 'KB' };
+ return { value: (bytes / 1_048_576).toFixed(1), unit: 'MB' };
+};
+
+/**
+ * Formats a corpus source's file count as a per-language breakdown
+ * (`124 typescript, 15 svelte, 31 css`), largest language first and dropping
+ * zero-count languages. Falls back to the plain `N files` total when the report
+ * predates the per-language split (or lists no recognized language).
+ */
+export const format_corpus_source_files = (source: CorpusSource): string => {
+ const total = `${source.files.toLocaleString('en-US')} files`;
+ if (!source.by_language) return total;
+ const parts = Object.entries(source.by_language)
+ .filter((entry): entry is [string, number] => (entry[1] ?? 0) > 0)
+ .sort((a, b) => b[1] - a[1])
+ .map(([language, count]) => `${count.toLocaleString('en-US')} ${language}`);
+ return parts.length > 0 ? parts.join(', ') : total;
};
+// Corpus source repos (site-owned path → URL mapping)
+
+export interface CorpusRepo {
+ // public repo URL the entry links to
+ url: string;
+ // `org/name`, derived from the URL — the linkified display label
+ label: string;
+}
+
+/**
+ * Maps a corpus source's on-disk path to its public repo URL. The bench records
+ * only local paths (`../zzz/src`), so the site owns this mapping — the links then
+ * survive an `update-benchmarks` refresh without touching the copied JSON. Keyed by
+ * the repo's directory prefix, each ending in a slash so siblings like `../fuz_css/`
+ * and `../fuz_code/`, and `../svelte/` vs `../svelte.dev/`, stay distinct. A source
+ * matching no prefix — the derived `svelte_styles` CSS cache, which isn't a single
+ * repo — is dropped from the repos list rather than shown unlinked.
+ */
+const CORPUS_REPO_URL_BY_PREFIX: ReadonlyArray = [
+ ['../zzz/', 'https://github.com/fuzdev/zzz'],
+ ['../fuz_app/', 'https://github.com/fuzdev/fuz_app'],
+ ['../fuz_blog/', 'https://github.com/fuzdev/fuz_blog'],
+ ['../fuz_code/', 'https://github.com/fuzdev/fuz_code'],
+ ['../fuz_css/', 'https://github.com/fuzdev/fuz_css'],
+ ['../fuz_docs/', 'https://github.com/fuzdev/fuz_docs'],
+ ['../fuz_gitops/', 'https://github.com/fuzdev/fuz_gitops'],
+ ['../fuz_mastodon/', 'https://github.com/fuzdev/fuz_mastodon'],
+ ['../fuz_template/', 'https://github.com/fuzdev/fuz_template'],
+ ['../fuz_ui/', 'https://github.com/fuzdev/fuz_ui'],
+ ['../fuz_util/', 'https://github.com/fuzdev/fuz_util'],
+ ['../mdz/', 'https://github.com/fuzdev/mdz'],
+ ['../gro/', 'https://github.com/fuzdev/gro'],
+ ['../svelte-docinfo/', 'https://github.com/fuzdev/svelte-docinfo'],
+ ['../tsv.fuz.dev/', 'https://github.com/fuzdev/tsv.fuz.dev'],
+ ['../ryanatkn.com/', 'https://github.com/ryanatkn/ryanatkn.com'],
+ ['../webdevladder.net/', 'https://github.com/ryanatkn/webdevladder.net'],
+ ['../kit/', 'https://github.com/sveltejs/kit'],
+ ['../svelte.dev/', 'https://github.com/sveltejs/svelte.dev'],
+ ['../svelte/', 'https://github.com/sveltejs/svelte']
+];
+
+/** The repo URL for a corpus source path, or `undefined` when it maps to no repo. */
+const corpus_repo_url = (path: string): string | undefined =>
+ CORPUS_REPO_URL_BY_PREFIX.find(([prefix]) => path.startsWith(prefix))?.[1];
+
+/** `org/name` from a `https://github.com/org/name` URL — the linkified label. */
+const corpus_repo_label = (url: string): string => new URL(url).pathname.slice(1);
+
/**
- * Formats per-implementation corpus coverage as `processed/total`, or
- * `undefined` when either value is missing (older baselines without coverage).
+ * The distinct source repos behind a report's corpus, one entry per URL in
+ * first-seen order (the author's ecosystem leads, the upstream framework repos
+ * trail, matching the source order). Sources sharing a repo (svelte.dev's several
+ * packages) collapse to one entry; sources with no mapped repo (the `svelte_styles`
+ * CSS cache) are dropped. URLs come from the site-owned `CORPUS_REPO_URL_BY_PREFIX`,
+ * not the report, so the links survive a refresh.
*/
-export const format_coverage = (
- processed: number | null | undefined,
- total: number | null | undefined,
-): string | undefined => {
- if (processed == null || total == null) return undefined;
- return `${processed}/${total}`;
+export const derive_corpus_repos = (
+ sources: Array | undefined
+): Array => {
+ const by_url: Map = new Map();
+ for (const source of sources ?? []) {
+ // Prefer the report's git-detected repo; fall back to the legacy prefix
+ // map for reports predating `version` 8 (which lack `source.repo`).
+ const url = source.repo?.url ?? corpus_repo_url(source.path);
+ if (!url || by_url.has(url)) continue;
+ by_url.set(url, {
+ url,
+ label: source.repo?.slug ?? corpus_repo_label(url)
+ });
+ }
+ return [...by_url.values()];
};
/**
@@ -279,7 +873,7 @@ export const format_coverage = (
*/
export const format_gzip_size = (gzip_bytes: number | null | undefined): string | undefined => {
if (gzip_bytes == null) return undefined;
- const {value, unit} = format_bytes(gzip_bytes);
+ const { value, unit } = format_bytes(gzip_bytes);
return `${value} ${unit} gz`;
};
@@ -287,10 +881,92 @@ export const format_gzip_size = (gzip_bytes: number | null | undefined): string
export const format_speedup = (ratio: number): string =>
ratio >= 10 ? `${ratio.toFixed(1)}x` : `${ratio.toFixed(2)}x`;
+/**
+ * Loose ratio formatting for prose, which reads better with fewer digits than a
+ * table column (`1.7x`, `26x`) — always paired with a `~` in the copy. Renders
+ * `—` for a missing ratio rather than throwing mid-sentence; the benchmark tests
+ * assert every ratio the page quotes actually resolves.
+ */
+export const format_ratio_approx = (ratio: number | undefined): string =>
+ ratio === undefined ? '—' : ratio >= 10 ? `${Math.round(ratio)}x` : `${ratio.toFixed(1)}x`;
+
+/**
+ * An inclusive ratio range for prose (`3–9x`), FLOORED at both ends so a
+ * "3–9x less memory" claim never overstates either bound.
+ */
+export const format_ratio_range = (min: number, max: number): string =>
+ `${Math.floor(min)}–${Math.floor(max)}x`;
+
+/**
+ * How many times faster `faster` is than `slower` within one `operation/language`
+ * group, by mean time — the ratio the prose summaries quote.
+ *
+ * @returns the ratio, or `undefined` when either entry is absent from the report
+ */
+export const benchmark_speedup = (
+ baseline: BenchmarkBaseline,
+ group: string,
+ slower: string,
+ faster: string
+): number | undefined => {
+ const find = (name: string) => baseline.entries.find((e) => e.group === group && e.name === name);
+ // a coverage-only report carries null timings, so both sides must be real
+ const a = find(slower)?.mean_ns;
+ const b = find(faster)?.mean_ns;
+ if (a == null || !b) return undefined;
+ return a / b;
+};
+
+/**
+ * Signed speedup: entries at or above the anchor read as a plain multiple
+ * (`2.50x`), while slower entries show the reciprocal negated (`0.15x` → `-6.67x`)
+ * so "how many times slower" is directly legible instead of a fraction the reader
+ * has to invert. The minus is a convention for "times slower", not a literal
+ * negative rate. Used for the format/parse speed bars, where entries span widely
+ * on both sides of the anchor; the near-parity cross-runtime table and the
+ * bigger-is-worse size ratios stay on the plain fractional `format_speedup`.
+ */
+export const format_speedup_signed = (ratio: number): string => {
+ const magnitude = ratio >= 1 ? ratio : 1 / ratio;
+ const digits = magnitude >= 10 ? 1 : 2;
+ return `${ratio < 1 ? '-' : ''}${magnitude.toFixed(digits)}x`;
+};
+
/** Hyphenated tool names that should preserve their hyphens in display labels. */
const HYPHENATED_NAMES = ['acorn-typescript', 'oxc-parser'];
+/**
+ * Display labels for the raw benchmark entry names in the main (Node) tables,
+ * annotating each with the runtime **and** binding it runs under — the native
+ * builds load the N-API addon under Node, so they read `(node napi)` to
+ * distinguish them from the Deno FFI numbers the cross-runtime table surfaces
+ * (`tsv (deno ffi)`); the third-party wasm builds are marked `(wasm)`. Mirrors
+ * the parenthesized suffixes the binary-size section's labels already carry.
+ * tsv's own wasm entries keep their `tsv_wasm` package-name style, as in the size
+ * groups, so they aren't listed here. The already-parenthesized size labels
+ * aren't keys, so they fall through to the generic formatting below unchanged.
+ * The cross-runtime table neutralizes the `(node napi)` suffix per row (its
+ * columns span runtimes) via `format_cross_runtime_label`.
+ */
+const LABEL_OVERRIDES: Record = {
+ tsv: 'tsv (node napi)',
+ 'tsv-json': 'tsv json (node napi)',
+ // `no-locs` (not `no-locations`) — the full word eats too much column width.
+ 'tsv-json-no-locations': 'tsv json no-locs (node napi)',
+ // the one tsv_wasm entry listed here: the generic formatting below would
+ // break the `no-locs` hyphen its native sibling deliberately keeps
+ 'tsv_wasm-json-no-locations': 'tsv_wasm json no-locs',
+ 'tsv-internal': 'tsv internal (node napi)',
+ 'oxc-parser': 'oxc-parser (node napi)',
+ oxfmt: 'oxfmt (node napi)',
+ 'biome-wasm': 'biome (wasm)',
+ 'dprint-wasm': 'dprint (wasm)',
+ 'oxc-parser-wasm': 'oxc-parser (wasm)'
+};
+
export const format_label = (name: string): string => {
+ const override = LABEL_OVERRIDES[name];
+ if (override) return override;
for (const tool of HYPHENATED_NAMES) {
if (name.startsWith(tool)) {
return tool + name.slice(tool.length).replaceAll('-', ' ');
@@ -299,6 +975,16 @@ export const format_label = (name: string): string => {
return name.replaceAll('-', ' ');
};
+/**
+ * The label for a cross-runtime row. Each row spans node/deno/bun columns, and a
+ * native build's binding is runtime-specific (N-API on node & bun, C-FFI on
+ * deno), so the row can't pin one binding — neutralize `format_label`'s
+ * node-centric `(node napi)` suffix to `(native)`. The per-runtime binding is
+ * disclosed once in the table caption instead (see `BenchmarksCrossRuntime`).
+ */
+export const format_cross_runtime_label = (name: string): string =>
+ format_label(name).replace(' (node napi)', ' (native)');
+
/** Returns a CSS background color variable for a category. */
export const category_color = (category: ImplementationCategory): string => {
switch (category) {
@@ -314,16 +1000,24 @@ export const category_color = (category: ImplementationCategory): string => {
return 'var(--color_f_40)';
case 'biome':
return 'var(--color_a_40)';
+ case 'dprint':
+ return 'var(--color_b_40)';
case 'oxc':
return 'var(--color_i_40)';
}
};
-/** Returns a CSS color variable for a size ratio (inverted — bigger is worse). */
+/**
+ * Returns a CSS color variable for a size ratio (inverted — bigger is worse).
+ * Green is reserved for builds smaller than the baseline (ratio < 1, only reachable
+ * once a hover re-baselines onto a larger build); any build at or above the baseline
+ * reads yellow at the floor, ramping through orange to red as it grows.
+ */
export const size_ratio_color = (ratio: number): string => {
- if (ratio < 2) return 'var(--color_b_50)'; // green — similar
- if (ratio < 5) return 'var(--color_e_50)'; // yellow — notably larger
- return 'var(--color_c_50)'; // red — much larger
+ if (ratio < 1) return 'var(--color_b_50)'; // green — smaller than baseline
+ if (ratio < 3) return 'var(--color_e_50)'; // yellow — larger
+ if (ratio < 10) return 'var(--color_h_50)'; // orange — much larger
+ return 'var(--color_c_50)'; // red — enormous
};
/** Returns a CSS color variable for the speedup ratio. */
@@ -334,3 +1028,73 @@ export const speedup_color = (ratio: number): string => {
if (ratio < 5) return 'var(--color_b_50)'; // green — fast
return 'var(--color_j_50)'; // teal — exceptional
};
+
+/**
+ * Cross-runtime ratio cell background: a stable fuz_css red (`color_c`) / green
+ * (`color_b`) whose ALPHA varies with distance from parity. Fully transparent at ratio
+ * `1.0` (parity recedes), ramping to `0.3` alpha at ratio `0.8` and below (red) or `1.2`
+ * and above (green). The hue stays constant — only opacity moves — so it reads
+ * consistently in light and dark themes while the cell's text keeps the default color.
+ * Deliberately its own scale — NOT the shared `speedup_color` — because cross-runtime
+ * deltas cluster tightly near 1.0 and this must not bleed into the other displays.
+ */
+export const cross_runtime_ratio_background = (ratio: number): string => {
+ // 0 alpha at ratio 1.0, up to 0.3 at ratio ≤ 0.8 (red) or ≥ 1.2 (green)
+ const alpha = Math.min(1, Math.abs(ratio - 1) / 0.2) * 0.3;
+ const color = ratio < 1 ? 'var(--color_c_50)' : 'var(--color_b_50)';
+ return `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(1)}%, transparent)`;
+};
+
+// Interactive baseline (hover-to-rebaseline)
+
+/**
+ * Which way a group's ratio runs, so re-anchoring on hover stays consistent. A
+ * `speed` group (format/parse) reads its anchor as a reference speed — faster
+ * entries are positive multiples, slower ones negative (`format_speedup_signed` /
+ * `speedup_color`). A `size` group reads its anchor as a reference size — bigger
+ * builds are multiples ≥ 1 (`format_size_ratio` / `size_ratio_color`). The two
+ * ratios are reciprocals, which is exactly why one direction flag suffices.
+ */
+export type BaselineDirection = 'speed' | 'size';
+
+/** An entry's ratio against its group's anchor, in the group's direction. */
+export const compute_baseline_ratio = (
+ direction: BaselineDirection,
+ entry_raw: number,
+ anchor_raw: number
+): number => (direction === 'speed' ? anchor_raw / entry_raw : entry_raw / anchor_raw);
+
+/** Plain size ratio (`2.3x`) — its own formatter since sizes never take the signed treatment. */
+export const format_size_ratio = (ratio: number): string => `${ratio.toFixed(1)}x`;
+
+/** Formats a baseline ratio for display in the given direction. */
+export const format_baseline_ratio = (direction: BaselineDirection, ratio: number): string =>
+ direction === 'speed' ? format_speedup_signed(ratio) : format_size_ratio(ratio);
+
+/** Color for a baseline ratio in the given direction. */
+export const baseline_ratio_color = (direction: BaselineDirection, ratio: number): string =>
+ direction === 'speed' ? speedup_color(ratio) : size_ratio_color(ratio);
+
+/**
+ * A normalized row for `BenchmarksBaselineGroup` — the shared hover-to-rebaseline
+ * column behind the format, parse, and binary-size groups. `raw` is the number the
+ * ratio derives from (sweep mean ns for speed, bytes for size); `value` is the
+ * row's displayed measurement (the whole-sweep mean — total time over the group's
+ * iterated corpus — for speed, bytes for size). Flattening the group-specific
+ * display entries to this one shape lets a single component own the anchor state
+ * for all three sections.
+ */
+export interface BaselineRow {
+ // stable identity for `#each` and anchor matching (the entry name / size label)
+ key: string;
+ // raw name/label; `BenchmarksBar` formats it for display
+ label: string;
+ category: ImplementationCategory;
+ bar_fraction: number;
+ value: FormattedUnit;
+ // the number the ratio derives from — mean ns (speed) or bytes (size)
+ raw: number;
+ annotation: string | undefined;
+ // grayed-out, inert placeholder — never an anchor, no hover highlight
+ disabled: boolean;
+}
diff --git a/src/routes/docs/benchmarks/benchmarks.json b/src/routes/docs/benchmarks/benchmarks.json
index bf56417..171cc7a 100644
--- a/src/routes/docs/benchmarks/benchmarks.json
+++ b/src/routes/docs/benchmarks/benchmarks.json
@@ -1,685 +1,1189 @@
{
- "version": 4,
- "timestamp": "2026-06-12T09:22:00.588Z",
- "git_commit": "98918f9",
+ "version": 7,
+ "runtime": "node",
+ "corpus_kind": "perf",
+ "timestamp": "2026-07-22T09:18:34.995Z",
+ "git_commit": "d063479a",
+ "machine": {
+ "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics",
+ "os": "linux",
+ "arch": "x86_64",
+ "runtime_version": "24.14.1"
+ },
"corpus": {
- "svelte": 1240,
- "typescript": 4025,
- "css": 193
+ "svelte": 767,
+ "typescript": 2448,
+ "css": 50
},
+ "corpus_sources": [
+ {
+ "path": "../zzz/src",
+ "files": 326,
+ "by_language": {
+ "svelte": 173,
+ "typescript": 152,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/zzz",
+ "slug": "fuzdev/zzz",
+ "commit": "838d499c7cc746ebe1f5da8470e7679c8895aee0",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_app/src",
+ "files": 665,
+ "by_language": {
+ "svelte": 33,
+ "typescript": 631,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_app",
+ "slug": "fuzdev/fuz_app",
+ "commit": "4f012eae9ff11e5a1fb7a4faacf640ea9b63607a",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_blog/src",
+ "files": 37,
+ "by_language": {
+ "svelte": 20,
+ "typescript": 16,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_blog",
+ "slug": "fuzdev/fuz_blog",
+ "commit": "ecc86884c6e1ebe31d1f492e5df631cb8e586273",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_code/src",
+ "files": 74,
+ "by_language": {
+ "svelte": 20,
+ "typescript": 50,
+ "css": 4
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_code",
+ "slug": "fuzdev/fuz_code",
+ "commit": "fea306ae68aa9f40155e108242d0b7168b49c41f",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_css/src",
+ "files": 146,
+ "by_language": {
+ "svelte": 39,
+ "typescript": 105,
+ "css": 2
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_css",
+ "slug": "fuzdev/fuz_css",
+ "commit": "9263f076e8b23c64564a2236b886279d1b42d626",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_docs/src",
+ "files": 65,
+ "by_language": {
+ "svelte": 45,
+ "typescript": 19,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_docs",
+ "slug": "fuzdev/fuz_docs",
+ "commit": "e27d7b7acbff67a442354cbd318df5d2c1f78552",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_gitops/src",
+ "files": 99,
+ "by_language": {
+ "svelte": 28,
+ "typescript": 70,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_gitops",
+ "slug": "fuzdev/fuz_gitops",
+ "commit": "6d1a90269e16559259d7660ba1b7b119da5967ac",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_mastodon/src",
+ "files": 25,
+ "by_language": {
+ "svelte": 15,
+ "typescript": 9,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_mastodon",
+ "slug": "fuzdev/fuz_mastodon",
+ "commit": "6179d626e50dd1a53540a074fc23c354c2985321",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_template/src",
+ "files": 18,
+ "by_language": {
+ "svelte": 10,
+ "typescript": 7,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_template",
+ "slug": "fuzdev/fuz_template",
+ "commit": "664498efdd7952010a314dd5e93f5ca0b269881e",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_ui/src",
+ "files": 216,
+ "by_language": {
+ "svelte": 121,
+ "typescript": 95,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_ui",
+ "slug": "fuzdev/fuz_ui",
+ "commit": "067e491c40b360e150e24b2c591942f505d0046f",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../fuz_util/src",
+ "files": 145,
+ "by_language": {
+ "svelte": 8,
+ "typescript": 136,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/fuz_util",
+ "slug": "fuzdev/fuz_util",
+ "commit": "f7887bc67ba8398d4e1f858b319f2d83e7455d3e",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../mdz/src",
+ "files": 71,
+ "by_language": {
+ "svelte": 22,
+ "typescript": 48,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/mdz",
+ "slug": "fuzdev/mdz",
+ "commit": "860b62134eb9ecafd7a52240414e87bb481448f4",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../gro/src",
+ "files": 156,
+ "by_language": {
+ "svelte": 10,
+ "typescript": 146,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/gro",
+ "slug": "fuzdev/gro",
+ "commit": "32af831867a0e73f98b4a6f2d35f435e21fea7db",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../svelte-docinfo/src",
+ "files": 99,
+ "by_language": {
+ "svelte": 22,
+ "typescript": 76,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/svelte-docinfo",
+ "slug": "fuzdev/svelte-docinfo",
+ "commit": "39e10bdff2ca77a82a8c017bbff004cc2f809aa0",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../tsv.fuz.dev/src",
+ "files": 34,
+ "by_language": {
+ "svelte": 17,
+ "typescript": 16,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/fuzdev/tsv.fuz.dev",
+ "slug": "fuzdev/tsv.fuz.dev",
+ "commit": "a370a32a1dbe33c55026708019d0e403a91b13c3",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../ryanatkn.com/src",
+ "files": 52,
+ "by_language": {
+ "svelte": 31,
+ "typescript": 20,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/ryanatkn/ryanatkn.com",
+ "slug": "ryanatkn/ryanatkn.com",
+ "commit": "590ca7be62869763c0aa5fe7531ab9b4b00b275e",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "../webdevladder.net/src",
+ "files": 39,
+ "by_language": {
+ "svelte": 24,
+ "typescript": 14,
+ "css": 1
+ },
+ "repo": {
+ "url": "https://github.com/ryanatkn/webdevladder.net",
+ "slug": "ryanatkn/webdevladder.net",
+ "commit": "9262030ba9469383ea83f88978f18779d0712566",
+ "subpath": "src"
+ }
+ },
+ {
+ "path": "benches/js/.cache/svelte_styles",
+ "files": 18,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 0,
+ "css": 18
+ }
+ },
+ {
+ "path": "../kit/packages/kit/src",
+ "files": 297,
+ "by_language": {
+ "svelte": 26,
+ "typescript": 271,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/kit",
+ "slug": "sveltejs/kit",
+ "commit": "da5b08ea7f5182f505b5d1cb9b347effbcb35231",
+ "subpath": "packages/kit/src"
+ }
+ },
+ {
+ "path": "../svelte/packages/svelte/src",
+ "files": 415,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 415,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/svelte",
+ "slug": "sveltejs/svelte",
+ "commit": "b4d1583ae20f3869a88a731d9a265c546c099f66",
+ "subpath": "packages/svelte/src"
+ }
+ },
+ {
+ "path": "../svelte.dev/apps/svelte.dev/src",
+ "files": 145,
+ "by_language": {
+ "svelte": 60,
+ "typescript": 85,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/svelte.dev",
+ "slug": "sveltejs/svelte.dev",
+ "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c",
+ "subpath": "apps/svelte.dev/src"
+ }
+ },
+ {
+ "path": "../svelte.dev/packages/repl/src",
+ "files": 53,
+ "by_language": {
+ "svelte": 17,
+ "typescript": 34,
+ "css": 2
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/svelte.dev",
+ "slug": "sveltejs/svelte.dev",
+ "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c",
+ "subpath": "packages/repl/src"
+ }
+ },
+ {
+ "path": "../svelte.dev/packages/site-kit/src",
+ "files": 70,
+ "by_language": {
+ "svelte": 26,
+ "typescript": 33,
+ "css": 11
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/svelte.dev",
+ "slug": "sveltejs/svelte.dev",
+ "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c",
+ "subpath": "packages/site-kit/src"
+ }
+ }
+ ],
"versions": {
- "tsv": "0.1.0",
- "svelte": "5.56.1",
+ "tsv": "0.2.0",
+ "svelte": "5.56.4",
"acorn": "8.16.0",
- "acorn_ts": "1.0.10",
- "prettier": "3.8.3",
- "prettier_svelte": "3.5.2",
- "oxc_parser": "0.134.0",
- "oxfmt": "0.53.0",
- "biome": "2.4.16"
+ "acorn_ts": "1.0.11",
+ "prettier": "3.9.5",
+ "prettier_svelte": "4.1.1",
+ "oxc_parser": "0.140.0",
+ "oxfmt": "0.60.0",
+ "biome": "2.5.4",
+ "dprint": "0.96.1"
},
"binary_sizes": [
{
- "label": "tsv (native)",
- "bytes": 3861760,
+ "label": "tsv (ffi)",
+ "bytes": 3346544,
+ "kind": "native",
+ "gzip_bytes": 1442507
+ },
+ {
+ "label": "tsv format (ffi)",
+ "bytes": 3095968,
"kind": "native",
- "gzip_bytes": 1622771
+ "gzip_bytes": 1339168
+ },
+ {
+ "label": "tsv parse (ffi)",
+ "bytes": 1606912,
+ "kind": "native",
+ "gzip_bytes": 703248
+ },
+ {
+ "label": "tsv (napi)",
+ "bytes": 3479392,
+ "kind": "native",
+ "gzip_bytes": 1486322
},
{
"label": "tsv_format_wasm",
- "bytes": 2196292,
+ "bytes": 2216330,
"kind": "wasm",
- "gzip_bytes": 715422
+ "gzip_bytes": 787915
},
{
"label": "tsv_parse_wasm",
- "bytes": 1699625,
+ "bytes": 1052601,
"kind": "wasm",
- "gzip_bytes": 526578
+ "gzip_bytes": 388661
},
{
"label": "tsv_wasm",
- "bytes": 2888918,
+ "bytes": 2444292,
"kind": "wasm",
- "gzip_bytes": 911323
+ "gzip_bytes": 867803
},
{
"label": "biome (wasm)",
- "bytes": 34430368,
+ "bytes": 38584187,
"kind": "wasm",
- "gzip_bytes": 8187702
+ "gzip_bytes": 9281487
},
{
- "label": "oxc-parser (native)",
- "bytes": 2658008,
+ "label": "dprint (wasm)",
+ "bytes": 4174185,
+ "kind": "wasm",
+ "gzip_bytes": 1155127
+ },
+ {
+ "label": "oxc-parser (napi)",
+ "bytes": 2354904,
"kind": "native",
- "gzip_bytes": 1032774
+ "gzip_bytes": 954754
},
{
- "label": "oxfmt (native)",
- "bytes": 8016464,
+ "label": "oxfmt (napi)",
+ "bytes": 8844056,
"kind": "native",
- "gzip_bytes": 3224684
+ "gzip_bytes": 3575335
},
{
"label": "oxc-parser (wasm)",
- "bytes": 1866798,
+ "bytes": 1531455,
"kind": "wasm",
- "gzip_bytes": 518682
+ "gzip_bytes": 495227
}
],
"entries": [
{
"name": "svelte/compiler",
"group": "parse/svelte",
- "mean_ns": 353647929.8666667,
- "p50_ns": 351794280,
- "p75_ns": 355710613.5,
- "p90_ns": 358757843.8,
- "p95_ns": 359832344.1,
- "p99_ns": 361455435.21999997,
- "min_ns": 349590498,
- "max_ns": 361861208,
- "std_dev_ns": 3732468.9277879535,
- "cv": 0.010554194193064213,
- "ops_per_second": 2.8276710127414653,
- "sample_size": 15,
- "files_processed": 1193,
- "files_total": 1240,
- "files_iterated": 1192
+ "mean_ns": 450117289.7,
+ "p50_ns": 449990982,
+ "p75_ns": 454086904,
+ "p90_ns": 460782361.3,
+ "p95_ns": 464255661.1,
+ "p99_ns": 467309973.02,
+ "min_ns": 446842297,
+ "max_ns": 468073551,
+ "std_dev_ns": 3145025.5647039055,
+ "cv": 0.006987124548803808,
+ "ops_per_second": 2.2216431647548864,
+ "sample_size": 10,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv-json",
"group": "parse/svelte",
- "mean_ns": 485025992.4444444,
- "p50_ns": 485086813,
- "p75_ns": 487644066,
- "p90_ns": 491656222,
- "p95_ns": 494258617.5,
- "p99_ns": 496340533.9,
- "min_ns": 481783564,
- "max_ns": 496861013,
- "std_dev_ns": 1892163.1540198317,
- "cv": 0.0039011582543930627,
- "ops_per_second": 2.061745175676418,
- "sample_size": 9,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1192
+ "mean_ns": 215054374.7368421,
+ "p50_ns": 215108675,
+ "p75_ns": 216642780.75,
+ "p90_ns": 221466791.9,
+ "p95_ns": 222243263.35,
+ "p99_ns": 222392394.47,
+ "min_ns": 214038777,
+ "max_ns": 222411234,
+ "std_dev_ns": 885187.5854249354,
+ "cv": 0.00411611057207333,
+ "ops_per_second": 4.6499867822902035,
+ "sample_size": 19,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json-no-locations",
+ "group": "parse/svelte",
+ "mean_ns": 136940898.48387095,
+ "p50_ns": 137100604,
+ "p75_ns": 138022550,
+ "p90_ns": 141696235,
+ "p95_ns": 142315365,
+ "p99_ns": 142784485,
+ "min_ns": 134880981,
+ "max_ns": 142856476,
+ "std_dev_ns": 1406524.9125825886,
+ "cv": 0.010271036105026363,
+ "ops_per_second": 7.302420321988621,
+ "sample_size": 31,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv_wasm-json",
"group": "parse/svelte",
- "mean_ns": 515988848.71428573,
- "p50_ns": 516072939.5,
- "p75_ns": 517319991,
- "p90_ns": 519264292.5,
- "p95_ns": 520772636.25,
- "p99_ns": 521979311.25,
- "min_ns": 515560589,
- "max_ns": 522280980,
- "std_dev_ns": 253147.57598596797,
- "cv": 0.0004906066800023837,
- "ops_per_second": 1.9380263788485899,
- "sample_size": 7,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1192
+ "mean_ns": 239961216.70588234,
+ "p50_ns": 239843331,
+ "p75_ns": 242042203,
+ "p90_ns": 244289741,
+ "p95_ns": 245784287,
+ "p99_ns": 245964537.4,
+ "min_ns": 238927466,
+ "max_ns": 246009600,
+ "std_dev_ns": 1100810.371221286,
+ "cv": 0.0045874511987099,
+ "ops_per_second": 4.16734009656939,
+ "sample_size": 17,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json-no-locations",
+ "group": "parse/svelte",
+ "mean_ns": 155138759.1153846,
+ "p50_ns": 155155418,
+ "p75_ns": 156264183,
+ "p90_ns": 159277904.4,
+ "p95_ns": 159735132.8,
+ "p99_ns": 160006222.28,
+ "min_ns": 153705591,
+ "max_ns": 160051685,
+ "std_dev_ns": 786708.3806164177,
+ "cv": 0.005070998279877323,
+ "ops_per_second": 6.445842455503006,
+ "sample_size": 26,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv-internal",
"group": "parse/svelte",
- "mean_ns": 37041735.949579835,
- "p50_ns": 36817941,
- "p75_ns": 38031003.5,
- "p90_ns": 39745595,
- "p95_ns": 49374707.5,
- "p99_ns": 70563356.69999997,
- "min_ns": 35490755,
- "max_ns": 73560234,
- "std_dev_ns": 1041838.039960544,
- "cv": 0.02812605870790355,
- "ops_per_second": 26.996574927297463,
- "sample_size": 119,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1192
+ "mean_ns": 20330465.730088495,
+ "p50_ns": 20197596,
+ "p75_ns": 20708728,
+ "p90_ns": 20865536.5,
+ "p95_ns": 20965844.5,
+ "p99_ns": 21051817.8,
+ "min_ns": 19995227,
+ "max_ns": 21250059,
+ "std_dev_ns": 287750.5964940437,
+ "cv": 0.014153664766674835,
+ "ops_per_second": 49.18726473245663,
+ "sample_size": 226,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv_wasm-internal",
"group": "parse/svelte",
- "mean_ns": 45254932.2745098,
- "p50_ns": 45241227,
- "p75_ns": 45348407.5,
- "p90_ns": 45449227,
- "p95_ns": 45624719,
- "p99_ns": 45694463.7,
- "min_ns": 45087137,
- "max_ns": 45720491,
- "std_dev_ns": 94029.59204496059,
- "cv": 0.0020777755554817943,
- "ops_per_second": 22.097038924600444,
- "sample_size": 102,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1192
+ "mean_ns": 27634829.2109375,
+ "p50_ns": 27607981.5,
+ "p75_ns": 28245700,
+ "p90_ns": 28455070.2,
+ "p95_ns": 28526437.65,
+ "p99_ns": 28606624.3,
+ "min_ns": 27483201,
+ "max_ns": 28758034,
+ "std_dev_ns": 181690.4859527825,
+ "cv": 0.006574691834204346,
+ "ops_per_second": 36.1862196566141,
+ "sample_size": 128,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "prettier",
"group": "format/svelte",
- "mean_ns": 3156053205.2,
- "p50_ns": 3153333425,
- "p75_ns": 3161183958,
- "p90_ns": 3191384359.8,
- "p95_ns": 3201451160.4,
- "p99_ns": 3209504600.88,
- "min_ns": 3116677318,
- "max_ns": 3211517961,
- "std_dev_ns": 35365115.51584766,
- "cv": 0.011205487745763958,
- "ops_per_second": 0.316851439117811,
- "sample_size": 5,
- "files_processed": 1201,
- "files_total": 1240,
- "files_iterated": 1194
+ "mean_ns": 4391740370.714286,
+ "p50_ns": 4380682349,
+ "p75_ns": 4412690367,
+ "p90_ns": 4435167488,
+ "p95_ns": 4441739174,
+ "p99_ns": 4446996522.8,
+ "min_ns": 4355169372,
+ "max_ns": 4448310860,
+ "std_dev_ns": 35134200.08841751,
+ "cv": 0.008000063100884804,
+ "ops_per_second": 0.22770016339498617,
+ "sample_size": 7,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv",
"group": "format/svelte",
- "mean_ns": 253589550.66666666,
- "p50_ns": 250899714,
- "p75_ns": 261502311.75,
- "p90_ns": 270307677.1,
- "p95_ns": 291692469.25,
- "p99_ns": 299630277.84999996,
- "min_ns": 247143299,
- "max_ns": 301614730,
- "std_dev_ns": 6469364.524097994,
- "cv": 0.02551116363860637,
- "ops_per_second": 3.9433801486342004,
- "sample_size": 18,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1194
+ "mean_ns": 68011448.20289855,
+ "p50_ns": 67663689.5,
+ "p75_ns": 69131073.5,
+ "p90_ns": 69616594.5,
+ "p95_ns": 69748928.95,
+ "p99_ns": 71027832.83999999,
+ "min_ns": 67189585,
+ "max_ns": 73851102,
+ "std_dev_ns": 843409.4539816556,
+ "cv": 0.012400992425062209,
+ "ops_per_second": 14.703406947264233,
+ "sample_size": 69,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "tsv_wasm",
"group": "format/svelte",
- "mean_ns": 348305291.1666667,
- "p50_ns": 348407825,
- "p75_ns": 349005457.5,
- "p90_ns": 351966927.4,
- "p95_ns": 353359477.1,
- "p99_ns": 355469057.02,
- "min_ns": 347712508,
- "max_ns": 355996452,
- "std_dev_ns": 403574.7899962474,
- "cv": 0.0011586811921359351,
- "ops_per_second": 2.8710445272032703,
- "sample_size": 12,
- "files_processed": 1202,
- "files_total": 1240,
- "files_iterated": 1194
+ "mean_ns": 97308671.10416667,
+ "p50_ns": 96982593,
+ "p75_ns": 98709308.25,
+ "p90_ns": 99791989.3,
+ "p95_ns": 100754954.55,
+ "p99_ns": 100989705.23,
+ "min_ns": 96112674,
+ "max_ns": 101040464,
+ "std_dev_ns": 1164641.1530361492,
+ "cv": 0.011968523871725962,
+ "ops_per_second": 10.276576472095927,
+ "sample_size": 48,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "oxfmt",
"group": "format/svelte",
- "mean_ns": 3363123440.8,
- "p50_ns": 3355465187,
- "p75_ns": 3380464215,
- "p90_ns": 3387858512.4,
- "p95_ns": 3390323278.2,
- "p99_ns": 3392295090.84,
- "min_ns": 3336319057,
- "max_ns": 3392788044,
- "std_dev_ns": 22995300.045456335,
- "cv": 0.006837483205786329,
- "ops_per_second": 0.2973426392467253,
- "sample_size": 5,
- "files_processed": 1201,
- "files_total": 1240,
- "files_iterated": 1194
+ "mean_ns": 4328676301.428572,
+ "p50_ns": 4312345072,
+ "p75_ns": 4375547937,
+ "p90_ns": 4393736093.4,
+ "p95_ns": 4399416073.2,
+ "p99_ns": 4403960057.04,
+ "min_ns": 4256952931,
+ "max_ns": 4405096053,
+ "std_dev_ns": 56768693.307661824,
+ "cv": 0.013114561901735811,
+ "ops_per_second": 0.23101750520591594,
+ "sample_size": 7,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "biome-wasm",
"group": "format/svelte",
- "mean_ns": 651628305.75,
- "p50_ns": 651348032.5,
- "p75_ns": 652776746.25,
- "p90_ns": 655602311.7,
- "p95_ns": 657068072.85,
- "p99_ns": 658240681.77,
- "min_ns": 646287594,
- "max_ns": 658533834,
- "std_dev_ns": 3695077.8107031505,
- "cv": 0.005670529929558927,
- "ops_per_second": 1.5346171907757093,
- "sample_size": 8,
- "files_processed": 1238,
- "files_total": 1240,
- "files_iterated": 1194
+ "mean_ns": 908653574.3333334,
+ "p50_ns": 907743476,
+ "p75_ns": 917705754.75,
+ "p90_ns": 923123625,
+ "p95_ns": 925151147.5,
+ "p99_ns": 926773165.5,
+ "min_ns": 892632974,
+ "max_ns": 927178670,
+ "std_dev_ns": 13432537.999488356,
+ "cv": 0.014782903384651982,
+ "ops_per_second": 1.1005294297485004,
+ "sample_size": 6,
+ "files_processed": 767,
+ "files_total": 767,
+ "files_iterated": 767,
+ "runtime": "node"
},
{
"name": "acorn-typescript",
"group": "parse/typescript",
- "mean_ns": 2213405912.6,
- "p50_ns": 2212095774,
- "p75_ns": 2213405114,
- "p90_ns": 2218802607.8,
- "p95_ns": 2220601772.4,
- "p99_ns": 2222041104.08,
- "min_ns": 2209487030,
- "max_ns": 2222400937,
- "std_dev_ns": 5295312.414249306,
- "cv": 0.0023923819775240017,
- "ops_per_second": 0.4517924138123132,
+ "mean_ns": 3128887486.2,
+ "p50_ns": 3128799385,
+ "p75_ns": 3132110168,
+ "p90_ns": 3132563774,
+ "p95_ns": 3132714976,
+ "p99_ns": 3132835937.6,
+ "min_ns": 3125296462,
+ "max_ns": 3132866178,
+ "std_dev_ns": 3589049.854497316,
+ "cv": 0.0011470690046627974,
+ "ops_per_second": 0.319602416005853,
"sample_size": 5,
- "files_processed": 3693,
- "files_total": 4025,
- "files_iterated": 3603
+ "files_processed": 2445,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "tsv-json",
"group": "parse/typescript",
- "mean_ns": 2049624429.8,
- "p50_ns": 2046277896,
- "p75_ns": 2052475955,
- "p90_ns": 2062325034.8,
- "p95_ns": 2065608061.4,
- "p99_ns": 2068234482.68,
- "min_ns": 2036381168,
- "max_ns": 2068891088,
- "std_dev_ns": 12211486.98364508,
- "cv": 0.005957914438420635,
- "ops_per_second": 0.4878942627052796,
+ "mean_ns": 2036068900,
+ "p50_ns": 2036373272,
+ "p75_ns": 2037932744,
+ "p90_ns": 2042044840.4,
+ "p95_ns": 2043415539.2,
+ "p99_ns": 2044512098.24,
+ "min_ns": 2034690477,
+ "max_ns": 2044786238,
+ "std_dev_ns": 1424825.1027755418,
+ "cv": 0.0006997921842308685,
+ "ops_per_second": 0.49114251487265487,
+ "sample_size": 4,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json-no-locations",
+ "group": "parse/typescript",
+ "mean_ns": 1006124754.2,
+ "p50_ns": 1005276990,
+ "p75_ns": 1007632481,
+ "p90_ns": 1007854793,
+ "p95_ns": 1007928897,
+ "p99_ns": 1007988180.2,
+ "min_ns": 1004502634,
+ "max_ns": 1008003001,
+ "std_dev_ns": 1580367.7301149566,
+ "cv": 0.0015707472890591529,
+ "ops_per_second": 0.9939125300571001,
"sample_size": 5,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3603
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "tsv_wasm-json",
"group": "parse/typescript",
- "mean_ns": 2223665077.4,
- "p50_ns": 2223809451,
- "p75_ns": 2224511693,
- "p90_ns": 2224643608.4,
- "p95_ns": 2224687580.2,
- "p99_ns": 2224722757.64,
- "min_ns": 2222334633,
- "max_ns": 2224731552,
- "std_dev_ns": 1021470.6667904371,
- "cv": 0.0004593635422762416,
- "ops_per_second": 0.44970801141026184,
+ "mean_ns": 2160965352.2,
+ "p50_ns": 2161367082,
+ "p75_ns": 2164544152,
+ "p90_ns": 2169681129.4,
+ "p95_ns": 2171393455.2,
+ "p99_ns": 2172763315.84,
+ "min_ns": 2150546632,
+ "max_ns": 2173105781,
+ "std_dev_ns": 8680789.960942276,
+ "cv": 0.004017088914500495,
+ "ops_per_second": 0.46275614691458916,
"sample_size": 5,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3603
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json-no-locations",
+ "group": "parse/typescript",
+ "mean_ns": 1091721513.6666667,
+ "p50_ns": 1091788165,
+ "p75_ns": 1093452294,
+ "p90_ns": 1093973845.8,
+ "p95_ns": 1094147696.4,
+ "p99_ns": 1094286776.88,
+ "min_ns": 1091656195,
+ "max_ns": 1094321547,
+ "std_dev_ns": 65995.0924337055,
+ "cv": 0.0000604504826620607,
+ "ops_per_second": 0.9159845138907174,
+ "sample_size": 3,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "tsv-internal",
"group": "parse/typescript",
- "mean_ns": 236250428.1875,
- "p50_ns": 235985787,
- "p75_ns": 239292380,
- "p90_ns": 262673433,
- "p95_ns": 263116119,
- "p99_ns": 263948169.4,
- "min_ns": 234967322,
- "max_ns": 264156182,
- "std_dev_ns": 1336552.468610353,
- "cv": 0.005657354692917632,
- "ops_per_second": 4.232796561140413,
- "sample_size": 16,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3603
+ "mean_ns": 140078986.69444445,
+ "p50_ns": 139384058,
+ "p75_ns": 140847581.25,
+ "p90_ns": 142696759.5,
+ "p95_ns": 142841338,
+ "p99_ns": 142886196.25,
+ "min_ns": 138381808,
+ "max_ns": 142906624,
+ "std_dev_ns": 1517145.3312055282,
+ "cv": 0.01083064181863973,
+ "ops_per_second": 7.138829481835908,
+ "sample_size": 36,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "tsv_wasm-internal",
"group": "parse/typescript",
- "mean_ns": 292595892.125,
- "p50_ns": 292647736.5,
- "p75_ns": 292773388.75,
- "p90_ns": 293666035.6,
- "p95_ns": 294709175.9,
- "p99_ns": 294963799.18,
- "min_ns": 292266142,
- "max_ns": 295027455,
- "std_dev_ns": 262236.36256371846,
- "cv": 0.0008962407525929597,
- "ops_per_second": 3.4176829781765687,
- "sample_size": 16,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3603
+ "mean_ns": 178725141.85714287,
+ "p50_ns": 178797972,
+ "p75_ns": 180230857.25,
+ "p90_ns": 182445355.5,
+ "p95_ns": 182612111.35,
+ "p99_ns": 182940545.16,
+ "min_ns": 178043485,
+ "max_ns": 183029940,
+ "std_dev_ns": 499324.82544725196,
+ "cv": 0.002793814122952932,
+ "ops_per_second": 5.5951836972064735,
+ "sample_size": 21,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "oxc-parser",
"group": "parse/typescript",
- "mean_ns": 864942821.5,
- "p50_ns": 863969686.5,
- "p75_ns": 878826551,
- "p90_ns": 884913960,
- "p95_ns": 887896262.5,
- "p99_ns": 890282104.5,
- "min_ns": 845083325,
- "max_ns": 890878565,
- "std_dev_ns": 20069394.017162047,
- "cv": 0.023203145362091483,
- "ops_per_second": 1.1561457880716106,
- "sample_size": 6,
- "files_processed": 3833,
- "files_total": 4025,
- "files_iterated": 3603
+ "mean_ns": 1296597720,
+ "p50_ns": 1297203582,
+ "p75_ns": 1297308037,
+ "p90_ns": 1300549918.6,
+ "p95_ns": 1301630545.8,
+ "p99_ns": 1302495047.56,
+ "min_ns": 1292854323,
+ "max_ns": 1302711173,
+ "std_dev_ns": 4057316.729119628,
+ "cv": 0.0031292024245728493,
+ "ops_per_second": 0.7712492352678207,
+ "sample_size": 5,
+ "files_processed": 2446,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "oxc-parser-wasm",
"group": "parse/typescript",
- "mean_ns": 976785685.3333334,
- "p50_ns": 975594108.5,
- "p75_ns": 979113434.75,
- "p90_ns": 982226200,
- "p95_ns": 983362576,
- "p99_ns": 984271676.8,
- "min_ns": 971966753,
- "max_ns": 984498952,
- "std_dev_ns": 4714293.024074751,
- "cv": 0.004826333037902754,
- "ops_per_second": 1.023766026688592,
- "sample_size": 6,
- "files_processed": 4025,
- "files_total": 4025,
- "files_iterated": 3603
+ "mean_ns": 1342497528.2,
+ "p50_ns": 1340425353,
+ "p75_ns": 1346905511,
+ "p90_ns": 1350118596.2,
+ "p95_ns": 1351189624.6,
+ "p99_ns": 1352046447.32,
+ "min_ns": 1335012499,
+ "max_ns": 1352260653,
+ "std_dev_ns": 7006364.841142459,
+ "cv": 0.005218903345421041,
+ "ops_per_second": 0.7448803286370177,
+ "sample_size": 5,
+ "files_processed": 2446,
+ "files_total": 2448,
+ "files_iterated": 2445,
+ "runtime": "node"
},
{
"name": "prettier",
"group": "format/typescript",
- "mean_ns": 8773944013.666666,
- "p50_ns": 8784008051,
- "p75_ns": 8807150369,
- "p90_ns": 8902301987.4,
- "p95_ns": 8961594658.2,
- "p99_ns": 9009028794.84,
- "min_ns": 8712430383,
- "max_ns": 9020887329,
- "std_dev_ns": 36684367.115987,
- "cv": 0.004181057806939032,
- "ops_per_second": 0.1139738296075696,
+ "mean_ns": 13428895514.5,
+ "p50_ns": 13434472454,
+ "p75_ns": 13448460090.5,
+ "p90_ns": 13483414228.2,
+ "p95_ns": 13506700293.6,
+ "p99_ns": 13525329145.92,
+ "min_ns": 13399688608,
+ "max_ns": 13529986359,
+ "std_dev_ns": 19154668.344905008,
+ "cv": 0.001426377048225786,
+ "ops_per_second": 0.07446628793263295,
"sample_size": 6,
- "files_processed": 3855,
- "files_total": 4025,
- "files_iterated": 3701
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
},
{
"name": "tsv",
"group": "format/typescript",
- "mean_ns": 854559033.5,
- "p50_ns": 856988296.5,
- "p75_ns": 860171191.5,
- "p90_ns": 861313209,
- "p95_ns": 861398115.5,
- "p99_ns": 861466040.7,
- "min_ns": 838541329,
- "max_ns": 861483022,
- "std_dev_ns": 8547810.99796243,
- "cv": 0.01000259860685497,
- "ops_per_second": 1.1701941712608437,
- "sample_size": 6,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3701
+ "mean_ns": 507668043.5,
+ "p50_ns": 507537106.5,
+ "p75_ns": 510123808.25,
+ "p90_ns": 516586304,
+ "p95_ns": 519310964,
+ "p99_ns": 521490692,
+ "min_ns": 506339768,
+ "max_ns": 522035624,
+ "std_dev_ns": 1395815.5141539504,
+ "cv": 0.00274946499395712,
+ "ops_per_second": 1.9697911121325091,
+ "sample_size": 8,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
},
{
"name": "tsv_wasm",
"group": "format/typescript",
- "mean_ns": 1145280037.8,
- "p50_ns": 1144938011,
- "p75_ns": 1145815746,
- "p90_ns": 1146670300.2,
- "p95_ns": 1146955151.6,
- "p99_ns": 1147183032.72,
- "min_ns": 1144152898,
- "max_ns": 1147240003,
- "std_dev_ns": 1281560.5264546424,
- "cv": 0.0011189931581418527,
- "ops_per_second": 0.8731488954622204,
- "sample_size": 5,
- "files_processed": 3743,
- "files_total": 4025,
- "files_iterated": 3701
+ "mean_ns": 694161288.1428572,
+ "p50_ns": 694435813.5,
+ "p75_ns": 694714348.5,
+ "p90_ns": 701147903.1,
+ "p95_ns": 708334588.55,
+ "p99_ns": 714083936.91,
+ "min_ns": 692827892,
+ "max_ns": 715521274,
+ "std_dev_ns": 706691.9638473869,
+ "cv": 0.0010180515334383656,
+ "ops_per_second": 1.4405873924133346,
+ "sample_size": 7,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
},
{
"name": "oxfmt",
"group": "format/typescript",
- "mean_ns": 802078434.8571428,
- "p50_ns": 804423326,
- "p75_ns": 806569662,
- "p90_ns": 809150059.2,
- "p95_ns": 809881109.1,
- "p99_ns": 810465949.02,
- "min_ns": 792012009,
- "max_ns": 810612159,
- "std_dev_ns": 7054022.950975906,
- "cv": 0.008794679727590843,
- "ops_per_second": 1.246760860960074,
- "sample_size": 7,
- "files_processed": 3836,
- "files_total": 4025,
- "files_iterated": 3701
+ "mean_ns": 842581054.8,
+ "p50_ns": 841016016,
+ "p75_ns": 842934023,
+ "p90_ns": 845209529,
+ "p95_ns": 846062717.5,
+ "p99_ns": 846745268.3,
+ "min_ns": 840454184,
+ "max_ns": 846915906,
+ "std_dev_ns": 2699638.495803318,
+ "cv": 0.0032040104396177296,
+ "ops_per_second": 1.1868294383112683,
+ "sample_size": 5,
+ "files_processed": 2446,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
},
{
"name": "biome-wasm",
"group": "format/typescript",
- "mean_ns": 3651240436.25,
- "p50_ns": 3651115766,
- "p75_ns": 3652416674,
- "p90_ns": 3654275795,
- "p95_ns": 3654895502,
- "p99_ns": 3655391267.6,
- "min_ns": 3650446557,
- "max_ns": 3655515209,
- "std_dev_ns": 835808.0747653235,
- "cv": 0.00022891071934548872,
- "ops_per_second": 0.2738795260021409,
- "sample_size": 4,
- "files_processed": 4025,
- "files_total": 4025,
- "files_iterated": 3701
+ "mean_ns": 4493099769.333333,
+ "p50_ns": 4498314715,
+ "p75_ns": 7300728081,
+ "p90_ns": 9612963193.8,
+ "p95_ns": 10383708231.4,
+ "p99_ns": 11000304261.48,
+ "min_ns": 4486677054,
+ "max_ns": 11154453269,
+ "std_dev_ns": 5912090.921452437,
+ "cv": 0.0013158156339648,
+ "ops_per_second": 0.22256349766040823,
+ "sample_size": 3,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
+ },
+ {
+ "name": "dprint-wasm",
+ "group": "format/typescript",
+ "mean_ns": 3104428012.4,
+ "p50_ns": 3105607188,
+ "p75_ns": 3109195408,
+ "p90_ns": 3109704749.2,
+ "p95_ns": 3109874529.6,
+ "p99_ns": 3110010353.92,
+ "min_ns": 3092698333,
+ "max_ns": 3110044310,
+ "std_dev_ns": 6951160.321078726,
+ "cv": 0.0022391114541273766,
+ "ops_per_second": 0.3221205310626323,
+ "sample_size": 5,
+ "files_processed": 2448,
+ "files_total": 2448,
+ "files_iterated": 2446,
+ "runtime": "node"
},
{
"name": "svelte/compiler",
"group": "parse/css",
- "mean_ns": 19403785.678294573,
- "p50_ns": 19376518.5,
- "p75_ns": 19515459.5,
- "p90_ns": 19626101.3,
- "p95_ns": 19749809.1,
- "p99_ns": 19835973.34,
- "min_ns": 19083462,
- "max_ns": 19889772,
- "std_dev_ns": 170231.79292084067,
- "cv": 0.008773122716525623,
- "ops_per_second": 51.53633505232013,
- "sample_size": 258,
- "files_processed": 163,
- "files_total": 193,
- "files_iterated": 159
+ "mean_ns": 9123909.508840865,
+ "p50_ns": 9042106,
+ "p75_ns": 9416333.5,
+ "p90_ns": 9891497.4,
+ "p95_ns": 10234852.2,
+ "p99_ns": 14176169.600000005,
+ "min_ns": 8752205,
+ "max_ns": 18981239,
+ "std_dev_ns": 342912.83917107055,
+ "cv": 0.03758398073093509,
+ "ops_per_second": 109.60213919603459,
+ "sample_size": 509,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv-json",
"group": "parse/css",
- "mean_ns": 176879819.27586207,
- "p50_ns": 176417640,
- "p75_ns": 178453737,
- "p90_ns": 179335842.4,
- "p95_ns": 179414368,
- "p99_ns": 179869721.35999998,
- "min_ns": 173847338,
- "max_ns": 180029857,
- "std_dev_ns": 1802916.9896241936,
- "cv": 0.010192892535763852,
- "ops_per_second": 5.653556206094932,
- "sample_size": 29,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 159
+ "mean_ns": 17316347.027027026,
+ "p50_ns": 17274356,
+ "p75_ns": 17689193,
+ "p90_ns": 17922625.6,
+ "p95_ns": 19570044,
+ "p99_ns": 20664208.400000002,
+ "min_ns": 16632722,
+ "max_ns": 25342203,
+ "std_dev_ns": 313635.4404078728,
+ "cv": 0.01811210181445062,
+ "ops_per_second": 57.74890041411269,
+ "sample_size": 259,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv_wasm-json",
"group": "parse/css",
- "mean_ns": 223033824.05555555,
- "p50_ns": 222987561,
- "p75_ns": 224042748.5,
- "p90_ns": 226002163.2,
- "p95_ns": 226359314,
- "p99_ns": 226689078.04,
- "min_ns": 222564338,
- "max_ns": 226779975,
- "std_dev_ns": 476931.02029914665,
- "cv": 0.002138379783060832,
- "ops_per_second": 4.483624868266213,
- "sample_size": 18,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 159
+ "mean_ns": 18085558.795081966,
+ "p50_ns": 18020847,
+ "p75_ns": 18356488,
+ "p90_ns": 19277685.8,
+ "p95_ns": 19763633.6,
+ "p99_ns": 22989714.4,
+ "min_ns": 17680807,
+ "max_ns": 23769112,
+ "std_dev_ns": 317166.320415112,
+ "cv": 0.0175369931340667,
+ "ops_per_second": 55.29273445904981,
+ "sample_size": 244,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv-internal",
"group": "parse/css",
- "mean_ns": 23123759.831578948,
- "p50_ns": 23081315.5,
- "p75_ns": 23581269.25,
- "p90_ns": 25285569.600000005,
- "p95_ns": 26399632.149999995,
- "p99_ns": 34455670.899999976,
- "min_ns": 22528124,
- "max_ns": 43861736,
- "std_dev_ns": 454153.9601688399,
- "cv": 0.019640143448844544,
- "ops_per_second": 43.24556245539061,
- "sample_size": 190,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 159
+ "mean_ns": 3316126.9743024963,
+ "p50_ns": 3313593.5,
+ "p75_ns": 3338725.5,
+ "p90_ns": 3399061.8000000003,
+ "p95_ns": 3446014.9,
+ "p99_ns": 3507148.37,
+ "min_ns": 3260485,
+ "max_ns": 3995943,
+ "std_dev_ns": 26931.138323164356,
+ "cv": 0.008121262705517773,
+ "ops_per_second": 301.5566073763918,
+ "sample_size": 1362,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv_wasm-internal",
"group": "parse/css",
- "mean_ns": 32072885.51879699,
- "p50_ns": 32066954.5,
- "p75_ns": 32148053.5,
- "p90_ns": 32335215.5,
- "p95_ns": 32349665.75,
- "p99_ns": 32378088.75,
- "min_ns": 31983936,
- "max_ns": 32565198,
- "std_dev_ns": 53586.24561517934,
- "cv": 0.0016707647206789045,
- "ops_per_second": 31.178984485631293,
- "sample_size": 133,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 159
+ "mean_ns": 4737140.870020964,
+ "p50_ns": 4726423,
+ "p75_ns": 4774568,
+ "p90_ns": 4831878,
+ "p95_ns": 4860150.8,
+ "p99_ns": 4913048,
+ "min_ns": 4658890,
+ "max_ns": 5058387,
+ "std_dev_ns": 36121.217938256246,
+ "cv": 0.007625109518454408,
+ "ops_per_second": 211.09779663267108,
+ "sample_size": 954,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "prettier",
"group": "format/css",
- "mean_ns": 731055145.8571428,
- "p50_ns": 726866934,
- "p75_ns": 737411474,
- "p90_ns": 739494874,
- "p95_ns": 740913023.5,
- "p99_ns": 742047543.1,
- "min_ns": 723869573,
- "max_ns": 742331173,
- "std_dev_ns": 7724050.9095097985,
- "cv": 0.010565620053810789,
- "ops_per_second": 1.3678858642428766,
- "sample_size": 7,
- "files_processed": 192,
- "files_total": 193,
- "files_iterated": 165
+ "mean_ns": 557339230,
+ "p50_ns": 549537411,
+ "p75_ns": 565597254,
+ "p90_ns": 578082432.8,
+ "p95_ns": 578788740.4,
+ "p99_ns": 579353786.48,
+ "min_ns": 539649489,
+ "max_ns": 579495048,
+ "std_dev_ns": 14362111.19491702,
+ "cv": 0.025769065627978528,
+ "ops_per_second": 1.7942393898954503,
+ "sample_size": 9,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv",
"group": "format/css",
- "mean_ns": 42276700.15044248,
- "p50_ns": 42363994,
- "p75_ns": 42798711,
- "p90_ns": 43541791.3,
- "p95_ns": 44634919.449999996,
- "p99_ns": 50024498.75,
- "min_ns": 40686670,
- "max_ns": 51286794,
- "std_dev_ns": 850647.2521532943,
- "cv": 0.020120947215043962,
- "ops_per_second": 23.65369095604624,
- "sample_size": 113,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 165
+ "mean_ns": 6774260.620414673,
+ "p50_ns": 6765946.5,
+ "p75_ns": 6830886.5,
+ "p90_ns": 6995684.8,
+ "p95_ns": 7064173.8,
+ "p99_ns": 7342010.549999992,
+ "min_ns": 6701634,
+ "max_ns": 10761764,
+ "std_dev_ns": 47048.678557182546,
+ "cv": 0.006945212355042601,
+ "ops_per_second": 147.6175860412626,
+ "sample_size": 627,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "tsv_wasm",
"group": "format/css",
- "mean_ns": 56842907.02898551,
- "p50_ns": 56836126.5,
- "p75_ns": 57051209.25,
- "p90_ns": 57277215.5,
- "p95_ns": 57411214.85,
- "p99_ns": 57614455.839999996,
- "min_ns": 56701796,
- "max_ns": 57937459,
- "std_dev_ns": 94515.15490540798,
- "cv": 0.0016627431608522859,
- "ops_per_second": 17.59234445011894,
- "sample_size": 69,
- "files_processed": 165,
- "files_total": 193,
- "files_iterated": 165
+ "mean_ns": 9738625.60479042,
+ "p50_ns": 9710363,
+ "p75_ns": 9816992,
+ "p90_ns": 9914974.2,
+ "p95_ns": 9965780,
+ "p99_ns": 10203037.44,
+ "min_ns": 9549996,
+ "max_ns": 13430272,
+ "std_dev_ns": 103264.4596055366,
+ "cv": 0.010603596831440047,
+ "ops_per_second": 102.68389407105876,
+ "sample_size": 501,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "oxfmt",
"group": "format/css",
- "mean_ns": 759715951,
- "p50_ns": 758504294,
- "p75_ns": 764356670.5,
- "p90_ns": 792120220.8000001,
- "p95_ns": 810371960.4,
- "p99_ns": 824973352.0799999,
- "min_ns": 755827467,
- "max_ns": 828623700,
- "std_dev_ns": 4300372.980217599,
- "cv": 0.005660501105126328,
- "ops_per_second": 1.3162814321375227,
- "sample_size": 6,
- "files_processed": 192,
- "files_total": 193,
- "files_iterated": 165
+ "mean_ns": 18918226.36781609,
+ "p50_ns": 18885129.5,
+ "p75_ns": 19345143.75,
+ "p90_ns": 19802674,
+ "p95_ns": 20013384.15,
+ "p99_ns": 21136025.340000004,
+ "min_ns": 17218416,
+ "max_ns": 21629487,
+ "std_dev_ns": 621908.927584265,
+ "cv": 0.0328735324069419,
+ "ops_per_second": 52.85907783095416,
+ "sample_size": 261,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
},
{
"name": "biome-wasm",
"group": "format/css",
- "mean_ns": 207351523.875,
- "p50_ns": 207159397,
- "p75_ns": 208264123,
- "p90_ns": 208754043.4,
- "p95_ns": 209395575,
- "p99_ns": 209795343.92,
- "min_ns": 205889964,
- "max_ns": 209888054,
- "std_dev_ns": 897025.2129294197,
- "cv": 0.004326108610950857,
- "ops_per_second": 4.822727999832984,
- "sample_size": 24,
- "files_processed": 193,
- "files_total": 193,
- "files_iterated": 165
+ "mean_ns": 132540457.48387097,
+ "p50_ns": 133175334.5,
+ "p75_ns": 142052510.75,
+ "p90_ns": 179326793,
+ "p95_ns": 187457932.25,
+ "p99_ns": 211404413.35,
+ "min_ns": 123861288,
+ "max_ns": 212788173,
+ "std_dev_ns": 9449635.541954929,
+ "cv": 0.07129623453355644,
+ "ops_per_second": 7.544866065681804,
+ "sample_size": 31,
+ "files_processed": 50,
+ "files_total": 50,
+ "files_iterated": 50,
+ "runtime": "node"
}
],
- "suppressed_noise": {
- "oxfmt::textToDoc": 1,
- "panicked at crates/biome_rowan": 1
- }
+ "suppressed_noise": {},
+ "variant_parity": []
}
diff --git a/src/routes/docs/benchmarks/benchmarks.ts b/src/routes/docs/benchmarks/benchmarks.ts
index e457993..f0cfa95 100644
--- a/src/routes/docs/benchmarks/benchmarks.ts
+++ b/src/routes/docs/benchmarks/benchmarks.ts
@@ -1,5 +1,5 @@
-import type {BenchmarkBaseline} from './benchmark_data.ts';
+import type { BenchmarkBaseline } from './benchmark_data.ts';
-import json from './benchmarks.json' with {type: 'json'};
+import json from './benchmarks.json' with { type: 'json' };
export const benchmarks_json: BenchmarkBaseline = json as BenchmarkBaseline;
diff --git a/src/routes/docs/benchmarks/benchmarks_cli.ts b/src/routes/docs/benchmarks/benchmarks_cli.ts
new file mode 100644
index 0000000..1aead10
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_cli.ts
@@ -0,0 +1,144 @@
+// The independent end-to-end CLI benchmark — a fork of Oxc's official
+// `bench-formatter` that adds tsv (https://github.com/ryanatkn/oxc-bench-formatter,
+// forked from https://github.com/oxc-project/bench-formatter — `tsv` is the fork's
+// DEFAULT branch, so the bare fork URL already lands on it; upstream carries
+// neither tsv nor this analysis). Unlike the in-process, single-threaded numbers
+// elsewhere on this page, it measures the WHOLE CLI: process spawn, file
+// discovery, I/O, and each tool's default multi-file parallelism. tsv, oxfmt, and
+// biome parallelize across files while prettier is effectively serial, so the
+// wall-clock ratios scale with core count and are machine-dependent — the
+// parallelism-neutral view is CPU work (hyperfine `User` time). tsv runs only in
+// the JSX-free scenarios (it has no JSX/TSX parser).
+//
+// The numbers come from `benchmarks_formatters.json`, generated from that
+// harness's README by `benchmarks_formatters.gen.json.ts`; only the prose below
+// is authored here. To refresh: run `pnpm run update-readme` in the harness, then
+// `gro gen` here.
+
+import { benchmarks_formatters_json } from './benchmarks_formatters.ts';
+import type { FormatterScenario } from './formatter_benchmark_data.ts';
+
+export interface CliFormatterResult {
+ /** Display label; `tsv` is the reference row every ratio is computed against. */
+ label: string;
+ /** hyperfine wall-clock mean, in milliseconds — what you experience typing the command. */
+ wall_ms: number;
+ /** hyperfine `User` time (total CPU across all threads), in ms — the parallelism-neutral view. */
+ cpu_ms: number;
+ /** Peak resident set size (RSS), in megabytes; `null` when the harness measured no memory. */
+ memory_mb: number | null;
+}
+
+export interface CliScenario {
+ key: string;
+ heading: string;
+ /** One-line description of the corpus and what makes the comparison fair. */
+ description: string;
+ /** Whether every formatter is effectively single-threaded here (one input file). */
+ single_threaded: boolean;
+ /** Results ascending by wall-clock time, tsv-relative ratios computed by the component. */
+ results: Array;
+}
+
+export interface BenchmarksCliReport {
+ machine: string;
+ versions: Record;
+ scenarios: Array;
+}
+
+/**
+ * The prose framing for each scenario, keyed by its generated scenario id — the
+ * harness measures the numbers but doesn't explain them. Also fixes the page
+ * order, which is by narrative weight (the multi-file repo leads), not the order
+ * the harness runs them in. A scenario missing from here is dropped rather than
+ * rendered unexplained.
+ */
+const SCENARIO_COPY: Record> = {
+ 'typescript-only-tsv-fair': {
+ heading: 'TypeScript repo (Outline, non-JSX subset)',
+ description:
+ "Outline's .ts/.js/.mjs files — the common set every formatter supports, each scoped to it, with a preflight parse check confirming none reject anything. The fair way to put tsv on a real multi-file repo.",
+ single_threaded: false
+ },
+ 'large-single-file': {
+ heading: 'Large single file (TypeScript compiler parser.ts, ~540KB)',
+ description:
+ 'One ~13.7K-line .ts file. With a single input every formatter is effectively single-threaded, so wall-clock is close to an engine comparison here — except oxfmt, which spins up a worker pool it cannot use, inflating its CPU time.',
+ single_threaded: true
+ }
+};
+
+/**
+ * Display labels for the harness's hyperfine command names, which read better
+ * spaced out in a table. Names absent here are displayed as-is.
+ */
+export const CLI_LABELS: Record = {
+ 'prettier+oxc-parser': 'prettier + oxc-parser'
+};
+
+const to_results = (scenario: FormatterScenario): Array =>
+ scenario.timings
+ .map((timing) => ({
+ label: CLI_LABELS[timing.name] ?? timing.name,
+ wall_ms: timing.mean_ms,
+ cpu_ms: timing.user_ms,
+ memory_mb: scenario.memory.find((m) => m.name === timing.name)?.mean_mb ?? null
+ }))
+ .sort((a, b) => a.wall_ms - b.wall_ms);
+
+/** The scenarios the page has prose for; every one must resolve to generated data. */
+export const CLI_SCENARIO_KEYS = Object.keys(SCENARIO_COPY);
+
+const to_scenarios = (): Array =>
+ Object.entries(SCENARIO_COPY).flatMap(([key, copy]) => {
+ const scenario = benchmarks_formatters_json.scenarios.find((s) => s.id === key);
+ return scenario ? [{ key, ...copy, results: to_results(scenario) }] : [];
+ });
+
+export const benchmarks_cli: BenchmarksCliReport = {
+ machine: benchmarks_formatters_json.machine,
+ versions: benchmarks_formatters_json.versions,
+ scenarios: to_scenarios()
+};
+
+/** The scenario id the page's prose calls "the TypeScript repo" — the multi-file, real-repo run. */
+export const CLI_TS_REPO_KEY = 'typescript-only-tsv-fair';
+
+/**
+ * How many times faster or lighter tsv is than `label` in one CLI scenario, by
+ * the given metric — the ratios the page's prose quotes.
+ *
+ * @returns the ratio, or `undefined` when the scenario, the formatter, or either
+ * side's measurement is absent
+ */
+export const cli_speedup_vs_tsv = (
+ scenario_key: string,
+ label: string,
+ metric: keyof Omit
+): number | undefined => {
+ const results = benchmarks_cli.scenarios.find((s) => s.key === scenario_key)?.results;
+ const tsv = results?.find((r) => r.label === 'tsv')?.[metric];
+ const other = results?.find((r) => r.label === label)?.[metric];
+ if (tsv == null || other == null || !tsv) return undefined;
+ return other / tsv;
+};
+
+/**
+ * The span of "times less memory than tsv" across every non-tsv row, over one
+ * scenario or all of them — the prose's `3–13x` claim.
+ *
+ * @returns the low and high ratio, or `undefined` when nothing was measured
+ */
+export const cli_memory_ratio_range = (
+ scenario_key?: string
+): { min: number; max: number } | undefined => {
+ const scenarios = benchmarks_cli.scenarios.filter((s) => !scenario_key || s.key === scenario_key);
+ const ratios = scenarios.flatMap((scenario) =>
+ scenario.results
+ .filter((r) => r.label !== 'tsv')
+ .map((r) => cli_speedup_vs_tsv(scenario.key, r.label, 'memory_mb'))
+ .filter((ratio) => ratio !== undefined)
+ );
+ if (ratios.length === 0) return undefined;
+ return { min: Math.min(...ratios), max: Math.max(...ratios) };
+};
diff --git a/src/routes/docs/benchmarks/benchmarks_conformance.json b/src/routes/docs/benchmarks/benchmarks_conformance.json
new file mode 100644
index 0000000..82322b0
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_conformance.json
@@ -0,0 +1,650 @@
+{
+ "version": 7,
+ "runtime": "node",
+ "corpus_kind": "conformance",
+ "timestamp": "2026-07-22T09:27:46.776Z",
+ "git_commit": "d063479a",
+ "machine": {
+ "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics",
+ "os": "linux",
+ "arch": "x86_64",
+ "runtime_version": "24.14.1"
+ },
+ "corpus": {
+ "svelte": 4539,
+ "typescript": 44224,
+ "css": 22641
+ },
+ "corpus_sources": [
+ {
+ "path": "../prettier-plugin-svelte/test",
+ "files": 318,
+ "by_language": {
+ "svelte": 318,
+ "typescript": 0,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/prettier-plugin-svelte",
+ "slug": "sveltejs/prettier-plugin-svelte",
+ "commit": "7809486a9716faa2234c8a45d88b601034de52d8",
+ "subpath": "test"
+ }
+ },
+ {
+ "path": "../prettier/tests/format/typescript",
+ "files": 793,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 793,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/prettier/prettier",
+ "slug": "prettier/prettier",
+ "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b",
+ "subpath": "tests/format/typescript"
+ }
+ },
+ {
+ "path": "../prettier/tests/format/js",
+ "files": 1103,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 1103,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/prettier/prettier",
+ "slug": "prettier/prettier",
+ "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b",
+ "subpath": "tests/format/js"
+ }
+ },
+ {
+ "path": "../prettier/tests/format/css",
+ "files": 228,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 78,
+ "css": 150
+ },
+ "repo": {
+ "url": "https://github.com/prettier/prettier",
+ "slug": "prettier/prettier",
+ "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b",
+ "subpath": "tests/format/css"
+ }
+ },
+ {
+ "path": "../prettier/tests/format/html",
+ "files": 84,
+ "by_language": {
+ "svelte": 84,
+ "typescript": 0,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/prettier/prettier",
+ "slug": "prettier/prettier",
+ "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b",
+ "subpath": "tests/format/html"
+ }
+ },
+ {
+ "path": "../svelte/packages/svelte/tests",
+ "files": 4455,
+ "by_language": {
+ "svelte": 4137,
+ "typescript": 137,
+ "css": 181
+ },
+ "repo": {
+ "url": "https://github.com/sveltejs/svelte",
+ "slug": "sveltejs/svelte",
+ "commit": "b4d1583ae20f3869a88a731d9a265c546c099f66",
+ "subpath": "packages/svelte/tests"
+ }
+ },
+ {
+ "path": "benches/js/.cache/wpt_css",
+ "files": 22310,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 0,
+ "css": 22310
+ },
+ "repo": {
+ "url": "https://github.com/web-platform-tests/wpt",
+ "slug": "web-platform-tests/wpt",
+ "commit": "",
+ "subpath": ""
+ }
+ },
+ {
+ "path": "benches/js/.cache/test262_files.json",
+ "files": 42113,
+ "by_language": {
+ "svelte": 0,
+ "typescript": 42113,
+ "css": 0
+ },
+ "repo": {
+ "url": "https://github.com/tc39/test262",
+ "slug": "tc39/test262",
+ "commit": "",
+ "subpath": ""
+ }
+ }
+ ],
+ "versions": {
+ "tsv": "0.2.0",
+ "svelte": "5.56.4",
+ "acorn": "8.16.0",
+ "acorn_ts": "1.0.11",
+ "prettier": "3.9.5",
+ "prettier_svelte": "4.1.1",
+ "oxc_parser": "0.140.0",
+ "oxfmt": "0.60.0",
+ "biome": "2.5.4",
+ "dprint": "0.96.1"
+ },
+ "binary_sizes": [
+ {
+ "label": "tsv (ffi)",
+ "bytes": 3346544,
+ "kind": "native",
+ "gzip_bytes": 1442507
+ },
+ {
+ "label": "tsv format (ffi)",
+ "bytes": 3095968,
+ "kind": "native",
+ "gzip_bytes": 1339168
+ },
+ {
+ "label": "tsv parse (ffi)",
+ "bytes": 1606912,
+ "kind": "native",
+ "gzip_bytes": 703248
+ },
+ {
+ "label": "tsv (napi)",
+ "bytes": 3479392,
+ "kind": "native",
+ "gzip_bytes": 1486322
+ },
+ {
+ "label": "tsv_format_wasm",
+ "bytes": 2216330,
+ "kind": "wasm",
+ "gzip_bytes": 787915
+ },
+ {
+ "label": "tsv_parse_wasm",
+ "bytes": 1052601,
+ "kind": "wasm",
+ "gzip_bytes": 388661
+ },
+ {
+ "label": "tsv_wasm",
+ "bytes": 2444292,
+ "kind": "wasm",
+ "gzip_bytes": 867803
+ },
+ {
+ "label": "biome (wasm)",
+ "bytes": 38584187,
+ "kind": "wasm",
+ "gzip_bytes": 9281487
+ },
+ {
+ "label": "dprint (wasm)",
+ "bytes": 4174185,
+ "kind": "wasm",
+ "gzip_bytes": 1155127
+ },
+ {
+ "label": "oxc-parser (napi)",
+ "bytes": 2354904,
+ "kind": "native",
+ "gzip_bytes": 954754
+ },
+ {
+ "label": "oxfmt (napi)",
+ "bytes": 8844056,
+ "kind": "native",
+ "gzip_bytes": 3575335
+ },
+ {
+ "label": "oxc-parser (wasm)",
+ "bytes": 1531455,
+ "kind": "wasm",
+ "gzip_bytes": 495227
+ }
+ ],
+ "entries": [
+ {
+ "name": "svelte/compiler",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json-no-locations",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json-no-locations",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-internal",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-internal",
+ "group": "parse/svelte",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 4539,
+ "files_total": 4539,
+ "files_iterated": 4539,
+ "runtime": "node"
+ },
+ {
+ "name": "acorn-typescript",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 43647,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json-no-locations",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json-no-locations",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-internal",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-internal",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44023,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "oxc-parser",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44014,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "oxc-parser-wasm",
+ "group": "parse/typescript",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 44014,
+ "files_total": 44224,
+ "files_iterated": 43619,
+ "runtime": "node"
+ },
+ {
+ "name": "svelte/compiler",
+ "group": "parse/css",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 22402,
+ "files_total": 22641,
+ "files_iterated": 22388,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-json",
+ "group": "parse/css",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 22457,
+ "files_total": 22641,
+ "files_iterated": 22388,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-json",
+ "group": "parse/css",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 22457,
+ "files_total": 22641,
+ "files_iterated": 22388,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv-internal",
+ "group": "parse/css",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 22457,
+ "files_total": 22641,
+ "files_iterated": 22388,
+ "runtime": "node"
+ },
+ {
+ "name": "tsv_wasm-internal",
+ "group": "parse/css",
+ "mean_ns": null,
+ "p50_ns": null,
+ "p75_ns": null,
+ "p90_ns": null,
+ "p95_ns": null,
+ "p99_ns": null,
+ "min_ns": null,
+ "max_ns": null,
+ "std_dev_ns": null,
+ "cv": null,
+ "ops_per_second": null,
+ "sample_size": null,
+ "files_processed": 22457,
+ "files_total": 22641,
+ "files_iterated": 22388,
+ "runtime": "node"
+ }
+ ],
+ "suppressed_noise": {},
+ "variant_parity": []
+}
diff --git a/src/routes/docs/benchmarks/benchmarks_conformance.ts b/src/routes/docs/benchmarks/benchmarks_conformance.ts
new file mode 100644
index 0000000..cf47f5d
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_conformance.ts
@@ -0,0 +1,5 @@
+import type { BenchmarkBaseline } from './benchmark_data.ts';
+
+import json from './benchmarks_conformance.json' with { type: 'json' };
+
+export const benchmarks_conformance_json: BenchmarkBaseline = json as BenchmarkBaseline;
diff --git a/src/routes/docs/benchmarks/benchmarks_cross_runtime.json b/src/routes/docs/benchmarks/benchmarks_cross_runtime.json
new file mode 100644
index 0000000..52ab6b6
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_cross_runtime.json
@@ -0,0 +1,739 @@
+{
+ "version": 7,
+ "kind": "combined",
+ "generated": "2026-07-22T09:27:01.099Z",
+ "runtimes": ["deno", "node", "bun"],
+ "mixed_vintage": false,
+ "mixed_machine": false,
+ "sources": [
+ {
+ "runtime": "deno",
+ "timestamp": "2026-07-22T09:08:34.891Z",
+ "git_commit": "d063479a",
+ "tsv": "0.2.0",
+ "machine": {
+ "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics",
+ "os": "linux",
+ "arch": "x86_64",
+ "runtime_version": "2.9.3"
+ }
+ },
+ {
+ "runtime": "node",
+ "timestamp": "2026-07-22T09:18:34.995Z",
+ "git_commit": "d063479a",
+ "tsv": "0.2.0",
+ "machine": {
+ "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics",
+ "os": "linux",
+ "arch": "x86_64",
+ "runtime_version": "24.14.1"
+ }
+ },
+ {
+ "runtime": "bun",
+ "timestamp": "2026-07-22T09:27:00.825Z",
+ "git_commit": "d063479a",
+ "tsv": "0.2.0",
+ "machine": {
+ "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics",
+ "os": "linux",
+ "arch": "x86_64",
+ "runtime_version": "1.3.14"
+ }
+ }
+ ],
+ "rows": [
+ {
+ "group": "parse/svelte",
+ "name": "svelte/compiler",
+ "ops_per_second": {
+ "deno": 2.3234362309765233,
+ "node": 2.2216431647548864,
+ "bun": 1.4131199693476677
+ },
+ "mean_ns": {
+ "deno": 430397007.1,
+ "node": 450117289.7,
+ "bun": 707654000.8571428
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv-json",
+ "ops_per_second": {
+ "deno": 5.03910728545968,
+ "node": 4.6499867822902035,
+ "bun": 6.022114301293246
+ },
+ "mean_ns": {
+ "deno": 198447848.66666666,
+ "node": 215054374.7368421,
+ "bun": 166054636.29032257
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv-json-no-locations",
+ "ops_per_second": {
+ "deno": 7.866195242246116,
+ "node": 7.302420321988621,
+ "bun": 8.425385240870403
+ },
+ "mean_ns": {
+ "deno": 127126262.34210527,
+ "node": 136940898.48387095,
+ "bun": 118688934.85714285
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv_wasm-json",
+ "ops_per_second": {
+ "deno": 4.11280382829322,
+ "node": 4.16734009656939,
+ "bun": 5.599466083525936
+ },
+ "mean_ns": {
+ "deno": 243143131,
+ "node": 239961216.70588234,
+ "bun": 178588455.5925926
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv_wasm-json-no-locations",
+ "ops_per_second": {
+ "deno": 6.26423723490975,
+ "node": 6.445842455503006,
+ "bun": 7.629741637830749
+ },
+ "mean_ns": {
+ "deno": 159636355.15384614,
+ "node": 155138759.1153846,
+ "bun": 131066037.02564102
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv-internal",
+ "ops_per_second": {
+ "deno": 50.884877826102425,
+ "node": 49.18726473245663,
+ "bun": 52.797052562303065
+ },
+ "mean_ns": {
+ "deno": 19652204.00877193,
+ "node": 20330465.730088495,
+ "bun": 18940451.24621212
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "parse/svelte",
+ "name": "tsv_wasm-internal",
+ "ops_per_second": {
+ "deno": 32.22839602213156,
+ "node": 36.1862196566141,
+ "bun": 37.6696281621773
+ },
+ "mean_ns": {
+ "deno": 31028537.669491526,
+ "node": 27634829.2109375,
+ "bun": 26546585.373626374
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "format/svelte",
+ "name": "prettier",
+ "ops_per_second": {
+ "deno": 0.23714053845257216,
+ "node": 0.22770016339498617,
+ "bun": 0.2587928860879906
+ },
+ "mean_ns": {
+ "deno": 4216908701.1666665,
+ "node": 4391740370.714286,
+ "bun": 3864093851.714286
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "format/svelte",
+ "name": "tsv",
+ "ops_per_second": {
+ "deno": 14.710947545190182,
+ "node": 14.703406947264233,
+ "bun": 14.469091128435753
+ },
+ "mean_ns": {
+ "deno": 67976586.61538461,
+ "node": 68011448.20289855,
+ "bun": 69112841.375
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "format/svelte",
+ "name": "tsv_wasm",
+ "ops_per_second": {
+ "deno": 9.340617577022751,
+ "node": 10.276576472095927,
+ "bun": 10.522953526889031
+ },
+ "mean_ns": {
+ "deno": 107059302.20930232,
+ "node": 97308671.10416667,
+ "bun": 95030354.11538461
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "format/svelte",
+ "name": "oxfmt",
+ "ops_per_second": {
+ "deno": 0.23743594970499868,
+ "node": 0.23101750520591594,
+ "bun": 0.19643263399368738
+ },
+ "mean_ns": {
+ "deno": 4211662139.8,
+ "node": 4328676301.428572,
+ "bun": 5090803802.142858
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767,
+ "bun": 767
+ }
+ },
+ {
+ "group": "format/svelte",
+ "name": "biome-wasm",
+ "ops_per_second": {
+ "deno": 1.4068179347357905,
+ "node": 1.1005294297485004
+ },
+ "mean_ns": {
+ "deno": 710824034.375,
+ "node": 908653574.3333334
+ },
+ "files_iterated": {
+ "deno": 767,
+ "node": 767
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "acorn-typescript",
+ "ops_per_second": {
+ "deno": 0.35017861058934563,
+ "node": 0.319602416005853,
+ "bun": 0.14577892405595072
+ },
+ "mean_ns": {
+ "deno": 2855685555.2,
+ "node": 3128887486.2,
+ "bun": 6859702158.428572
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv-json",
+ "ops_per_second": {
+ "deno": 0.5530626912278549,
+ "node": 0.49114251487265487,
+ "bun": 0.7188311660438776
+ },
+ "mean_ns": {
+ "deno": 1808113286,
+ "node": 2036068900,
+ "bun": 1391147250.2
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv-json-no-locations",
+ "ops_per_second": {
+ "deno": 1.1423054195223503,
+ "node": 0.9939125300571001,
+ "bun": 1.2560381326014052
+ },
+ "mean_ns": {
+ "deno": 875422617.2,
+ "node": 1006124754.2,
+ "bun": 796154172.4285715
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv_wasm-json",
+ "ops_per_second": {
+ "deno": 0.474850274197908,
+ "node": 0.46275614691458916,
+ "bun": 0.6751595659121256
+ },
+ "mean_ns": {
+ "deno": 2105926971.8,
+ "node": 2160965352.2,
+ "bun": 1481131351
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv_wasm-json-no-locations",
+ "ops_per_second": {
+ "deno": 0.933349493315349,
+ "node": 0.9159845138907174,
+ "bun": 1.1532055304323752
+ },
+ "mean_ns": {
+ "deno": 1071410020.75,
+ "node": 1091721513.6666667,
+ "bun": 867148113.3333334
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv-internal",
+ "ops_per_second": {
+ "deno": 7.63397113225453,
+ "node": 7.138829481835908,
+ "bun": 8.284841871605634
+ },
+ "mean_ns": {
+ "deno": 130993421.72972973,
+ "node": 140078986.69444445,
+ "bun": 120702364.08823529
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "tsv_wasm-internal",
+ "ops_per_second": {
+ "deno": 4.961691948931678,
+ "node": 5.5951836972064735,
+ "bun": 5.9011458444952325
+ },
+ "mean_ns": {
+ "deno": 201544152.73913044,
+ "node": 178725141.85714287,
+ "bun": 169458614.70833334
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "oxc-parser",
+ "ops_per_second": {
+ "deno": 0.8635787815927837,
+ "node": 0.7712492352678207,
+ "bun": 1.0291052890541648
+ },
+ "mean_ns": {
+ "deno": 1157971943.4,
+ "node": 1296597720,
+ "bun": 971717870.5
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445,
+ "bun": 2445
+ }
+ },
+ {
+ "group": "parse/typescript",
+ "name": "oxc-parser-wasm",
+ "ops_per_second": {
+ "deno": 0.7875832431777169,
+ "node": 0.7448803286370177
+ },
+ "mean_ns": {
+ "deno": 1269707054.6666667,
+ "node": 1342497528.2
+ },
+ "files_iterated": {
+ "deno": 2445,
+ "node": 2445
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "prettier",
+ "ops_per_second": {
+ "deno": 0.08232064665797907,
+ "node": 0.07446628793263295,
+ "bun": 0.06408892677184211
+ },
+ "mean_ns": {
+ "deno": 12147620804.714285,
+ "node": 13428895514.5,
+ "bun": 15603319487
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446,
+ "bun": 2446
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "tsv",
+ "ops_per_second": {
+ "deno": 2.0016063010662433,
+ "node": 1.9697911121325091,
+ "bun": 1.9487498480140761
+ },
+ "mean_ns": {
+ "deno": 499598747,
+ "node": 507668043.5,
+ "bun": 513149494.8
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446,
+ "bun": 2446
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "tsv_wasm",
+ "ops_per_second": {
+ "deno": 1.2977692582359848,
+ "node": 1.4405873924133346,
+ "bun": 1.4775642868780696
+ },
+ "mean_ns": {
+ "deno": 770553003.6666666,
+ "node": 694161288.1428572,
+ "bun": 676789503.4285715
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446,
+ "bun": 2446
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "oxfmt",
+ "ops_per_second": {
+ "deno": 1.1953372949795649,
+ "node": 1.1868294383112683,
+ "bun": 0.9657736954840443
+ },
+ "mean_ns": {
+ "deno": 836583953.5,
+ "node": 842581054.8,
+ "bun": 1035439259.4
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446,
+ "bun": 2446
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "biome-wasm",
+ "ops_per_second": {
+ "deno": 0.24161617382436792,
+ "node": 0.22256349766040823
+ },
+ "mean_ns": {
+ "deno": 4138795777.5,
+ "node": 4493099769.333333
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446
+ }
+ },
+ {
+ "group": "format/typescript",
+ "name": "dprint-wasm",
+ "ops_per_second": {
+ "deno": 0.2939344856052669,
+ "node": 0.3221205310626323,
+ "bun": 0.3360723021627639
+ },
+ "mean_ns": {
+ "deno": 3402118665.8,
+ "node": 3104428012.4,
+ "bun": 2975550182.4
+ },
+ "files_iterated": {
+ "deno": 2446,
+ "node": 2446,
+ "bun": 2446
+ }
+ },
+ {
+ "group": "parse/css",
+ "name": "svelte/compiler",
+ "ops_per_second": {
+ "deno": 96.83747985855102,
+ "node": 109.60213919603459,
+ "bun": 68.1549019339242
+ },
+ "mean_ns": {
+ "deno": 10326580.17805383,
+ "node": 9123909.508840865,
+ "bun": 14672458.93728223
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "parse/css",
+ "name": "tsv-json",
+ "ops_per_second": {
+ "deno": 67.15927625563137,
+ "node": 57.74890041411269,
+ "bun": 74.45528345165681
+ },
+ "mean_ns": {
+ "deno": 14889975.826923076,
+ "node": 17316347.027027026,
+ "bun": 13430880.303468209
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "parse/css",
+ "name": "tsv_wasm-json",
+ "ops_per_second": {
+ "deno": 52.00600032417462,
+ "node": 55.29273445904981,
+ "bun": 69.69675930034147
+ },
+ "mean_ns": {
+ "deno": 19228550.432,
+ "node": 18085558.795081966,
+ "bun": 14347869.399361022
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "parse/css",
+ "name": "tsv-internal",
+ "ops_per_second": {
+ "deno": 316.0234093250357,
+ "node": 301.5566073763918,
+ "bun": 343.2247565755417
+ },
+ "mean_ns": {
+ "deno": 3164322.5485599465,
+ "node": 3316126.9743024963,
+ "bun": 2913542.746675111
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "parse/css",
+ "name": "tsv_wasm-internal",
+ "ops_per_second": {
+ "deno": 178.49939360958777,
+ "node": 211.09779663267108,
+ "bun": 218.36861786361445
+ },
+ "mean_ns": {
+ "deno": 5602259.92804878,
+ "node": 4737140.870020964,
+ "bun": 4579412.59959142
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "format/css",
+ "name": "prettier",
+ "ops_per_second": {
+ "deno": 1.9453387285461383,
+ "node": 1.7942393898954503,
+ "bun": 1.9775382167053115
+ },
+ "mean_ns": {
+ "deno": 514049294,
+ "node": 557339230,
+ "bun": 505679228.625
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "format/css",
+ "name": "tsv",
+ "ops_per_second": {
+ "deno": 153.9639336807719,
+ "node": 147.6175860412626,
+ "bun": 153.79399297527175
+ },
+ "mean_ns": {
+ "deno": 6495027.608695652,
+ "node": 6774260.620414673,
+ "bun": 6502204.544235925
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "format/css",
+ "name": "tsv_wasm",
+ "ops_per_second": {
+ "deno": 88.06396381287563,
+ "node": 102.68389407105876,
+ "bun": 104.09990571889091
+ },
+ "mean_ns": {
+ "deno": 11355382.573113207,
+ "node": 9738625.60479042,
+ "bun": 9606156.634765625
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "format/css",
+ "name": "oxfmt",
+ "ops_per_second": {
+ "deno": 55.80449913414042,
+ "node": 52.85907783095416,
+ "bun": 46.70127981781842
+ },
+ "mean_ns": {
+ "deno": 17919702.0942029,
+ "node": 18918226.36781609,
+ "bun": 21412689.414529916
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50,
+ "bun": 50
+ }
+ },
+ {
+ "group": "format/css",
+ "name": "biome-wasm",
+ "ops_per_second": {
+ "deno": 10.13362212121382,
+ "node": 7.544866065681804
+ },
+ "mean_ns": {
+ "deno": 98681398.2244898,
+ "node": 132540457.48387097
+ },
+ "files_iterated": {
+ "deno": 50,
+ "node": 50
+ }
+ }
+ ]
+}
diff --git a/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts b/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts
new file mode 100644
index 0000000..3793a17
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts
@@ -0,0 +1,5 @@
+import type { CrossRuntimeReport } from './benchmark_data.ts';
+
+import json from './benchmarks_cross_runtime.json' with { type: 'json' };
+
+export const benchmarks_cross_runtime_json: CrossRuntimeReport = json as CrossRuntimeReport;
diff --git a/src/routes/docs/benchmarks/benchmarks_formatters.gen.json.ts b/src/routes/docs/benchmarks/benchmarks_formatters.gen.json.ts
new file mode 100644
index 0000000..6b91b5f
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_formatters.gen.json.ts
@@ -0,0 +1,40 @@
+import type { Gen } from '@fuzdev/gro/gen.ts';
+import { readFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+
+import { parse_formatter_benchmarks } from './formatter_benchmark_data.ts';
+
+// The formatter comparison harness lives in a sibling checkout and publishes its
+// results only as prose — see `formatter_benchmark_data.ts`.
+const README_PATH = '../oxc-bench-formatter/README.md';
+
+/**
+ * Generate `benchmarks_formatters.json` from the sibling formatter-benchmark
+ * harness's README.
+ *
+ * A MISSING sibling checkout is the one tolerated case — it's an optional repo,
+ * so generation is skipped, the committed JSON stands, and `gro gen --check`
+ * passes on a machine (or CI) that has only this repo. A README that IS present
+ * but doesn't parse fails the task instead: the alternative is quietly publishing
+ * stale or scenario-stripped numbers.
+ */
+export const gen: Gen = {
+ generate: async ({ log }) => {
+ const path = resolve(README_PATH);
+
+ let readme;
+ try {
+ readme = await readFile(path, 'utf8');
+ } catch (error) {
+ // only "it isn't there" is benign; an unreadable file is a real problem
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
+ log.info(`skipping formatter benchmarks, no readme at ${path}`);
+ return null;
+ }
+
+ const benchmarks = parse_formatter_benchmarks(readme);
+ log.info(`parsed ${benchmarks.scenarios.length} tsv scenario(s) from ${path}`);
+ return JSON.stringify(benchmarks);
+ },
+ dependencies: { files: [resolve(README_PATH)] }
+};
diff --git a/src/routes/docs/benchmarks/benchmarks_formatters.json b/src/routes/docs/benchmarks/benchmarks_formatters.json
new file mode 100644
index 0000000..c37f10b
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_formatters.json
@@ -0,0 +1,299 @@
+{
+ "machine": "AMD Ryzen 5 PRO 7530U with Radeon Graphics · 12 threads · linux x64",
+ "versions": {
+ "prettier": "3.9.5",
+ "biome": "2.5.4",
+ "oxfmt": "0.60.0",
+ "tsv": "0.1.0"
+ },
+ "scenarios": [
+ {
+ "id": "large-single-file",
+ "name": "Large Single File",
+ "target": "TypeScript compiler parser.ts (~540KB)",
+ "warmup_runs": 2,
+ "benchmark_runs": 5,
+ "preflight": [
+ {
+ "name": "prettier",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "biome",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "oxfmt",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "tsv",
+ "rejected": 0,
+ "unavailable": false
+ }
+ ],
+ "timings": [
+ {
+ "name": "prettier",
+ "mean_ms": 1605,
+ "stddev_ms": 22,
+ "user_ms": 1541,
+ "system_ms": 858,
+ "min_ms": 1572,
+ "max_ms": 1623
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "mean_ms": 985,
+ "stddev_ms": 4.8,
+ "user_ms": 889,
+ "system_ms": 462.3,
+ "min_ms": 977.6,
+ "max_ms": 990
+ },
+ {
+ "name": "biome",
+ "mean_ms": 113.9,
+ "stddev_ms": 1.7,
+ "user_ms": 87.1,
+ "system_ms": 34.6,
+ "min_ms": 112.2,
+ "max_ms": 116.7
+ },
+ {
+ "name": "oxfmt",
+ "mean_ms": 195.8,
+ "stddev_ms": 9.4,
+ "user_ms": 354.4,
+ "system_ms": 201.4,
+ "min_ms": 188.1,
+ "max_ms": 211.9
+ },
+ {
+ "name": "tsv",
+ "mean_ms": 23.8,
+ "stddev_ms": 0.2,
+ "user_ms": 14.9,
+ "system_ms": 8.8,
+ "min_ms": 23.6,
+ "max_ms": 24.1
+ }
+ ],
+ "baseline": "tsv",
+ "speedups": [
+ {
+ "name": "biome",
+ "ratio": 4.79,
+ "ratio_stddev": 0.08
+ },
+ {
+ "name": "oxfmt",
+ "ratio": 8.24,
+ "ratio_stddev": 0.4
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "ratio": 41.44,
+ "ratio_stddev": 0.43
+ },
+ {
+ "name": "prettier",
+ "ratio": 67.55,
+ "ratio_stddev": 1.13
+ }
+ ],
+ "memory": [
+ {
+ "name": "prettier",
+ "mean_mb": 305.9,
+ "min_mb": 293.4,
+ "max_mb": 313.3,
+ "ratio": 12.34,
+ "ratio_stddev": 0.44
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "mean_mb": 196.2,
+ "min_mb": 194.6,
+ "max_mb": 197.9,
+ "ratio": 7.91,
+ "ratio_stddev": 0.19
+ },
+ {
+ "name": "biome",
+ "mean_mb": 101,
+ "min_mb": 98,
+ "max_mb": 104,
+ "ratio": 4.07,
+ "ratio_stddev": 0.13
+ },
+ {
+ "name": "oxfmt",
+ "mean_mb": 118.6,
+ "min_mb": 118.6,
+ "max_mb": 118.7,
+ "ratio": 4.79,
+ "ratio_stddev": 0.11
+ },
+ {
+ "name": "tsv",
+ "mean_mb": 24.8,
+ "min_mb": 24.2,
+ "max_mb": 25.6
+ }
+ ]
+ },
+ {
+ "id": "typescript-only-tsv-fair",
+ "name": "TypeScript-only (tsv-fair)",
+ "target": "Outline repository (non-JSX JS/TS subset)",
+ "warmup_runs": 2,
+ "benchmark_runs": 5,
+ "preflight": [
+ {
+ "name": "prettier",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "biome",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "oxfmt",
+ "rejected": 0,
+ "unavailable": false
+ },
+ {
+ "name": "tsv",
+ "rejected": 0,
+ "unavailable": false
+ }
+ ],
+ "timings": [
+ {
+ "name": "prettier",
+ "mean_ms": 8250,
+ "stddev_ms": 118,
+ "user_ms": 13524,
+ "system_ms": 882,
+ "min_ms": 8121,
+ "max_ms": 8382
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "mean_ms": 6740,
+ "stddev_ms": 24,
+ "user_ms": 8533,
+ "system_ms": 560,
+ "min_ms": 6710,
+ "max_ms": 6768
+ },
+ {
+ "name": "biome",
+ "mean_ms": 361.6,
+ "stddev_ms": 1.4,
+ "user_ms": 2510.6,
+ "system_ms": 350.3,
+ "min_ms": 359.7,
+ "max_ms": 363.1
+ },
+ {
+ "name": "oxfmt",
+ "mean_ms": 278.2,
+ "stddev_ms": 12.2,
+ "user_ms": 867.6,
+ "system_ms": 433.7,
+ "min_ms": 260.9,
+ "max_ms": 293
+ },
+ {
+ "name": "tsv",
+ "mean_ms": 65.4,
+ "stddev_ms": 2.9,
+ "user_ms": 439.2,
+ "system_ms": 137.6,
+ "min_ms": 63.2,
+ "max_ms": 70.2
+ }
+ ],
+ "baseline": "tsv",
+ "speedups": [
+ {
+ "name": "oxfmt",
+ "ratio": 4.25,
+ "ratio_stddev": 0.26
+ },
+ {
+ "name": "biome",
+ "ratio": 5.53,
+ "ratio_stddev": 0.25
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "ratio": 102.99,
+ "ratio_stddev": 4.59
+ },
+ {
+ "name": "prettier",
+ "ratio": 126.06,
+ "ratio_stddev": 5.88
+ }
+ ],
+ "memory": [
+ {
+ "name": "prettier",
+ "mean_mb": 472.9,
+ "min_mb": 385.2,
+ "max_mb": 529.1,
+ "ratio": 10.01,
+ "ratio_stddev": 1.38
+ },
+ {
+ "name": "prettier+oxc-parser",
+ "mean_mb": 302.5,
+ "min_mb": 300.6,
+ "max_mb": 304.4,
+ "ratio": 6.4,
+ "ratio_stddev": 0.37
+ },
+ {
+ "name": "biome",
+ "mean_mb": 211.6,
+ "min_mb": 207.1,
+ "max_mb": 213.7,
+ "ratio": 4.48,
+ "ratio_stddev": 0.26
+ },
+ {
+ "name": "oxfmt",
+ "mean_mb": 229.5,
+ "min_mb": 215.2,
+ "max_mb": 243.2,
+ "ratio": 4.86,
+ "ratio_stddev": 0.37
+ },
+ {
+ "name": "tsv",
+ "mean_mb": 47.3,
+ "min_mb": 44.2,
+ "max_mb": 50.2
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/routes/docs/benchmarks/benchmarks_formatters.ts b/src/routes/docs/benchmarks/benchmarks_formatters.ts
new file mode 100644
index 0000000..0f0fa0d
--- /dev/null
+++ b/src/routes/docs/benchmarks/benchmarks_formatters.ts
@@ -0,0 +1,5 @@
+import type { FormatterBenchmarks } from './formatter_benchmark_data.ts';
+
+import json from './benchmarks_formatters.json' with { type: 'json' };
+
+export const benchmarks_formatters_json: FormatterBenchmarks = json as FormatterBenchmarks;
diff --git a/src/routes/docs/benchmarks/formatter_benchmark_data.ts b/src/routes/docs/benchmarks/formatter_benchmark_data.ts
new file mode 100644
index 0000000..ee23057
--- /dev/null
+++ b/src/routes/docs/benchmarks/formatter_benchmark_data.ts
@@ -0,0 +1,265 @@
+// Types and parser for the formatter comparison benchmark — Prettier, Biome,
+// Oxfmt, and tsv on shared corpora. Unlike the tsv bench reports, that harness
+// emits no JSON: its numbers exist only as the hyperfine console dump embedded
+// in its README between the `BENCHMARK_RESULTS_START`/`END` markers, plus a
+// `## Versions` list and a `_Measured on: …_` machine line. This module turns
+// that text into data; `benchmarks_formatters.gen.json.ts` writes it out.
+
+/** A single formatter's timing in one scenario. All durations in milliseconds. */
+export interface FormatterTiming {
+ name: string;
+ mean_ms: number;
+ stddev_ms: number;
+ min_ms: number;
+ max_ms: number;
+ user_ms: number;
+ system_ms: number;
+}
+
+/** A single formatter's peak-RSS measurement in one scenario. */
+export interface FormatterMemory {
+ name: string;
+ mean_mb: number;
+ min_mb: number;
+ max_mb: number;
+ /** Ratio to the scenario's lowest-memory formatter; absent on that baseline row. */
+ ratio?: number;
+ ratio_stddev?: number;
+}
+
+/** How a scenario's fastest formatter compares to one of the others. */
+export interface FormatterSpeedup {
+ name: string;
+ ratio: number;
+ ratio_stddev: number;
+}
+
+/**
+ * A formatter's parse-check result over the scenario's corpus. The harness runs
+ * this before timing so a tool that rejects files isn't credited for skipping
+ * them.
+ */
+export interface FormatterPreflight {
+ name: string;
+ /** Files the formatter refused to parse. */
+ rejected: number;
+ /** The formatter's binary never launched, so its timing row is meaningless. */
+ unavailable: boolean;
+}
+
+/** One benchmark scenario — a corpus benched across every formatter that supports it. */
+export interface FormatterScenario {
+ /** Slug derived from `name`, e.g. `large-single-file`. */
+ id: string;
+ name: string;
+ /** The corpus, as the harness describes it. */
+ target: string;
+ warmup_runs: number;
+ benchmark_runs: number;
+ preflight: Array;
+ timings: Array;
+ /** The fastest formatter and its margin over each other one. */
+ baseline: string;
+ speedups: Array;
+ memory: Array;
+}
+
+/**
+ * The parsed formatter comparison. Only scenarios tsv participates in are
+ * included — it has no JSX/TSX parser, so the harness runs it on the JSX-free
+ * corpora only and the other scenarios have no tsv row to compare against.
+ */
+export interface FormatterBenchmarks {
+ /**
+ * The machine the numbers came from. The ratios move with it — Biome, Oxfmt,
+ * and tsv scale across cores while Prettier is effectively serial.
+ */
+ machine: string;
+ /** Formatter name to version string, e.g. `prettier` to `3.9.1`. */
+ versions: Record;
+ scenarios: Array;
+}
+
+const to_slug = (value: string): string =>
+ value
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, '-')
+ .replace(/^-|-$/g, '');
+
+/** Milliseconds from a hyperfine duration and its unit, e.g. `1.597` + `s`. */
+const to_ms = (value: string, unit: string): number => {
+ const n = Number(value);
+ if (unit === 's') return n * 1000;
+ if (unit === 'ms') return n;
+ if (unit === 'µs' || unit === 'us') return n / 1000;
+ throw new Error(`unknown duration unit: ${unit}`);
+};
+
+// The console dump the harness writes into its README.
+const RESULTS_START = '';
+const RESULTS_END = '';
+
+const SCENARIO_RE = /^=+\nBenchmarking (.+)\n=+$/gm;
+const TARGET_RE = /^Target: (.+)$/m;
+const RUNS_RE = /^- (\d+) warmup runs?, (\d+) benchmark runs?$/m;
+const TIMING_RE =
+ /^Benchmark \d+: (.+)\n\s*Time \(mean ± σ\):\s*([\d.]+) (\S+) ±\s*([\d.]+) (\S+)\s*\[User: ([\d.]+) (\S+), System: ([\d.]+) (\S+)\]\n\s*Range \(min … max\):\s*([\d.]+) (\S+) …\s*([\d.]+) (\S+)/gm;
+const SPEEDUP_BASELINE_RE = /^Summary\n\s*(.+?) ran$/m;
+const SPEEDUP_RE = /^\s*([\d.]+) ± ([\d.]+) times faster than (.+)$/gm;
+const MEMORY_RE =
+ /^\s*(\S+): ([\d.]+) MB \(min: ([\d.]+) MB, max: ([\d.]+) MB(?:, ([\d.]+) ± ([\d.]+) times more than \S+)?\)$/gm;
+const PREFLIGHT_HEADING = 'Preflight (per-formatter parse check):';
+const PREFLIGHT_RE = /^\s{2}(\S+): (clean|unavailable|\d+ rejected)/gm;
+const VERSIONS_RE = /^## Versions\n\n((?:- \*\*.+\*\*: .+\n)+)/m;
+const VERSION_RE = /^- \*\*(.+?)\*\*: (.+)$/gm;
+const MACHINE_RE = /^_Measured on: (.+?)(?: — |_$)/m;
+
+/** The text of one section of a scenario block, between two of its headings. */
+const slice_section = (block: string, start: string, ...ends: Array): string => {
+ const from = block.indexOf(start);
+ if (from === -1) return '';
+ const rest = block.slice(from + start.length);
+ const to = ends.reduce((lowest, end) => {
+ const i = rest.indexOf(end);
+ return i === -1 || i >= lowest ? lowest : i;
+ }, rest.length);
+ return rest.slice(0, to);
+};
+
+const parse_timings = (block: string): Array =>
+ [...block.matchAll(TIMING_RE)].map((m) => ({
+ name: m[1]!.trim(),
+ mean_ms: to_ms(m[2]!, m[3]!),
+ stddev_ms: to_ms(m[4]!, m[5]!),
+ user_ms: to_ms(m[6]!, m[7]!),
+ system_ms: to_ms(m[8]!, m[9]!),
+ min_ms: to_ms(m[10]!, m[11]!),
+ max_ms: to_ms(m[12]!, m[13]!)
+ }));
+
+const parse_preflight = (block: string): Array =>
+ [...slice_section(block, PREFLIGHT_HEADING, 'Benchmark 1:').matchAll(PREFLIGHT_RE)].map((m) => ({
+ name: m[1]!,
+ rejected: Number.parseInt(m[2]!, 10) || 0,
+ unavailable: m[2] === 'unavailable'
+ }));
+
+const parse_memory = (block: string): Array =>
+ [...slice_section(block, 'Memory Usage:', ' benchmark complete!').matchAll(MEMORY_RE)].map(
+ (m) => ({
+ name: m[1]!,
+ mean_mb: Number(m[2]),
+ min_mb: Number(m[3]),
+ max_mb: Number(m[4]),
+ ...(m[5] === undefined ? null : { ratio: Number(m[5]), ratio_stddev: Number(m[6]) })
+ })
+ );
+
+const parse_scenario = (name: string, block: string): FormatterScenario => {
+ const runs = RUNS_RE.exec(block);
+ const summary = slice_section(block, 'Summary\n', 'Memory Usage:', ' benchmark complete!');
+ const timings = parse_timings(block);
+ // A scenario banner with no timings under it means the timing lines changed
+ // shape, not that the harness measured nothing — hyperfine always prints them.
+ if (timings.length === 0) {
+ throw new Error(`formatter benchmarks: scenario "${name}" has no parseable timings`);
+ }
+ // Distinguish "not measured" from "misparsed": these sections are optional (no
+ // GNU time, no preflight in the JSX scenarios), but a section that's present
+ // and yields no rows is a broken pattern.
+ const memory = parse_memory(block);
+ if (block.includes('Memory Usage:') && memory.length === 0) {
+ throw new Error(`formatter benchmarks: scenario "${name}" has an unparseable memory section`);
+ }
+ const preflight = parse_preflight(block);
+ if (block.includes(PREFLIGHT_HEADING) && preflight.length === 0) {
+ throw new Error(
+ `formatter benchmarks: scenario "${name}" has an unparseable preflight section`
+ );
+ }
+ return {
+ id: to_slug(name),
+ name,
+ target: TARGET_RE.exec(block)?.[1] ?? '',
+ warmup_runs: Number(runs?.[1] ?? 0),
+ benchmark_runs: Number(runs?.[2] ?? 0),
+ preflight,
+ timings,
+ baseline: SPEEDUP_BASELINE_RE.exec(block)?.[1] ?? '',
+ speedups: [...summary.matchAll(SPEEDUP_RE)].map((m) => ({
+ name: m[3]!.trim(),
+ ratio: Number(m[1]),
+ ratio_stddev: Number(m[2])
+ })),
+ memory
+ };
+};
+
+/**
+ * Parse the formatter comparison out of the bench harness's README, keeping only
+ * the scenarios tsv runs in.
+ *
+ * Throws rather than returning a partial result: the harness publishes no JSON,
+ * so this parses prose, and a drifted heading or timing line would otherwise
+ * strip scenarios from the site silently. The caller decides what a MISSING
+ * README means (the sibling checkout is optional); everything past that point is
+ * a broken contract.
+ *
+ * @param readme - the full README text, markers included
+ * @returns the parsed benchmarks
+ * @throws if the results markers, the scenario banners, the versions list, the
+ * machine line, or any tsv scenario's numbers can't be parsed
+ */
+export const parse_formatter_benchmarks = (readme: string): FormatterBenchmarks => {
+ const start = readme.indexOf(RESULTS_START);
+ const end = readme.indexOf(RESULTS_END);
+ if (start === -1 || end === -1 || end < start) {
+ throw new Error(
+ `formatter benchmarks: no ${RESULTS_START} / ${
+ RESULTS_END
+ } block — is this the harness's readme?`
+ );
+ }
+ const results = readme.slice(start + RESULTS_START.length, end);
+
+ // Split on the scenario banners, pairing each title with the text that follows
+ // it up to the next banner.
+ const headings = [...results.matchAll(SCENARIO_RE)];
+ if (headings.length === 0) {
+ throw new Error('formatter benchmarks: results block carries no scenario banners');
+ }
+ const parsed = headings.map(({ 0: heading, 1: name, index }, i) =>
+ parse_scenario(name!.trim(), results.slice(index + heading.length, headings[i + 1]?.index))
+ );
+ // tsv has no JSX/TSX parser, so it sits out some scenarios by design — but if it
+ // ran in NONE of them, either the harness stopped benching tsv or the timing
+ // labels drifted, and the site would render a comparison without its subject.
+ const scenarios = parsed.filter((scenario) =>
+ scenario.timings.some((timing) => timing.name === 'tsv')
+ );
+ if (scenarios.length === 0) {
+ throw new Error(
+ `formatter benchmarks: no scenario includes a tsv row (found ${parsed
+ .map((s) => s.id)
+ .join(', ')})`
+ );
+ }
+
+ const versions: Record = {};
+ const versions_block = VERSIONS_RE.exec(readme)?.[1] ?? '';
+ for (const m of versions_block.matchAll(VERSION_RE)) {
+ versions[m[1]!.toLowerCase()] = m[2]!.trim();
+ }
+ if (!versions.tsv) {
+ throw new Error("formatter benchmarks: no tsv version in the readme's `## Versions` list");
+ }
+
+ // The ratios move with the core count, so a report that can't say which machine
+ // produced it isn't publishable.
+ const machine = MACHINE_RE.exec(readme)?.[1]?.trim();
+ if (!machine) {
+ throw new Error('formatter benchmarks: no `_Measured on: …_` machine line');
+ }
+
+ return { machine, versions, scenarios };
+};
diff --git a/src/routes/docs/introduction/+page.svelte b/src/routes/docs/introduction/+page.svelte
index dfefa5f..62d8341 100644
--- a/src/routes/docs/introduction/+page.svelte
+++ b/src/routes/docs/introduction/+page.svelte
@@ -4,9 +4,9 @@
import TomeLink from '@fuzdev/fuz_ui/TomeLink.svelte';
import TomeSection from '@fuzdev/fuz_ui/TomeSection.svelte';
import TomeSectionHeader from '@fuzdev/fuz_ui/TomeSectionHeader.svelte';
- import {tome_get_by_slug} from '@fuzdev/fuz_ui/tome.ts';
+ import { tome_get_by_slug } from '@fuzdev/fuz_ui/tome.ts';
import Svg from '@fuzdev/fuz_ui/Svg.svelte';
- import {logo_tsv} from '@fuzdev/fuz_ui/logos.ts';
+ import { logo_tsv } from '@fuzdev/fuz_ui/logos.ts';
const LIBRARY_ITEM_NAME = 'introduction';
@@ -47,32 +47,29 @@ reconstruct_locations(ast, 'const x = 1;');`;
Compared to Oxc, Biome, and SWC, tsv is a set of focused tools, not a generic language
- platform, so the focus is web standards and there's no support for JSX/SCSS/etc, beyond Svelte
- as the only JS framework. The extensibility story is currently limited to using its Rust
- crates as libraries; bridging to JS or WASM plugins is an open question, but may not be
- supported.
+ platform, so the focus is Web standards + Svelte and there's no support for JSX/SCSS/etc. The
+ extensibility story is currently limited to using its Rust crates as libraries (or forking);
+ bridging to JS or WASM plugins is an open question (leaning against).
tsv prioritizes, in order:
correctness (Svelte and TypeScript conformance, spec adherence for HTML/CSS/JS)
speed
binary size and memory usage
-
extensibility (valued but deprioritized)
+
extensibility (valued but deprioritized), modularity, and reusability
- See the benchmarks for stats. Compared to Oxc
- and Biome, tsv is significantly faster, smaller, and uses less memory to parse and format its supported
- languages
+ See the for stats. Compared to Oxc and Biome, tsv is faster, smaller,
+ and uses less memory to parse and format its supported languages.
- This is an early release, and reports and feedback are appreciated - see the
+ This is an early release, and reports and feedback are appreciated — see the
issues and
discussions.
- AI disclosure: this codebase is mostly LLM-generated, and the usual caveats apply. The first
- release took 7 months and ~1800 manual commits. It's a high-effort project that prioritizes
- quality.
+ AI disclosure: this codebase is mostly LLM-generated, and the usual caveats apply. It's a
+ high-effort project that prioritizes quality.
These docs are a work in progress. For design details see the for size and performance details.
- Native builds are not yet available but are coming in v0.2, see
+ Native builds are not yet published — follow
issue 139.
@@ -137,11 +134,11 @@ reconstruct_locations(ast, 'const x = 1;');`;