Introduction
crossbind compiles C++ and Rust into WebAssembly and native iOS and Android libraries, then hands the result to JavaScript as an ordinary module; a separate target turns C++ into WASI commands. You import a header; the bindings are generated from it.
There is no glue code to write and no second build system to run. The header you already have is the interface: whatever it declares - functions, classes, inheritance, overloads, vectors, maps, enums - shows up on the JavaScript side under the same names.
One import, no glue
Put your C++ under src/native, then import the header from JavaScript. initNative() boots the runtime once; after it resolves, every symbol the header exposes is callable.
The same shape works for Rust: an app-local .rs file, or a crates.io crate imported through the cargo: scheme. See Rust.
Libraries you do not have to build
16 C and C++ libraries ship prebuilt as @crossbind/port-* - GDAL, OpenSSL, SQLite, GEOS, PROJ and more, compiled from the real upstream sources at pinned versions. Install the variant for your platform and import its header directly; the upstream library is precompiled, and only your own code, the bindings and the final link go through the build toolchain.
One initNative() covers every module on the page: each imported header registers its bindings, the call boots the runtime and resolves all of them together. Full details in Libraries; every library is listed under Libraries.
Where the output runs
| Target | What is produced | Guide |
|---|---|---|
| Browser | WebAssembly + a JS loader | Runtimes |
| Node.js | WebAssembly, host filesystem access | Runtimes |
| Cloudflare Workers / edge | WebAssembly, single-threaded, in-memory fs | Runtimes |
| iOS and Android | Native machine code over JSI - no wasm | Runtimes |
WASI (wasm32-wasip3) | One .wasm command, no JS host | WASI commands |
The JavaScript you write does not change between the four JavaScript targets; the build target does. WASI commands are a separate path with no JavaScript host.
What ends up in your bundle
Only the code reachable from the headers you imported is linked in: dead-code elimination on top of -O3 is on by default, so pulling two functions out of a large library costs those functions and what they reach, not the whole library.
Two ways to use it
- Write C++ (or Rust) yourself. Your own sources under
src/native, imported by header. Best when the work belongs on the native side - a whole pipeline in one call, no boundary crossing per step. - Drive a prebuilt library from JavaScript. Import the package header and call into it directly. Quickest to wire up, and the way most people start.
Both paths use the same runtime and the same initNative() call, and they mix freely in one project.
Why crossbind
The header import is the front door. Underneath it, crossbind is the native library distribution layer for JavaScript runtimes: versioned packages, target-specific artifacts, transitive dependencies, build recipes and runtime adapters.
- Target-aware distribution. A package family carries WebAssembly, iOS, Android and WASI variants, and each build consumes only the artifact valid for its platform, architecture, runtime and build type.
- Native dependency resolution. Package manifests and crossbind configs carry the transitive native graph, so prerequisites are built and linked in the right order.
- A reproducible package contract. Port recipes record upstream versions, source integrity, licences, dependencies and target-specific artifacts.
- No hand-written binding glue. Public functions, classes, methods, enums, containers and the supported standard-library types are generated from the header you already own.
- C++ and Rust. Import local native sources, a
cargo:crate or a reusable native package through the same project. - First-party integrations and ports. Bundler plugins give incremental builds, and version-pinned ports make GDAL, SQLite, OpenSSL, GEOS, PROJ and more available without rebuilding upstream.
Limits worth knowing
- Browser OPFS persistence requires
useWorker: true: crossbind's OPFS integration relies on synchronous access handles, which browsers expose only in worker scope. - Browser multithread builds (
runtime: 'mt') need COOP/COEP headers in production, and the pthread pool defaults to two workers; the size and the exhaustion policy are configurable. - Edge runtimes such as Cloudflare Workers run single-threaded with an in-memory filesystem: no workers, no OPFS.
- Android builds link whole archives; dead-code elimination is not implemented there.
- iOS builds need a local Xcode and CocoaPods, Rust bindings need a local cargo, and WASI commands need wasmtime on PATH.
- There is no performance claim: a hand-written loop in JIT JavaScript can match the same algorithm in Wasm. The value is the libraries you would otherwise rewrite.
- The 2.0 line is a beta; prereleases carry no compatibility guarantee until the first stable train.
Browse the Libraries that ship prebuilt, or read the API reference for every option and default.
Where to next
- Quick start - from an empty directory to a running app.
- Bundlers - Vite, Webpack, Rspack, Rollup, Metro, or no bundler at all.
- C++ bindings - the rules your headers have to follow, and the type table.
- Troubleshooting - the errors people hit most, with the standard fix for each.