WASM (Emscripten)
Phase 0 task "WASM hello-world build (Emscripten)" lives here. The full engine-in-WASM build is Phase 2 work — see Phase 2 below.
Rule R5: WASM never blocks shipping. Server-side rendering is always the fallback. This Phase 0 deliverable is validation only — proves that the Emscripten toolchain end-to-end compiles a C++ function callable from JS. Do not gate Phase 1 on it.
What's here today
wasm/
emsdk.pinned # pinned Emscripten SDK version
CMakeLists.txt # builds the hello-world Emscripten target
hello.cpp # exports add() and hello_version() via EMSCRIPTEN_KEEPALIVE
hello.test.mjs # Node 20+ smoke test — loads the module, asserts return values
package.json # `npm test` shortcut for hello.test.mjs
Build output goes to out/build/wasm/bin/hello.{mjs,wasm}. The .mjs is an
ES6 module that imports cleanly into Node 20+ and modern browsers.
Prerequisites
-
Emscripten SDK at the version pinned in
emsdk.pinned.git clone https://github.com/emscripten-core/emsdk cd emsdk ./emsdk install 3.1.74 # match emsdk.pinned ./emsdk activate 3.1.74 source ./emsdk_env.sh # Linux/macOS .\emsdk_env.bat # Windows cmd.exe .\emsdk_env.ps1 # Windows PowerShellAfter
emsdk_envruns in your shell,$EMSDKis set — that's whatcmake/toolchains/wasm.cmakekeys off. On Windows, install emsdk on a space-free path outside OneDrive (same rule as the PDFium build — seedocs/phase0.md). -
Node 20+ for the smoke test (CI uses Node 20).
-
CMake 3.25+ and Ninja — already required for the rest of the repo.
Build + run
cmake --preset wasm
cmake --build --preset wasm
node wasm/hello.test.mjs
# → [wasm-smoke] OK — add(2,3)=5, hello_version()=1, cwrap add(40,2)=42
CI does the same three commands on Ubuntu after installing the pinned emsdk.
Local build dir override (Windows / OneDrive)
The default binaryDir lives under the repo. On Windows the
OneDrive + spaced-path constraint
applies — drop a CMakeUserPresets.json next to this README's parent that
overrides binaryDir to e.g. C:/Users/<you>/pdfeng-build/wasm, and set
HELLO_MJS=C:/Users/<you>/pdfeng-build/wasm/bin/hello.mjs when running the
smoke test.
What this proves (and what it does NOT)
| Validated by Phase 0 hello-world | Still TODO in Phase 2 |
|---|---|
| emsdk install + version pin | PDFium cross-compiled with Emscripten |
cmake --preset wasm configures cleanly |
Skia / FreeType / HarfBuzz cross-compiled |
C → JS export via ccall/cwrap |
Engine cross-compiled |
| ES6-module output usable from Node + web | Web Worker + OffscreenCanvas integration |
| CI pipeline for the WASM artifact | Page-render parity with the server path |
Phase 2 — real engine-in-WASM
When that work begins, do not delete the hello-world target — keep it as a
toolchain canary so a broken emsdk install fails fast and obviously. The real
target will live alongside it (e.g. pdfengine_wasm) and consume the engine
library via the standard CMake import path once the engine has Emscripten
support in its vcpkg/build story.
Key flags to carry forward (engine blueprint §6):
ALLOW_MEMORY_GROWTH=1, Web Worker + OffscreenCanvas for off-main-thread
rendering, ES6 module output.