Skip to content
Latchkey

How to Run SWC (@swc/core) in CI

SWC is a Rust compiler delivered as per-platform native bindings. CI breaks when the binding for the runner OS/libc is missing.

@swc/core resolves a platform package such as @swc/core-linux-x64-gnu (or -musl on Alpine) via optional dependencies. The wrong libc or an omitted optional dep produces a bindings error.

Why it fails in CI

  • Alpine/musl needs the -musl binding, not the default -gnu one.
  • A lockfile from another OS, or --omit=optional, leaves no usable binding.
  • A cached node_modules from another platform carries the wrong .node binding.

Install and run it reliably

Install fresh with optional dependencies enabled so the matching binding resolves. On Alpine the musl binding installs automatically with a clean install.

Terminal
# glibc Linux or Alpine: clean install picks the right binding
npm ci

# verify the binding loads
node -e "require('@swc/core'); console.log('swc ok')"

Cache & speed

Standard ~/.npm caching covers the binding download. Do not cache node_modules across libc/OS because the native binding differs.

.github/workflows/ci.yml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

Common errors

  • Bindings not found / Failed to load native binding → optional deps omitted or wrong libc; reinstall.
  • Error loading shared library ld-linux on Alpine → install the -musl binding (clean install).
  • invalid ELF header → reused node_modules from another arch; reinstall fresh.

Key takeaways

  • SWC ships per-platform native bindings via optional dependencies.
  • Alpine needs the -musl binding; a clean install resolves it.
  • Never reuse node_modules across libc or arch.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →