crossbind
GitHub
GUIDE · GETTING STARTED · INTRODUCTION

Introduction

crossbind compiles C++ and Rust into WebAssembly, native iOS and Android libraries, and WASI commands - then hands the result to JavaScript as an ordinary module. 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.

src/native/helloWorld.h
#pragma once
#include <string>
 
std::string getHelloWorldMessage() {
return "Hello World!";
}
src/main.js
import { initNative, getHelloWorldMessage } from './native/helloWorld.h';
 
await initNative();
console.log(getHelloWorldMessage());

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++ libraries ship prebuilt as @cpp.js/package-* - GDAL, OpenSSL, SQLite, GEOS, PROJ and more, compiled from the real upstream sources at pinned versions. Install one and import its header directly; nothing is compiled on your machine.

src/main.js
import { initNative, GDALVersionInfo } from '@cpp.js/package-gdal/gdal.h';
 
await initNative();
console.log(GDALVersionInfo('RELEASE_NAME'));

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 Packages.

Where the output runs

TargetWhat is producedGuide
BrowserWebAssembly + a JS loaderRuntimes
Node.jsWebAssembly, host filesystem accessRuntimes
Cloudflare Workers / edgeWebAssembly, single-threaded, in-memory fsRuntimes
iOS and AndroidNative machine code over JSI - no wasmRuntimes
WASI (wasm32-wasip3)One .wasm command, no JS hostWASI commands

The JavaScript you write does not change between them. The build target does.

What ends up in your bundle

Only the code reachable from the headers you imported is linked in. Dead-code elimination and LTO are on by default, so pulling two functions out of a large library costs two functions, not the library.

Android
Dead-code elimination is not implemented for Android builds; those link the full archive.

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.

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.
Type to search every guide page and section.
↑↓ navigate↵ openesc close