npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scalar/rust-fmt

v0.2.0

Published

Rust formatter (rustfmt on wasm) - runs on plain Node, no Rust toolchain

Readme

Scalar Rust Formatter

Version Downloads License Discord

Rust formatter that runs on plain Node. No Rust toolchain, no cargo on PATH, no rustup, no postinstall download.


Scalar is an open-source API platform for teams who want beautiful developer interfaces without vendor lock-in.

  • API References — Interactive API documentation from OpenAPI and AsyncAPI specs.
  • Developer Docs — Write in Markdown/MDX, generate API references, sync with two-way Git.
  • SDK Generator — Type-safe SDKs and CLIs in TypeScript, Python, Go, PHP, Java, and Ruby.
  • API Client — Open-source, offline-first Postman alternative built on OpenAPI.

20M+ monthly npm installs · 15,500+ GitHub stars · MIT licensed · scalar.com


npm i @scalar/rust-fmt
import { format } from '@scalar/rust-fmt'

await format('pub fn add(a: i32,b:i32)->i32{a+b}')
// pub fn add(a: i32, b: i32) -> i32 {
//     a + b
// }

Async because the first call decompresses the artifact, compiles it and boots the module — about 150ms. That work is cached, so every later call is a few milliseconds.

Options are rustfmt's own configuration keys, in camelCase — { maxWidth }, { tabSpaces }, { hardTabs }, { styleEdition }, and the rest. Anything you leave out keeps rustfmt's default, because they are applied through rustfmt's own Config::override_value, the same code path as rustfmt --config.

await format(source, { maxWidth: 120, tabSpaces: 2 })
await format(source, { styleEdition: '2024', edition: '2021' })
await format(source, { config: { fn_call_width: '80' } }) // anything not named above

Formatting without awaiting

formatSync is for callers with no await to give — a code generator that formats each file inside the synchronous builder that emits it, a template renderer, a plugin hook that has to return a string.

import { formatSync, init } from '@scalar/rust-fmt'

await init()
const formatted = formatSync(source)

Booting is the one thing that cannot be made synchronous, so init covers it once and formatSync throws until it has. Everything after that already was synchronous — format was only ever awaiting the boot, and both produce the same bytes.

Prefer format where you can await: it needs no setup call and cannot throw that error.

It runs in the browser too

The import does not change — bundlers and browsers pick the browser export condition on their own, and format has the same signature and returns the same bytes. Only the wasm's route in differs: fetched rather than read from disk.

import { format, init } from '@scalar/rust-fmt'

// Optional. The artifact resolves next to the module by default, which Vite,
// Rollup, webpack and a plain CDN handle unaided. esbuild does not rewrite
// `new URL(..., import.meta.url)`, so there it needs naming.
await init({ url: '/assets/rust_fmt.wasm.br' })

await format(source)

Run it in a worker. Booting compiles 6.2 MB of wasm, which is a visibly frozen tab if it happens on the main thread.

The browser reads the same brotli artifact as Node (1.3 MB over the wire) and expands it with DecompressionStream('brotli') where the engine has it, or a 208 KB wasm decoder where it does not — Chrome, today. Serving the artifact with Content-Encoding: br, or serving an uncompressed .wasm, skips the decoder entirely:

await init({ url: '/assets/rust_fmt.wasm', encoding: 'none' })

This is the real rustfmt, and the output is exact

This is actual rustfmt, the same build that ships with the pinned nightly, compiled to WebAssembly. It is not a reimplementation, so it does not drift.

test/native-conformance.test.ts asserts byte-identical output against a native rustfmt, across functions, structs, traits, generics, lifetimes, closures, macros, async, doc comments and unicode, under four configurations — and compares refusals as well as successes, because agreeing about what is not formattable is part of being the same tool. That test asserts rather than reports: any divergence is a real bug.

Beyond those samples, the build was checked against rustfmt's own test corpus — all 345 files in tests/source — formatted by both the wasm build and the native CLI. All 345 came out byte-identical, with matching exit status on the six that rustfmt refuses.

