Skip to content
Latchkey

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.

.github/workflows/ci.yml
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: dist

Caching 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 $0.0025/min at 2 vCPU against $0.006 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).

Making this reliable in CI

  • Pin every tool version. An unpinned toolchain turns a runner image update into a build break on unchanged code.
  • Cache what is expensive to produce, and key the cache to the tool version so a restore across a version boundary cannot poison the build.
  • Assert on the produced artifact rather than on the command succeeding.
  • Set an explicit timeout so a hung step fails fast instead of consuming the whole job budget.

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.

Frequently asked questions

CI/CD for an Emscripten WASM App with GitHub Actions?
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.
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.
Caching 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 $0.0025/min at 2 vCPU against $0.006 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).

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card