API · cpp.js · Headers as modules

Headers as modules

The most surprising part of the API: you import C++ header files. cpp.js's bundler plugin turns every .h import into the JavaScript glue for that header's classes, and compiles the wasm behind the scenes. This is how Gdal, Dataset and friends reach JavaScript.

The mechanism

how the imports work
import "gdal3.js/Dataset.h";        // side-effect: registers Dataset's bindings
import "gdal3.js/Driver.h";
import "gdal3.js/GCP.h";
import "gdal3.js/SubdatasetInfo.h";
import { initCppJs } from "gdal3.js/Gdal.h";   // entry header re-exports the factory

The bundler plugin (Vite / Webpack / Rollup / Rspack / Metro) intercepts any import whose path ends in .h. Importing a header generates a bridge file and registers that header's embind bindings into the wasm that gets compiled, so a bare import "pkg/Foo.h" is a side-effect import that makes Foo available on the Module. The entry header additionally re-exports initCppJs. At build time the plugin emits three assets, cpp.js, cpp.wasm and cpp.data.txt, which the runtime loads.

What binds

If you write your own C++ wrapper (the C++ path), these rules decide what cpp.js can expose:

C++ to JS type table

C++JavaScript
voidundefined
boolboolean
char, short, int, unsigned int, float, doubleNumber
long, int64_t, uint64_tBigInt (e.g. 9n)
std::stringString
emscripten::valanything
std::vector<T>via toArray / toVector
std::map<K,V>, enums, class objectsmapped objects / classes

SWIG escape hatch

cpp.js auto-generates a SWIG interface (.i) per header. You only hand-write one when you need a %typemap, %rename, %ignore, or selective export: drop a sibling foo.i next to foo.h, or point paths.module at a folder of them. Full binding details are at cpp.js.org.