crossbind
GitHub
GUIDE · GETTING STARTED · QUICK START

Quick start

Two paths lead to the same place: scaffold a fresh project, or add crossbind to an app you already have. Either way you end up with a bundler plugin, a cppjs.config.js, and a header you can import from JavaScript.

Prerequisites

The cross-toolchain ships as a Docker image, so crossbind needs very little installed on your machine:

  • Docker - carries the web, Android and WASI toolchains. Pulled automatically on the first build.
  • Node.js 22+.
  • CMake 3.28+ - mobile only.
  • Xcode and CocoaPods - iOS only, macOS only.
  • A Rust toolchain (cargo plus the platform targets) - only when you bind Rust. See Rust.
  • wasmtime - only to run platform: 'wasi' output and the prebuilt -bin-wasi tools.
shell
docker --version
node --version
docker pull bugra9/cpp.js # optional: the first build pulls it anyway
iOS
Xcode looks for Node.js in the system environment. If it is not there, link it once: ln -s $(which node) /usr/local/bin/node.

Set it up with a coding agent

Using Claude Code, Cursor, Copilot or similar? Hand it the prompt below - it inspects the repo, installs the right plugin, writes the config and wires your bundler.

prompt
Add crossbind to this project so I can call C++ (or Rust) from JavaScript.
 
First inspect the repo and tell me what you found: package manager, bundler (Vite, Webpack, Rspack, Rollup, Metro), whether it targets the browser, Node, Cloudflare Workers or React Native, and any existing C++/Rust sources or CMake project.
 
Then:
 
1. Install `crossbind` and the plugin matching my bundler (`@crossbind/plugin-vite`, `@crossbind/plugin-webpack`, `@crossbind/plugin-rspack`, `@crossbind/plugin-rollup`, `@crossbind/plugin-metro`).
2. Create `cppjs.config.js` at the repo root and register the plugin in my bundler config. Keep the change idempotent - do not duplicate an existing entry.
3. If I already have C++ or Rust sources, wire those up. Otherwise add one small example: a header under `src/native/`, imported directly from JavaScript.
4. Call `await initNative()` once at my app's entry point, before the first native call - import it from the header or crate the app already imports, not from a separate runtime package.
5. Run my build and report what changed, including the size of the generated WebAssembly.
 
Rules: do not hand-write binding or glue code - crossbind generates it from the header. Do not add a second build system. If I need a prebuilt library (GDAL, OpenSSL, SQLite, GEOS, PROJ and more), install the matching `@crossbind/package-*` instead of compiling it from source.
 
Reference: https:"color:#78829a">//cpp.js.org/docs/guide/getting-started/introduction

Prefer to do it yourself? Keep going.

A new project

The scaffolder wires the bundler, its plugin and a starter cppjs.config.js for you. Answer the prompts - if you are unsure, pick Web, React and Vite.

shell
npm create crossbind
shell
? Project name › my-app
? Where should we create your project? › ./my-app
? What kind of project? › Web
? Choose framework / variant › React
? Choose bundler / ecosystem › Vite

Every prompt can be preselected positionally, which is how CI and scripted setups create a project:

shell
npm create crossbind my-app Web React Vite

Then install and start the dev server - and skip to your first call.

shell
cd my-app
npm install
npm run dev

An existing project

Already have an app? Install the plugin for your bundler and register it. Vite is shown here; Webpack, Rspack, Rollup and Metro follow the same shape with their own plugin - see Bundlers.

shell
npm install -D @cpp.js/plugin-vite
vite.config.js
import { defineConfig } from 'vite';
import viteCppjsPlugin from '@cpp.js/plugin-vite';
 
export default defineConfig({
plugins: [viteCppjsPlugin()],
});

The plugin reads a cppjs.config.js from your project root. The minimal one is two lines - paths.config anchors every other path to this file, so it is never optional:

cppjs.config.js
export default {
paths: {
config: import.meta.url,
},
};

Everything else this file can carry is in Configuration.

Your first call

C++ lives under src/native. Declare the class in a header - the public surface has to be in the header, because that is what the binder reads.

src/native/MySampleClass.h
#pragma once
#include <string>
 
class MySampleClass {
public:
static std::string sample() {
return "Hello World!";
}
};

Now import that header from your app code and call it. initNative() is exported by the header module itself; one call boots every native module you imported.

src/main.js
import { initNative, MySampleClass } from './native/MySampleClass.h';
 
await initNative();
console.log(MySampleClass.sample());
First build
The first npm run dev compiles the wasm inside Docker and can take a while. After that the cache makes rebuilds incremental, and editing the header hot-reloads the page.

Build for production

Nothing special: your bundler build triggers the release compile through the plugin.

shell
npm run build

Targeting Node, Cloudflare Workers, React Native or WASI instead? Each one is a cppjs build flag combination - see Runtimes.

Where to next

  • C++ bindings - what the auto-binder accepts, and the C++ ↔ JS type table.
  • Packages - use a prebuilt library instead of compiling one.
  • Filesystem - where files live in the browser, Node and on edge.
  • Troubleshooting - when the first build does not go to plan.
Type to search every guide page and section.
↑↓ navigate↵ openesc close