Emscripten TypeScript Example
Overview
Section titled “Overview”Emscripten TypeScript Example is a minimal, production-ready template for compiling C code to WebAssembly and calling it from TypeScript in the browser — with full type safety. It solves a common pain point in WebAssembly projects: the gap between raw .wasm binaries and the typed, ergonomic APIs that frontend developers expect. The project ships a complete pipeline from C source to deployed web page, including auto-generated .d.ts files, esbuild bundling, and GitHub Pages CI/CD.
A live demo is available.
How It Works
Section titled “How It Works”The pipeline has three stages: compile, bundle, and serve.
1. Compile C → WebAssembly + TypeScript Definitions
Section titled “1. Compile C → WebAssembly + TypeScript Definitions”The C source in src/lib.c is intentionally simple — the point is the toolchain, not the algorithm:
#include <emscripten/emscripten.h>
EMSCRIPTEN_KEEPALIVEint add(int a, int b) { return a + b;}EMSCRIPTEN_KEEPALIVE prevents the compiler from dead-code-eliminating the function. The build.sh script runs emcc with carefully chosen flags:
emcc src/lib.c \ -Oz \ -s WASM=1 \ -s ENVIRONMENT='web' \ -s EXPORT_ES6=1 \ -s EXPORTED_FUNCTIONS="['_add']" \ -s NO_FILESYSTEM=1 \ -s ALLOW_MEMORY_GROWTH=0 \ -o src/lib.mjs \ --emit-tsd lib.d.tsKey choices here:
-Oz— Aggressively optimise for binary size, critical for web deliveryENVIRONMENT='web'— Strip Node.js-only code paths from the glue JSEXPORT_ES6=1— Output an ES module so it plays nicely with modern bundlersNO_FILESYSTEM=1— Remove Emscripten’s virtual filesystem (saves ~50KB of glue code)--emit-tsd— The star of the show: auto-generates TypeScript definitions for every exported function
2. Bundle with esbuild
Section titled “2. Bundle with esbuild”The build.mjs script orchestrates a two-phase build: first running the Emscripten compilation, then using esbuild to bundle the TypeScript entry point into a minified ES module with inline source maps. Static assets (lib.wasm, index.html) are copied to dist/.
3. Type-Safe Usage in TypeScript
Section titled “3. Type-Safe Usage in TypeScript”The TypeScript side in src/index.ts imports the generated module and calls the C function with full type checking:
import init from './lib'const promise = init()
async function main() { const { _add: add } = await promise const result = add(1, 2) document.getElementById('result')!.innerText = result.toString()}
main()Because --emit-tsd generates a .d.ts alongside the .mjs glue code, TypeScript knows that _add takes two numbers and returns a number. No manual type declarations needed.
Features
Section titled “Features”- Auto-generated TypeScript definitions — Uses Emscripten’s
--emit-tsdflag to produce.d.tsfiles, eliminating hand-written type stubs - Minimal WASM binary —
-Oz,NO_FILESYSTEM, andALLOW_MEMORY_GROWTH=0keep the output as small as possible - ES module output — Both the Emscripten glue and the final bundle use ESM, enabling tree-shaking and modern
<script type="module">loading - On-the-fly dev server —
serve.mjsis a custom HTTP server that transpiles.tsto.json each request via esbuild, so you never serve stale bundles during development - GitHub Pages CI/CD — A GitHub Actions workflow (
pages.yml) installs the Emscripten SDK, builds the project, and deploys to GitHub Pages on every push tomain - Zero runtime dependencies — Only
esbuild,typescript, and@types/nodeas dev dependencies; the final output is self-contained
Tech Stack
Section titled “Tech Stack”| Language | C, TypeScript |
| Compiler | Emscripten (emcc) |
| Bundler | esbuild |
| Platform | Browser (WebAssembly) |
| CI/CD | GitHub Actions → GitHub Pages |
| Dev Dependencies | esbuild 0.24, TypeScript 5.6 |
Source Code
Section titled “Source Code”The source code is available on the project’s GitHub repository.