tsv

introduction #

tsv is a toolchain for TypeScript/JS, CSS, and Svelte in Rust. The first release has a formatter that closely follows Prettier + prettier-plugin-svelte, and a drop-in replacement for Svelte's parser + acorn + acorn-typescript.

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.

tsv prioritizes, in order:

  1. correctness (Svelte and TypeScript conformance, spec adherence for HTML/CSS/JS)
  2. speed
  3. binary size and memory usage
  4. extensibility (valued but deprioritized)

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

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.

These docs are a work in progress. For design details see the readme.

Install
#

tsv ships as WASM packages on npm, a CLI with a formatter and parser:

npm i -D @fuzdev/tsv_wasm npx tsv format src npx tsv parse src/foo.svelte

For smaller builds, the formatter and parser also ship solo:

npm i -D @fuzdev/tsv_format_wasm npm i -D @fuzdev/tsv_parse_wasm

See the benchmarks for size and performance details.

Native builds are not yet available but are coming in v0.2, see issue 139.

Usage
#

All three packages share the same API. The full package exports both halves:

import {format_svelte, parse_svelte, type Root} from '@fuzdev/tsv_wasm'; const formatted = format_svelte('<script>\nconst x=1\n<\/script>'); const ast: Root = parse_svelte('<script>const x = 1;<\/script>');

The formatter alone:

import {format_svelte} from '@fuzdev/tsv_format_wasm'; const formatted = format_svelte('<script>\nconst x=1\n<\/script>');

The parser alone:

import {parse_svelte, type Root} from '@fuzdev/tsv_parse_wasm'; const ast: Root = parse_svelte('<script>const x = 1;<\/script>');

format_typescript, format_css, parse_typescript, and parse_css work the same way, and the parsers return Svelte-compatible JSON ASTs with bundled TS types. Everything works zero-config in Node.js, Bun, and Deno (sync auto-init); browsers and bundlers call await init() once first.

Span-only parsing
#

For TypeScript and Svelte, the parsers also emit a span-only AST that drops the per-node loc (line/column) object — Svelte also drops name_loc — for a ~46% smaller, faster-to-materialize result, mirroring acorn's locations: false. Line and column stay derivable from the start/end offsets plus your source, so nothing is lost when you have the source.

import {parse_typescript_no_locations, reconstruct_locations} from '@fuzdev/tsv_parse_wasm'; // span-only AST: start/end offsets, no per-node loc (~46% smaller) const ast = parse_typescript_no_locations('const x = 1;'); // derive line/column back when you need it, no re-parse reconstruct_locations(ast, 'const x = 1;');

reconstruct_locations(ast, source) walks the tree and adds loc back, mutating in place — exact for TypeScript, approximate for Svelte (it skips the parser's own name_loc and a couple of position quirks). For sparse lookups, create_locator(source) reuses one line table across calls. CSS has no loc, so there's no span-only variant for it.

Source code
#

playground #

Try tsv's formatter and parser live in the playground.

benchmarks #

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 native tsv native json tsv wasm tsv wasm json biome oxc canonical

Binary size
#

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:

wasm

tsv_parse_wasm
1.6 MB 514 KB gz 0.6x
oxc-parser (wasm)
1.8 MB 507 KB gz 0.6x
tsv_format_wasm
2.1 MB 699 KB gz 0.8x
tsv_wasm
2.8 MB 890 KB gz 1.0x
biome (wasm)
32.8 MB 7.8 MB gz 11.9x

native

oxc-parser (native)
2.5 MB 1009 KB gz 0.7x
tsv (native)
3.7 MB 1.5 MB gz 1.0x
oxfmt (native)
7.6 MB 3.1 MB gz 2.1x

Speedier than Prettier
#

tsv is heavily inspired by and borrows architectural patterns from Prettier. We're very grateful for the hard work of its contributors. tsv offers a speedup over Prettier:

Svelte
1194 of 1240 files
TypeScript
3701 of 4025 files
CSS
165 of 193 files
native12.4x10.3x17.3x
wasm9.06x7.66x12.9x

Format
#

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):

Formatting 1194 of 1240 svelte files files handled / total  ·  speed

prettier
3,156 ms 1201/1240 1.0x
oxfmt
3,363 ms 1201/1240 0.94x
biome wasm
652 ms 1238/1240 4.84x
tsv_wasm
348 ms 1202/1240 9.06x
tsv
254 ms 1202/1240 12.4x

Formatting 3701 of 4025 typescript files files handled / total  ·  speed

prettier
8,774 ms 3855/4025 1.0x
biome wasm
3,651 ms 4025/4025 2.40x
tsv_wasm
1,145 ms 3743/4025 7.66x
tsv
855 ms 3743/4025 10.3x
oxfmt
802 ms 3836/4025 10.9x

Formatting 165 of 193 css files files handled / total  ·  speed

prettier
731 ms 192/193 1.0x
oxfmt
760 ms 192/193 0.96x
biome wasm
207 ms 193/193 3.53x
tsv_wasm
57 ms 165/193 12.9x
tsv
42 ms 165/193 17.3x

Parse
#

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.

Parsing 1192 of 1240 svelte files files handled / total  ·  speed

svelte/compiler
354 ms 1193/1240 1.0x
tsv_wasm json
516 ms 1202/1240 0.69x
tsv json
485 ms 1202/1240 0.73x
tsv_wasm internal
45 ms 1202/1240 7.81x
tsv internal
37 ms 1202/1240 9.55x

Parsing 3603 of 4025 typescript files files handled / total  ·  speed

acorn-typescript
2,213 ms 3693/4025 1.0x
tsv_wasm json
2,224 ms 3743/4025 1.00x
tsv json
2,050 ms 3743/4025 1.08x
oxc-parser wasm
977 ms 4025/4025 2.27x
oxc-parser
865 ms 3833/4025 2.56x
tsv_wasm internal
293 ms 3743/4025 7.56x
tsv internal
236 ms 3743/4025 9.37x

Parsing 159 of 193 css files files handled / total  ·  speed

svelte/compiler
19 ms 163/193 1.0x
tsv_wasm json
223 ms 165/193 0.09x
tsv json
177 ms 165/193 0.11x
tsv_wasm internal
32 ms 165/193 0.60x
tsv internal
23 ms 165/193 0.84x

Benchmarking details
#

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.

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).

corpus

  • svelte: 1240 files
  • typescript: 4025 files
  • css: 193 files

versions

  • svelte 5.56.1
  • acorn 8.16.0
  • acorn-typescript 1.0.10
  • prettier 3.8.3
  • prettier-plugin-svelte 3.5.2
  • oxc-parser 0.134.0
  • oxfmt 0.53.0
  • biome 2.4.16

run