Architecture & internals
One C++ recipe, three runtimes. v3 binds GDAL's C++ API automatically and compiles the same sources to WebAssembly for the web and to real native libraries for mobile.
The binding pipeline
gdal3.js declares a thin C++ surface: Gdal, Dataset, Driver, GCP, SubdatasetInfo headers that wrap GDAL's C API in classes. cpp.js turns those headers into JavaScript modules: on the web through an embind binding compiled with Emscripten, on mobile through a JSI bridge. There is no hand-written glue per function. Adding a GDAL utility to the API is mostly adding its declaration to a header.
Your bundler resolves import { Gdal } from 'gdal3.js/Gdal.h' via the cpp.js plugin, which generates the JS bridge, ships the right binary for the target, and wires asset URLs. The C++ never appears in your bundle; the wasm and data files load lazily at initCppJs().
Web vs mobile runtimes
| ASPECT | BROWSER / NODE | IOS / ANDROID |
|---|---|---|
| Binary | WebAssembly (st & mt variants) | Native library (arm64 + x86_64) |
| Bridge | embind | JSI (New Architecture supported) |
| Filesystem | Virtual FS: OPFS / in-memory (guide) | Real device filesystem |
| Memory ceiling | Tab limits apply (see memory) | App-level limits: no wasm sandbox cap |
| Threading | useWorker + mt build (guide) | Native threads |
Package map
| PACKAGE | ROLE |
|---|---|
gdal3.js | The recipe: C++ headers + sources that define the JS-facing classes |
@gdal3.js/wasm | Prebuilt WebAssembly artifacts consumed by the bundler plugins |
@gdal3.js/wasm-bundle | Self-contained UMD builds (browser / Node / edge; st & mt) for use without a bundler |
@gdal3.js/wasm-bundle-minimal | Slimmer driver subset (in the works) |
@gdal3.js/ios · @gdal3.js/android | Prebuilt native libraries for React Native |
GDAL itself, with its dependency stack (PROJ, GEOS, SQLite, SpatiaLite, libtiff with JPEG/ZSTD/LERC, and more), comes prebuilt from the cpp.js package registry, currently GDAL 3.13.1.
Why the cpp.js rewrite
v2 was a single hand-maintained Emscripten build: every exposed function needed wrapper code, every bundler needed bespoke asset instructions, and mobile was out of reach. Three pressures drove the rewrite: the issue tracker was dominated by bundler/path setup problems; each new GDAL utility cost real wrapper work (gdalbuildvrt and friends waited years); and iOS browser memory limits (issue #96) capped what web delivery could do. cpp.js answers all three: plugins own the asset story, headers are bound automatically, and the same recipe compiles natively for phones.
Maintainability
The driver registry is asserted in CI on every build (Playwright suites boot the wasm and exercise read/write across the registry). Upstream GDAL versions arrive by bumping one dependency. And because bindings are generated, surface area grows by editing headers, which is how v3 picked up buildVRT, demProcessing, grid, nearblack, footprint and the multidim tools in one sweep.