a formatter, parser, and future linter + more for Svelte, TypeScript, and CSS - tsv.fuz.dev
tsv is a toolchain for Svelte, TypeScript, and CSS, written in Rust. The first release has a near-Prettier formatter, similar to prettier-plugin-svelte, and a drop-in replacement for Svelte's parser + acorn + acorn-typescript.
For benchmarks including performance and binary size, visit tsv.fuz.dev.
This is an early release, and reports and feedback are appreciated - see the issues and discussions.
AI disclosure: this codebase was generated with machine agents and the usual LLM caveats apply. The first release took 7 months and ~1800 manual commits. It's a high-effort project that emphasizes quality.
tsv ships three WASM packages to npm (native builds will arrive with the v0.2 release):
@fuzdev/tsv_wasm- the full tool (formatter + parser) with atsvCLI@fuzdev/tsv_format_wasm- formatter only (smaller)@fuzdev/tsv_parse_wasm- parser + JSON AST only (smallest)
npm i @fuzdev/tsv_wasm # or pick a subset: @fuzdev/tsv_format_wasm / @fuzdev/tsv_parse_wasm
npx tsv format src # if installed locally
npx @fuzdev/tsv_wasm format src # try the formatter without installingimport {format_svelte} from '@fuzdev/tsv_format_wasm';
const formatted = format_svelte('<script>\nconst x=1\n</script>');import {parse_svelte, type Root} from '@fuzdev/tsv_parse_wasm';
const ast: Root = parse_svelte('<script>const x = 1;</script>');Both halves import the same way from @fuzdev/tsv_wasm.
Works without setup in Node.js/Bun/Deno (sync auto-init);
browsers and bundlers must call await init() once first.
See the package READMEs for the full API and CLI flags:
There are no prebuilt native binaries yet - the npm packages are all WASM.
For native speed today, build the C FFI library from source
(deno task build:ffi, producing target/release/libtsv_ffi.so/.dylib/.dll) and bind it
from anything that speaks C FFI. Deno's FFI is used in the benchmarks.
- supports Svelte, TypeScript/JS, CSS (and planned HTML/JSON)
- formatting is similar Prettier and prettier-plugin-svelte for the common case, but intentionally diverges in some cases and fixes numerous bugs (see docs/conformance_prettier.md)
- tsv can generate a public JSON AST that should exactly match Svelte 5's modern AST with acorn and acorn-typescript (see docs/conformance_svelte.md), but has its own internal optimal AST
- non-configurable: formatter settings are fixed at Prettier's defaults except
printWidth: 100,useTabs: true,singleQuote: true, andtrailingComma: 'none'(no trailing comma on multiline lists, matching the Svelte project's own Prettier config), and there are no config files or CLI options for formatting style; tsv is opinionated likegofmtand Python's Black, see CLAUDE.md § Configuration tsv formatdiscovery is gitignore-aware, honoring.gitignoreand.formatignorehierarchically (nested files like git, unlike Prettier; also.formatignoreis original to tsv) plus a compatible.prettierignore(but relative to repo root if available, not cwd like Prettier's default) (gitignore syntax)- Rust-only implementation that never embeds or calls a JS runtime, for performance; JS reaches tsv through the WASM bindings, and native N-API bindings are not yet published
- outputs optimal artifacts as an invariant, not a preference: runtime speed and compiled code size are first-class goals for every shipped artifact, and heavier future layers (incremental parsing, CST for LSP) will be feature-gated so they don't regress the artifacts that exist today
- JS and TS always parse as modules in strict mode - sloppy-mode-only syntax
like
withis rejected, while strict-mode early errors (e.g. duplicate params, reserved-word bindings) still parse for now, with enforcement deferred to a future diagnostics layer; Svelte and TypeScript are inherently strict, so this only matters for standalone JS scripts - pushes complexity and mess to the printer and JSON conversion, out of the parser and internal AST, keeping the model clean for the other planned tools
Each language is a self-contained Rust crate exposing the same
parse/format/convert_ast functions over its own concrete types - there's no
central Language trait, registry, or enum dispatch ("closed scope, open convention").
That means no dynamic dispatch, and WASM builds tree-shake at the link level:
the parse build excludes the printers, and the format build excludes the JSON-AST conversion layer.
Languages tree-shake the same way - a build binding only TypeScript would exclude
Svelte and CSS entirely - though the published packages include all three.
Future LSP/incremental features will be later feature-gated layers that don't bloat
these artifacts - see docs/architecture.md
tsv's goal is to be an optimal toolchain for Svelte and TypeScript, and avoiding bloat is a key characteristic. The scope trade is depth over breadth: a closed language set with an expanding tool set, instead of more frameworks. Hard non-goals:
- other frameworks' markup - no Vue, Astro, JSX/TSX, etc (unlike Biome and friends);
Svelte is the only component language (it's in the name
tsv) - CSS preprocessor and vendor dialects - no SCSS, LESS, CSS Modules, or IE hacks; tsv parses standard and Svelte CSS only
- JS plugins - follows from never embedding a JS runtime; linter extensibility, if any, will be WASM plugins and/or pattern-based rules
- no style config settings, so on-disk state and caller params never change the output for a given input
- full Prettier conformance - see discussion 1
Deferred rather than refused:
- internal AST stabilization - not any time soon; the public JSON AST is the stable surface, tracking Svelte's
- N-API native bindings - npm is WASM-only for now
tsv is derived from:
- Svelte
- TypeScript
- Prettier and prettier-plugin-svelte
- HTML/CSS/JS
tsv currently supports:
- formatter matching Prettier + prettier-plugin-svelte (with intentional divergences)
- parser, drop-in for Svelte+acorn+acorn-typescript
Future features (unknown order):
- CSS error recovery (recover past invalid CSS per the spec instead of failing the parse - doesn't add dialect support)
- type stripper (easy, probably soon)
- module lexer (easy, probably soon)
- minifier
- LSP
- later
- linter (type aware, all Rust, maybe WASM plugins and/or pattern-based rules for extensibility)
- Svelte compiler (exact mirror)
- bundler
- typechecker isn't off the table
- more? see the issues and discussions, suggestions welcome
- CLAUDE.md - development guide (commands, structure, conventions)
- docs/architecture.md - the major design decisions
- docs/directives.md -
format-ignore/prettier-ignoreformatting directives - docs/cli.md - commands and design
- docs/conformance_prettier.md - where formatting diverges from Prettier (and why)
- docs/conformance_svelte.md - where the parser diverges from Svelte (and why)
- docs/conformance_test262.md - ECMAScript parser conformance
- docs/fixture_overview.md - fixture system design
- docs/fixture_workflow.md - step-by-step fixture creation
- docs/fixture_naming.md - fixture naming conventions and patterns
Dev dependencies:
- Rust - rustc, cargo
- Deno - see deno.json for the tasks
- uses
npm:imports fromsvelte,typescript,acorn,@sveltejs/acorn-typescript,prettier,prettier-plugin-svelte
- uses
Rust dependencies are kept fairly minimal. See CLAUDE.md § Rust Crates for the full list.
# Build workspace (recommended - uses deno tasks)
deno task build # dev build
deno task dev # watch mode (requires: cargo install cargo-watch)
deno task check # all checks (typecheck, test, lint, fmt)
# Or build directly with cargo
cargo build --workspace
cargo check --workspace # fast syntax check (no codegen)
cargo test --workspace # all tests including fixture validation
# Run CLI
cargo run -p tsv_cli parse --content "const x = 1;" --parser typescript
cargo run -p tsv_cli format --content "<div>test</div>" --parser svelteFor the full reference see CLAUDE.md § Commands.
Multi-crate workspace with clean separation of concerns:
tsv/
├── Cargo.toml # workspace root
├── crates/
│ ├── tsv_lang/ # foundation (Span, Location, ParseError)
│ ├── tsv_html/ # HTML classification and whitespace rules
│ ├── tsv_ignore/ # gitignore-aware discovery matcher (.gitignore/.formatignore/.prettierignore)
│ ├── tsv_discover/ # file-discovery policy (build-output heuristic + safety nets) over tsv_ignore
│ ├── tsv_ts/ # TypeScript parser/formatter (standalone)
│ ├── tsv_css/ # CSS parser/formatter (standalone)
│ ├── tsv_svelte/ # Svelte parser/formatter (uses tsv_ts + tsv_css)
│ ├── tsv_cli/ # unified CLI (binary: `tsv`)
│ ├── tsv_debug/ # dev utilities (binary: `tsv_debug`, uses Deno)
│ ├── tsv_ffi/ # C FFI bindings
│ └── tsv_wasm/ # WebAssembly bindings
└── tests/ # workspace-level integration tests
Each language crate exports a consistent API:
parse(source) -> Result<AST>format(ast, source) -> Stringconvert_ast(ast, source) -> PublicAST(default-onconvertcargo feature; turn off for parse+format-only builds)
See CLAUDE.md for detailed structure and full command reference.
tsv is an implementation of various software designs and specs:
Software:
- Svelte
- Prettier which was forked from recast's printer, which is based on the algorithms described in A prettier printer by Philip Wadler
- TypeScript
Web Standards:
- WHATWG HTML - HTML Living Standard
- WHATWG DOM - DOM Living Standard
- W3C CSS Working Group - CSS specifications
- TC39 ECMAScript - JS language specification
- TC39 test262 - ECMAScript conformance tests
- W3C webref - Machine-readable web specs
Claude Code was instrumental to this project, and tsv wouldn't exist without LLMs. Source code of projects similar to tsv was not used by agents or consulted by the author unless listed above.
The code for the following projects was sometimes read by AI agents while producing tsv, so their license information is included for completeness:
Svelte Copyright (c) 2016-2026 Svelte Contributors MIT - https://github.com/sveltejs/svelte/blob/main/LICENSE.md
Prettier Copyright © James Long and contributors MIT - https://github.com/prettier/prettier/blob/main/LICENSE