crossbind
GitHub
GUIDE · CONCEPTS · ARCHITECTURE

Architecture

You never call the compiler yourself, which is exactly why it helps to know what happens when you do. This is the one-screen model of a crossbind build - from cppjs build to the files your bundler picks up.

The pipeline

  1. Config load. cppjs.config.js is read and merged with cppjs.build.js (packages only) and machine settings from ~/.cppjs.json. Transitive dependencies are flattened; if any of them is multithreaded, the project is promoted to mt.
  2. Target expansion. CLI flags filter the target matrix down to the {platform, arch, runtime, runtimeEnv, buildType} tuples this host can actually build.
  3. `createLib`. Per target, your C++ is compiled to a static library - cmake or configure, inside Docker for wasm/Android/WASI, through Xcode for iOS.
  4. Bridge generation. Each imported header becomes a bridge translation unit that registers the binding surface; that is what makes classes and functions appear in JavaScript.
  5. Link. buildWasm links the archives with emcc; buildWasiCommand links a wasi command; buildCargo stages a Rust crate's .a; createXCFramework combines the iOS slices.
  6. `buildJs`. Rollup assembles the loader for the chosen runtime environment and writes the final artifacts.
dist/ (after a wasm build)
dist/
├── myapp-wasm-wasm32-st-release.browser.js # loader
├── myapp-wasm-wasm32-st-release.browser.wasm # module
└── prebuilt/
└── wasm-wasm32-st-release/
├── include/ # headers for dependent packages
└── lib/libmyapp.a

Targets are the unit of work

Everything downstream of config load is per target, and the target name is stamped into every artifact - myapp-wasm-wasm32-st-release.browser.js is platform, arch, runtime, build type and runtime environment in one string. That is why st and mt artifacts can never be confused for one another, and why mixing them in one bundle fails loudly.

The JavaScript runtime layer

The loader is a shared core plus thin per-environment adapters: URL-based asset resolution in the browser, filesystem-based in Node and on edge; OPFS mounting and file auto-mount in the browser; a Comlink bridge when the module runs in a worker. Supporting a new runtime is one more shim over the same core, which is why initNative() behaves identically everywhere.

Where the toolchain lives

TargetRuns inNeeds
wasm, Androidthe digest-pinned Docker imageDocker
WASIDocker, or a local wasi-sdk when configuredDocker or wasi-sdk 34+
iOSthe hostmacOS, Xcode, CocoaPods
Rust (export.type: 'cargo')the hostthe cargo toolchain

Docker mounts the project path as its working directory, so nothing above that path is visible to the build. In a monorepo, point paths.base at the repository root - see Configuration.

Cache and rebuilds

PathWhat it holdsSafe to delete
.cppjs/build cache: cmake output, generated bridges, generated typesyes - rebuilt on the next build
dist/prebuilt/<target>/the static library and headers other packages link againstyes, but dependents must rebuild
dist/<name>.<env>.{js,wasm,data.txt}what your app loadsyes

A build short-circuits when the artifact is newer than the sources, which is what keeps incremental builds cheap. Bundler plugins compare source timestamps for you and force a rebuild in dev when a .h or .cpp changes - that is the hot-reload path.

When a change is not picked up
Restart the dev server first. A stale artifact almost always means the source is older than the output, or paths.native does not point where you think it does.

The override hierarchy

When you need to change what the build does, reach for the highest layer that solves it - the higher the layer, the less of the pipeline you take ownership of.

  1. Narrow which targets build: target.{platform, arch, runtime, buildType}.
  2. Per-target flags and data, declaratively: targetSpecs[].specs.{cmake, emccFlags, env, data, ignoreLibName, wasiFlags}.
  3. Project-wide: dependencies, env, functions.isEnabled.
  4. Package authoring: cppjs.build.js hooks - source fetch, patches, build parameters, extra libs.
  5. Cross-package plugins: extensions[] hooks at config-load and build-step boundaries.
  6. Machine-wide: ~/.cppjs.json (runner, Xcode team, log level, local wasi-sdk).

Layers one to three live in Configuration; the rest are package-author territory, documented in the API reference linked from the sidebar.

Type to search every guide page and section.
↑↓ navigate↵ openesc close