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
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:
- No raw pointers,
char*, or C arrays in the public API. Usestd::string,std::vector<T>, andstd::shared_ptr<T>. - C++11 minimum (C++17 recommended). Public members bind; private members do not.
- Single inheritance with
virtualis fine; multiple inheritance is not. Templates must be explicitly instantiated. - Lifecycle is C++-side via
shared_ptr/ RAII, so there is no JS-sidedelete(); thrown C++ exceptions surface as JSError. - Experimental:
-sJSPIplus a_JSPImethod-name suffix makes a binding async on the JS side.
C++ to JS type table
| C++ | JavaScript |
|---|---|
void | undefined |
bool | boolean |
char, short, int, unsigned int, float, double | Number |
long, int64_t, uint64_t | BigInt (e.g. 9n) |
std::string | String |
emscripten::val | anything |
std::vector<T> | via toArray / toVector |
std::map<K,V>, enums, class objects | mapped 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.