A Rust port of the official Svelte 5 compiler — and the ecosystem around it — built to slot natively into the OXC toolchain.
Website · Playground · Benchmarks · Compatibility
⚠️ Early stage — rsvelte passes 100% of the in-scope fixtures in the official Svelte 5 test suite, but it's pre-1.0: APIs and behaviour may change without notice. Use in production at your own risk.
The native JS toolchain growing around OXC — oxlint, oxfmt, Rolldown, tsgo — can only see .js / .ts / .jsx / .tsx. .svelte files are invisible to it, because parsing Svelte means running the JavaScript-based Svelte compiler, which native tools can't link against. Svelte developers are locked out of the order-of-magnitude speed-ups the rest of the ecosystem is starting to take for granted.
rsvelte fixes that at the source: it ports the compiler — and the ecosystem hot paths around it (svelte2tsx, svelte-check, vite-plugin-svelte, formatting) — to Rust on top of OXC's parser, codegen, and semantic stack. The end goal is upstream integration, so oxlint can lint .svelte, oxfmt can format it, Rolldown can bundle it, and tsgo can type-check it — all without a JS compiler hop.
Until then, the @rsvelte/* packages let you use rsvelte today: the compiler, the Vite plugin, svelte-check, and svelte2tsx are drop-in replacements for their JS counterparts, verified byte-for-byte on every release. @rsvelte/fmt and @rsvelte/lint target output/behaviour parity as fast complements rather than configuration-compatible replacements, and @rsvelte/language-server currently covers formatting and lint diagnostics only — see Packages for what each one does and doesn't cover.
The plugin is a fork of @sveltejs/vite-plugin-svelte with the same public API — only the compiler underneath changes.
npm install -D @rsvelte/vite-plugin-svelte// vite.config.js
import { svelte } from '@rsvelte/vite-plugin-svelte';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [svelte()],
});SvelteKit pulls in @sveltejs/vite-plugin-svelte internally, so redirect it with a package-manager override — no config changes needed:
@rsvelte/svelte-check is a CLI-compatible replacement for svelte-check: a Rust walker generates a TSX overlay per component and hands it to tsc (or Microsoft's native tsgo with --tsgo), then maps diagnostics back to exact .svelte positions.
npm install -D @rsvelte/svelte-check
npx rsvelte-check # Svelte + TypeScript diagnostics
npx rsvelte-check --tsgo # prefer tsgo over tsc (faster)
npx rsvelte-check --watch --incremental
npx rsvelte-check --no-type-check # Svelte diagnostics onlyEvery upstream flag is accepted (--output, --fail-on-warnings, --compiler-warnings, --threshold, --no-tsconfig, --config, --preserveWatchOutput, …), plus rsvelte-specific ones — see the upstream flag compatibility table or npx rsvelte-check --help.
One Rust CLI that formats .svelte in-process and routes .js / .ts / .css / .json to oxfmt (an optional peer dependency), both in parallel:
npm install -D @rsvelte/fmt oxfmt
npx rsvelte-fmt # format the current directory in place (no path = cwd)
npx rsvelte-fmt src/ # format a specific path in place
npx rsvelte-fmt --check # CI gate: exit 1 if anything would changeSee @rsvelte/fmt for all flags, stdin/editor integration, and configuration.
A native Svelte linter that surfaces the compiler's own validator/a11y diagnostics plus a Rust port of eslint-plugin-svelte's rules, designed to run alongside ESLint rather than replace it:
npm install -D @rsvelte/lint
npx rsvelte-lint src/ # lint a directory
npx rsvelte-lint --fix src/ # autofix in placeSee @rsvelte/lint for configuration, ESLint config import (--config-from-eslint), and CI output formats (--format sarif, --format github-actions).
Already on oxlint? The same diagnostics can ride along in oxlint's own pass, so .svelte files stop being a blind spot in a single-linter setup:
npm install -D @rsvelte/oxlint-plugin oxlint{
"jsPlugins": ["@rsvelte/oxlint-plugin"],
"extends": ["./node_modules/@rsvelte/oxlint-plugin/recommended.json"]
}Rules land under svelte/* ids, so oxlint config controls their severity like any other rule. See @rsvelte/oxlint-plugin for the current limits of oxlint's .svelte support — script-less components and markup diagnostic positions are where the standalone CLI is still more faithful.
@rsvelte/compiler ships the compiler as WebAssembly — it runs anywhere Node or a browser does:
import init, { compile_client, compile_server, parse_svelte } from '@rsvelte/compiler';
await init(); // initialise the wasm module once
const { js, css } = compile_client('<h1>Hello {name}</h1>', 'App');
const ast = JSON.parse(parse_svelte('<h1>Hello</h1>').ast);Need the exact svelte/compiler surface (compile, compileModule, parse, preprocess, VERSION) at native speed? That's @rsvelte/vite-plugin-svelte-native, the NAPI binding the Vite plugin runs on. One caveat: function-valued options can't cross the language boundary — see Compiler option compatibility.
[dependencies]
rsvelte_core = { git = "https://github.com/baseballyama/rsvelte" }use rsvelte_core::{compile, CompileOptions};
let result = compile("<h1>Hello, {name}!</h1>", CompileOptions::default()).unwrap();
println!("{}", result.js.code);The Rust API honours every compile option, including css_hash and warning_filter as real closures.
For everything else there's a stable C ABI (crates/rsvelte_capi): one shared library + one header, JSON in / JSON out, with prebuilt binaries on GitHub Releases (capi-vX.Y.Z tags) and ready-to-run examples for C, Go, Python, Ruby, PHP, Zig, and Java.
All npm packages ship under the @rsvelte scope.
| Package | Compares to |
|---|---|
@rsvelte/vite-plugin-svelte |
@sveltejs/vite-plugin-svelte — drop-in fork, same public API |
@rsvelte/svelte-check |
svelte-check CLI — drop-in replacement |
@rsvelte/fmt |
prettier + prettier-plugin-svelte — targets output parity, not a configuration-compatible drop-in (reads .oxfmtrc, not .prettierrc; no Tailwind class sorting) |
@rsvelte/lint |
eslint + eslint-plugin-svelte — a complement designed to run alongside ESLint today, not yet a replacement |
@rsvelte/oxlint-plugin |
the same rules as an oxlint plugin — bounded by oxlint's alpha .svelte support (no script-less components; markup diagnostics anchor at the script head), so @rsvelte/lint stays the faithful path |
@rsvelte/svelte2tsx |
svelte2tsx — drop-in replacement |
@rsvelte/compiler |
svelte/compiler, as WebAssembly — drop-in replacement |
@rsvelte/vite-plugin-svelte-native |
svelte/compiler, as a native NAPI binding — drop-in replacement |
@rsvelte/language-server |
svelte-language-server — formatting + lint diagnostics only; no hover, completion, definition, rename, references, or TypeScript diagnostics (waits on tsgo's tsserver mode; use @rsvelte/svelte-check for type-checking) |
rsvelte-vscode |
The rsvelte VS Code extension (Marketplace) |
Multi-threaded rsvelte vs. the official JavaScript tool, same machine, same corpus (3,404 real .svelte files; Apple M4 Pro, 12-core; 10 iterations after 3 warmup):
| Task | JS baseline | Rust (1 thread) | Rust (multi) | Multi vs JS |
|---|---|---|---|---|
| Compile — client (full pipeline) | 519.5 ms | 187.6 ms | 25.5 ms | 20.4× |
| Compile — server (SSR) | 451.5 ms | 106.3 ms | 15.7 ms | 28.8× |
| Parse only | 127.2 ms | 7.3 ms | 1.7 ms | 75.7× |
svelte2tsx |
206.0 ms | 76.3 ms | 11.4 ms | 18.1× |
| Format (vs prettier-plugin-svelte) | 2,320.6 ms | 99.5 ms | 23.0 ms | 101.0× |
svelte-check (500-file workspace) |
828.5 ms | 44.7 ms | 14.7 ms | 56.5× |
The corpus is Svelte's own test suite, restricted to the 3,404 of 3,857 files the official compiler
accepts under the benchmark's options — otherwise the numbers would partly measure how fast each
compiler throws. Of the 453 excluded, 286 are valid sources that merely need experimental.async
(which the benchmark does not enable) and 167 are deliberately invalid error-case fixtures.
Because the corpus is Svelte's test suite, files are small (~236 bytes on average) and the numbers are dominated by per-file fixed costs rather than throughput on realistic components.
Live numbers, charts, and reproduction steps: benchmark page, or pnpm run generate-benchmark locally. A single-threaded 100× compile speedup remains an explicit goal — current numbers are a snapshot, not a ceiling.
Targeting Svelte v5.56.7 (b29d7002ecf9) — automatically maintained by pnpm run update-docs.
rsvelte passes 100% of the in-scope fixtures of the official Svelte compiler test suite — over 3,500 fixtures across parser, snapshot, CSS, validator, compiler errors, runtime (runes + legacy), hydration, SSR, preprocess, print, and svelte2tsx. The per-suite breakdown is on the live compatibility dashboard; regenerate locally with pnpm run test-and-update.
What "in-scope" excludes:
migrate(76 fixtures) — the Svelte 4 → 5 migrator is intentionally out of scope; rsvelte ports the Svelte 5 compiler, not the migration tool.- A handful of individually skipped fixtures — most notably
javascript-comments(acorn vs OXC comment attachment; legacy AST only, no output impact),error-mode-warn(skipped via the fixture's_config.js), and two fixtures pending small upstream ports (async-in-derived,css-keyframes-percent). The dashboard lists every skip with its reason.
On top of the fixture suite, a continuously growing output-equality corpus compiles ~12,000 units of real Svelte source — every .svelte / .svelte.(js|ts) file and markdown code block from 32 pinned repositories, including bits-ui, shadcn-svelte, melt-ui, and flowbite-svelte — with both the official tool and rsvelte, and asserts the outputs match:
| Track | Compared against | Known divergences |
|---|---|---|
| Compiler (CSR + SSR) | svelte/compiler |
8 client / 0 server (~99.9% parity) |
svelte2tsx |
official svelte2tsx |
0 |
| Formatter | oxfmt + prettier-plugin-svelte, byte-for-byte |
46 |
| Linter | eslint-plugin-svelte (compared rules) |
102 |
Each count is a CI ratchet: the baselines in compat/ may only shrink, so a new divergence turns CI red and parity can only improve. Normalization (formatting, blank lines) runs on the comparison side only — never inside the compiler — so real differences can't hide. Details: scripts/compat-corpus/README.md.
The drop-in JS surface (@rsvelte/vite-plugin-svelte-native, which the Vite plugin uses) and the C ABI accept the full svelte/compiler options shape, but function-valued options can't cross the language boundary — they are accepted (keeping the TypeScript types drop-in) and silently ignored:
| Option | Workaround |
|---|---|
cssHash(...) => string |
Falls back to upstream's default svelte-<hash> scheme. To force a specific value, pass the rsvelte-specific cssHashOverride: '<hash>'. |
warningFilter(warning) => boolean |
All warnings are returned; filter result.warnings yourself. |
Every other option matches upstream exactly. The Rust API has no such restriction, and the wasm @rsvelte/compiler is unaffected — it exposes a smaller fixed surface with no options object at all.
The directory layout mirrors the official compiler at submodules/svelte/packages/svelte/src/compiler/:
crates/rsvelte_core/src/compiler/phases/
├── 1_parse/ # Svelte syntax → AST
├── 2_analyze/ # scope tree, bindings, rune detection
└── 3_transform/ # AST → JS/CSS (client + SSR)
JavaScript parsing, semantic analysis, and codegen all run on OXC — the same crates oxlint and oxfmt use — with a memory-efficient AST (u32 spans, compact_str, arena allocation) and rayon parallelism across files.
git submodule update --init --recursive
git config core.hooksPath .githooks # cargo fmt/clippy pre-commit
pnpm install
pnpm run generate-fixtures # required before tests
cargo testSee CONTRIBUTING.md for the test-suite anatomy, how to run and debug a single fixture, and PR conventions.
MIT
