CI/CD for an Emscripten WASM App with GitHub Actions
Compile C/C++ to WASM and ship the web bundle on every push.
Emscripten compiles C/C++ to WebAssembly plus a JavaScript glue layer that the web app loads. This recipe sets up the Emscripten SDK, builds the WASM module, bundles the host, and deploys the static output.
What the pipeline does
- set up the Emscripten SDK with emsdk
- compile to WASM with emcc or make
- bundle the JS host with Vite
- run the JS test suite
- deploy the dist directory
The workflow
mymindstorm/setup-emsdk installs and caches the toolchain; emcc emits the .wasm and .js glue, which Vite then bundles into the web app.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mymindstorm/setup-emsdk@v14
with:
version: latest
actions-cache-folder: emsdk-cache
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: emcc src/core.c -O3 -o public/core.js -s MODULARIZE=1
- run: npm ci
- run: npx vitest run
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: distCaching and speed
setup-emsdk caches the toolchain via actions-cache-folder, and cache: npm covers the JS install. The emcc compile is the slow step; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) keep builds affordable and auto-retry transient SDK download failures.
Deploying
Deploy dist to Pages, Netlify, or S3+CloudFront, and serve the .wasm with the application/wasm MIME type so streaming compilation works. Enable cross-origin isolation headers if the module uses threads (SharedArrayBuffer).
Key takeaways
- setup-emsdk installs and caches the Emscripten toolchain.
- Serve .wasm with the application/wasm MIME type for streaming.
- Threaded WASM needs cross-origin isolation headers.