wasm-bindgen's two halves: a 1.81 library, a 1.86 CLI, and shims left in the DOM path
Facilitating high-level interactions between Wasm modules and JavaScript
At a glance
- What is it?
- wasm-bindgen ships a Rust library you depend on and a separate CLI that generates the JavaScript glue, and the distance between the two shows up in your toolchain floor, your lockfile, and every DOM call your code makes.
- Who is it for?
- wasm-bindgen is a sound floor to build on when Rust has to talk to JavaScript, and its rough edges are legible enough to plan around: match your Rust version against the CLI's 1.86 requirement before the install, read the CHANGELOG whenever a patch moves the library MSRV, and budget for a shim on every DOM call. A team that needs direct native DOM speed from wasm will not find it here today, because the Web IDL bindings proposal the README names is still future tense.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- wasm-bindgen/wasm-bindgen9,227 ★ · +74
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| wasm-bindgen/wasm-bindgen | 9.2k | 9.2k |
74 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The CLI installs three ways and the library never arrives with it
The command-line half of this project installs three documented ways, and none of them put the library into your Cargo.toml. The plain path compiles it from source:
cargo install wasm-bindgen-cliWith cargo-binstall already present, the prebuilt artifact is the shorter path:
cargo binstall wasm-bindgen-cliThe third route is the release page, a binary download for anyone who does not want cargo in the loop at all. Once the CLI is in place, the JS side of a Rust export is an ordinary ECMAScript module:
import { greet } from "./hello_world";
greet("World!");That import works because the project targets ECMAScript modules, so a WebAssembly module is imported the same way a JavaScript module is. The gap worth noticing before you start: the install command carries no version flag, so it takes whatever release is newest, and the manifest puts the library at 0.2.130 with a rust-version of 1.81 while the MSRV policy puts CLI tools at 1.86. A reader on Rust 1.81 can compile the library and still fail to build the CLI that generates its glue.
The MSRV table moved the floor twice in fourteen months
The project keeps a running record of its own minimum Rust version, split into two columns, and both columns have climbed:
| Version | Library MSRV | CLI MSRV | Date | |---|---|---|---| | 0.2.129 | 1.81 | 1.86 | 2026-09-23 | | 0.2.118 | 1.77 | 1.86 | 2026-04-10 | | 0.2.106 | 1.71 | 1.82 | 2025-11-27 | | 0.2.103 | 1.57 | 1.82 | 2025-09-17 | | 0.2.93 | 1.57 | 1.76 | 2024-08-13 |
The stated policy is a two-year MSRV window for libraries, meaning Rust versions released within the last two years are supported, and a shorter window for the CLI. The consequence for a reader is sharper than the table looks: changes to the MSRV may be made in patch versions, and the history shows the library MSRV rose three times between 0.2.103 (2025-09-17) and 0.2.129 (2026-09-23). 0.2.103 still allowed 1.57, and 0.2.106, a patch bump, moved the library to 1.71. If your build pins an old toolchain, an ordinary dependency refresh can raise the floor under you without a major or minor version change. The CHANGELOG and this table are where the move gets announced, so a project on a frozen toolchain needs to watch both.
The manifest pins macro and shared with an exact = requirement
Inside the published crate, the two internal dependencies are not floating. Both carry an exact version requirement rather than a caret:
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.130" }
wasm-bindgen-shared = { path = "crates/shared", version = "=0.2.130" }The equals sign means Cargo will not substitute a different patch release for the macro or the shared schema crate. Those two have to move in lockstep with the library release, which is a deliberate choice: the macro rewrites your source and the shared crate holds the schema both sides agree on, so a mismatched pair would produce bindings that disagree with the runtime. What it costs you is independence. You cannot hold the macro crate back while updating the library, and you cannot pick up a macro fix without moving the library too. One more manifest detail explains part of the layout: the build dependency is a renamed one, `rustversion-compat = { package = "rustversion", version = "1.0.6" }`, and the manifest comments say why, since in older MSRVs dependencies and crate features cannot share the same name.
Lightweight is a claim about generated bindings, not about the wasm file
The trimming claim is precise once you read what it scopes. The generated bindings and glue cover only the JavaScript imports you actually use and the Rust functionality you export, and the example given is a query selector call: importing and using `document.querySelector` does not cause `Node.prototype.appendChild` or `window.alert` to be pulled into the bindings as well. That is a statement about the JavaScript side of the boundary. Nothing in that paragraph addresses the size of the compiled module itself, and nothing there says unused Rust items disappear from the payload, so readers looking for a smaller .wasm file should not read this feature as that promise. The other half of the boundary is equally narrow: only items carrying the annotation cross it. The declared import and the exported function look like this:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}A public function without the annotation stays on the Rust side and never appears in the JavaScript module.
Every DOM method still routes through a JavaScript shim
The performance argument for using this toolchain rests on a proposal, and the project is explicit that the proposal is still pending. The design note says the Web IDL bindings proposal is in mind, and that eventually there will not be any JavaScript shims between Rust-generated wasm functions and native DOM methods. Because wasm functions are statically type checked, some of those native methods' dynamic type checks become unnecessary, which is where the faster-than-JavaScript DOM access claim comes from. Read that carefully: the word is eventually. As of release 0.2.130 the shims are still there, so a call from your Rust code into a DOM method still crosses into JavaScript and back. For a reader budgeting call costs that matters. The claimed win is a property of a future platform change, not of the glue this toolchain emits today. The companion crates where the type surface actually lives are js-sys for the JavaScript language builtins and web-sys for the web platform, both published with their own API docs alongside the main crate.
Deprecated crate features resolve to empty lists and never warn
Three feature flags survive in the manifest as empty definitions, sitting under a comment marking them deprecated:
# Deprecated crate features
msrv = []
rustversion = []
xxx_debug_only_print_generated_code = []Nothing errors when a downstream Cargo.toml still requests one of them. The feature resolves, the dependency graph stays satisfied, and the build succeeds with the flag doing nothing at all, which means a config carried over from an older release can look active while being inert. The manifest has a separate control for strictness that is worth knowing about, and it is off unless you ask: strict-macro decides whether the annotation macro is strict and generates an error on all unused attributes. So a typo or a leftover attribute on an item can pass silently in the default configuration, and a project that wants those to be errors has to enable strict-macro explicitly. Also on by default is std; the other real features are enable-interning, serde-serialize, which pulls in serde and serde_json, and spans.
The examples directory carries the platform matrix the install text omits
The install section covers the CLI and stops there. It does not name a packaging or bundling tool, and it does not enumerate what runs where, so the practical picture has to be assembled from the repository layout instead. The top level carries the pieces a contributor would expect: crates/ for the split crates, guide/ for the book, releases/, tests/, benchmarks/, benches/, build.rs, publish.rs, and lint configuration through clippy.toml, taplo.toml, and rust-analyzer.toml. The examples/ directory is the part that answers the platform question. It holds per-target directories including jspi, jspi-fetch-streams, jspi-opfs, explicit-resource-management, nodejs-threads, nodejs_and_deno, duck-typed-interfaces, fetch, dom, canvas, char, closures, console_log, add, paint, performance, julia_set, import_js, hello_world, and guide-supported-types-examples. A shared package.json sits beside them, along with cp-dist.mjs and a playwright.spec.ts. That listing tells you browser, Node, Deno, threads, and JSPI work is in scope, and that the browser paths are checked under Playwright, without the README ever spelling out a delivery story.
just test chains twelve recipes and the unwind run wants Node 22.22.3
Contribution runs through a justfile whose test recipe runs twelve suites in sequence, from the CLI and the macro tests through the wasm ones and the Web IDL tests. The wasm suite is where the environment gets specific:
NODE_ARGS="--stack-trace-limit=100" \
RUST_BACKTRACE=1 \
WASM_BINDGEN_TEST_ONLY_NODE=1 \
WASM_BINDGEN_SPLIT_LINKED_MODULES=1 \
cargo test --target wasm32-unknown-unknownWASM_BINDGEN_TEST_ONLY_NODE confines the run to Node, and the stack trace limit is raised to 100 so failures stay readable. The unwind variant of that suite carries the constraint that bites contributors on older Node: since rust-lang/rust#156061 it uses modern exception-reference EH by default, which requires Node 22.22.3 or newer, or Node 24.15.0 or newer. Two other recipes show the project's testing range. The 64-bit wasm target needs a nightly toolchain:
cargo +nightly test \
-Zbuild-std=std,panic_abort \
--target wasm64-unknown-unknownAnd the blessed-output recipes tell you where expectations are recorded, with `BLESS=1 cargo test -p wasm-bindgen-cli -- --skip headless_streaming_tests` overwriting the CLI snapshots and `TRYBUILD=overwrite cargo test -p wasm-bindgen-macro --test ui` rewriting the macro UI tests. A contributor who skips both has no way to regenerate expected output.
Editorial conclusion
wasm-bindgen is a sound floor to build on when Rust has to talk to JavaScript, and its rough edges are legible enough to plan around: match your Rust version against the CLI's 1.86 requirement before the install, read the CHANGELOG whenever a patch moves the library MSRV, and budget for a shim on every DOM call. A team that needs direct native DOM speed from wasm will not find it here today, because the Web IDL bindings proposal the README names is still future tense. Before pinning a version, check the MSRV history table against the toolchain your build actually has.
Frequently asked questions
How do you install wasm-bindgen?
The command-line tool installs three documented ways: `cargo install wasm-bindgen-cli` to compile it from source, `cargo binstall wasm-bindgen-cli` for a prebuilt artifact when cargo-binstall is present, or a binary from the project's release page. None of these add the library to your crate; that stays a normal Cargo dependency.
What does wasm-bindgen do?
It generates the JavaScript bindings and glue for a Rust WebAssembly module, importing JavaScript functions into Rust and exporting Rust functions to JavaScript as ECMAScript modules. Only the imports you actually use and the functions you export get generated glue.
What is wasm-bindgen?
It is the Rust crate and companion command-line tool for high-level interaction between Wasm modules and JavaScript. The crate is published under a dual Apache-2.0 or MIT license, with companion crates js-sys, web-sys, and wasm-bindgen-futures.
How do you use wasm-bindgen in Rust and JavaScript?
Declare the JavaScript functions you want in an extern block annotated with the macro, mark the Rust functions you export with the same attribute, then import the module from JavaScript: `import { greet } from "./hello_world";` and call `greet("World!");`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wasm-bindgen-wasm-bindgen)Star history chart for your README
[](https://hysenlabs.com/projects/wasm-bindgen-wasm-bindgen)Was this useful?