The conformance test only runs against the exact rustfmt the artifact was compiled from, read from the pin in build/rust_fmt/build.sh. rustfmt's output changes between versions, so a rustfmt from another toolchain is skipped rather than compared — comparing it would fail for the wrong reason. To run it:

RUSTFMT=$(rustup which --toolchain nightly-2026-07-19 rustfmt) bun test packages/rust

The one behaviour left out is configuration discovery

The CLI walks up the filesystem looking for a rustfmt.toml. There is no filesystem here to walk, so if your project has one, read it and pass it in:

import { readFileSync } from 'node:fs'
import { parse } from 'smol-toml' // or any TOML parser

await format(source, { config: parse(readFileSync('rustfmt.toml', 'utf8')) })

Worth knowing if you compare this package against your project's CLI output and see a difference: check that both are using the same configuration, and the same rustfmt version, before concluding anything. A rustfmt.toml the CLI silently picked up is by far the likeliest explanation.

Note also that rustfmt's default edition is 2015, in the package exactly as on the CLI. Modern syntax needs { edition: '2021' } — without it, async fn is a parse error in both.

How it is built, and why that was the hard part

It ships as a 1.3MB rust_fmt.wasm.br — 6.2MB of wasm, brotli-compressed — built by build/rust_fmt/build.sh. It is committed, so a fresh clone needs nothing extra; bun run rust:build rebuilds it. That makes it the smallest artifact in this repo, which is not what anyone expects from a package containing a Rust parser.

rustfmt is not a normal crate. It opens with #![feature(rustc_private)] and links eight compiler crates — rustc_parse, rustc_expand, rustc_ast, rustc_span and friends — from the sysroot. Those ship in rustup's rustc-dev component, which is published for 34 host targets and no wasm target at all. The usual conclusion is that this cannot be done.

It can, because those crates are ordinary Rust that wants bootstrap's environment, not bootstrap. Given CFG_RELEASE and five siblings, plain cargo cross-compiles them to wasm32-wasip1 with zero errors. rustfmt itself needs three lines of #[cfg], dropping a rustc_driver link that exists only because rustc-dev ships rmeta-only — its own comment says so, and we compile real rlibs.

build/rust_fmt/SPIKE.md is the full account, including the parts that took longest to find: rustfmt has to be built as a member of the rust-lang/rust workspace or its dependency versions collide, and the compiler crates have to be injected through an RUSTC_WRAPPER because RUSTFLAGS breaks cargo's --print probe.

The wasm stack is the one real limitation, and the build closes it. wasm cannot grow its stack the way stacker does natively, and rustfmt's parser is deeply recursive: at the default 1MB, expressions nested past ~256 levels trap with memory access out of bounds, while native rustfmt handles 1024. The build links with -z stack-size=33554432, and at 32MB the wasm build matches native at every depth native itself survives — beyond that, native aborts and this exits cleanly. It costs nothing in artifact size; the reservation is linear memory, not code.

The module is a reactor, and it does not leak

The artifact is a WASI reactor: it is instantiated once and its run export is called per format, rather than being re-instantiated for each one. That matters more here than elsewhere — the whole 345-file corpus runs through a single instance in 530ms, where instantiating per file costs more than the formatting does.

Linear memory plateaus at about 35MB and stays there across the corpus, so there is nothing to recycle. An instance is only dropped if a format traps, since a trap leaves the module mid-call with no way to unwind.

It uses @bjorn3/browser_wasi_shim rather than node:wasi, for the same reason the Ruby and Swift packages do: it is pure JavaScript with an in-memory filesystem, so the source being formatted never touches disk. It also sidesteps a real defect — repeatedly instantiating a module this size through node:wasi segfaults Node outright after about fifteen instances.

Community

We are API nerds. You too? Let's chat on Discord: https://discord.gg/scalar

License

MIT for this package. The artifact embeds rustfmt, the rustc crates it parses with and the Rust standard library, all MIT OR Apache-2.0, plus one MPL-2.0 dependency. See licenses/NOTICE.md.