scriptc: Compile TypeScript into native programs and WebAssembly
An experimental tool that compiles TypeScript into native programs or WASI modules whose runtime does not depend on Node.
GitHub vercel-labs/scriptc Updated 2026-09-28 Branch main Stars 5.4K Forks 141
TypeScript C LLVM WebAssembly WASI Preview 1 Node.js 24+

🧭 Decision Guide

Try it if you

  • You need to compile `hello.ts` or a Node.js `node:http` service into an executable that does not require Node.
    The README sections “Build a program” and “Use Node APIs” show `scriptc build`, `./hello`, and `node:http`.
  • You need TypeScript output as IR, C, LLVM IR, assembly, or object files under `.scriptc/`.
    The README section “Build a program” lists `--emit=ir|c|llvm|asm|obj` and the corresponding files.
  • You need to embed an npm package such as `picocolors` into an executable instead of reading `node_modules` at runtime.
    The README section “Use npm packages” requires `--dynamic` and states that the result does not read `node_modules` at runtime.
  • Your target is WASI Preview 1 and you can install Zig and use `SCRIPTC_CC=zigcc`.
    The README section “Build WebAssembly” provides commands using `SCRIPTC_TARGET=wasm32-wasi` and Zig.

Skip it if you

  • Your WASI program depends on network sockets, fetch, child processes, OS signals, or filesystem watching.
    The README section “Build WebAssembly” says these capabilities fail before linking with `SC3002` on WASI Preview 1.
  • You need to use `--emit=obj` directly as a standalone library.
    The README explicitly says `--emit=obj` is a relocatable program object, not a standalone library, and contains undefined `scr_*` references.
  • Your project requires a mature production-grade compiler rather than an experimental v0.1.7 project.
    The README says scriptc is experimental; release metadata shows the latest version is v0.1.7.
  • You can use only Node.js 23 or older.
    The README section “Installation” requires Node.js 24 or newer.

Requirements

  • Node.js 24 or newer.
  • `--emit=ir|c|llvm` requires only Node.
  • `--emit=asm|obj` uses the matching optional platform helper installed with scriptc on supported macOS, Linux, and Windows hosts.
  • Ordinary LLVM-tier executable builds require a platform linker driver and SDK/sysroot.
  • WASI and other cross-target builds require Zig, with `zig` available on `PATH`.
  • `--dynamic` is used for npm packages and `any`-typed code, and explicitly embeds `quickjs-ng`.

First step (verbatim from README)

$ npm install -g scriptc

Watch out

  • `SCRIPTC_CC=zigcc` selects Zig's `cc` subcommand; it is not a standalone executable.
    The README section “Build WebAssembly” explicitly explains `SCRIPTC_CC=zigcc`.
  • On macOS 15+ arm64, helper artifacts use the `arm64-apple-macosx14.0.0` deployment target.
    The README section “Build a program” specifies the helper platform and deployment target.
  • WASI sanitizer builds, native FFI, and library-mode archive builds become target diagnostics.
    The README section “Build WebAssembly” lists these WASI limitations.
  • External object consumption is still experimental and requires `--print=native-link-info` to generate a JSON recipe with an ABI marker.
    The README section “Build a program” explicitly calls external object consumption experimental.

Not stated in the README

  • The README does not provide a complete support list for different TypeScript projects or Node.js APIs.
  • The README provides no performance benchmarks against Node.js, the TypeScript compiler, or other native compilers.
  • The README does not state the artifact-size or runtime overhead of embedding `quickjs-ng` with `--dynamic`.
  • The README does not provide a complete compatibility matrix for macOS, Linux, and Windows versions.
  • The README does not describe API, CLI-parameter, or compatibility commitments after v0.1.7.
  • The README does not specify the exact ABI, platform boundaries, or example coverage for native FFI.

💡 Deep Analysis

6
No I want to compile TypeScript into an object file consumed by a C driver or the Apple linker. Can I treat `--emit=obj` as a standalone library and link it directly?
For: A systems developer integrating scriptc object files through a C driver or direct Apple linker, while accepting that native FFI and external object consumption are experimental

No. You should not treat --emit=obj as a standalone library; the README explicitly says it is a relocatable program object, not a standalone library.

  • The object contains undefined scr_* runtime references and requires a scr_runtime_abi_v2 marker, so matching runtime and ABI information are still required.
  • External object consumption is experimental; the README requires --print=native-link-info to emit a versioned JSON recipe.
  • That recipe records the target, main entry, exact @scriptc/runtime source pack, system libraries, FFI inputs, and ABI marker. The link command should not be reconstructed from hidden cache paths.
  • For a self-contained archive, the README points to scriptc build --lib --profile ...; however, library-mode archives are diagnosed as unsupported for WASI targets.

C-driver or direct Apple-linker integration is possible, but only by following the complete recipe rather than treating the .o as an ordinary library.

  • Build a program: `--emit=obj` writes a relocatable program object, not a standalone library
  • Build a program: It has undefined `scr_*` runtime references and a required `scr_runtime_abi_v2` marker
  • Build a program: Use `--print=native-link-info` to emit a versioned JSON recipe
  • Build a program: `scriptc build --lib --profile ...` remains the self-contained archive interface
Not stated in the README:The README does not provide the complete concrete link command for a particular C driver, FFI input set, and system-library configuration.;It does not state the backward-compatibility guarantees for native-link-info recipes across scriptc versions.
Yes My TypeScript tool targets `wasm32-wasi`, the build machine can install Zig, and the program only needs stdin/readline, Promises, timers, and filesystem APIs. Is scriptc suitable?
For: A tools developer targeting WASI Preview 1 who can install Zig but cannot use networking, child processes, OS signals, or filesystem watching

Yes, provided that the program strictly follows the capability boundary of WASI Preview 1.

  • The README requires Zig for WASI and other cross-target builds and uses bundled WASI libc to produce a WASI Preview 1 module.
  • It explicitly lists async/await, Promises, generators, timers, stdin/readline events, and callback- and Promise-based filesystem APIs as supported capabilities.
  • Network sockets/fetch, child processes, OS signals, and filesystem watching fail before linking with SC3002; your constraints avoid those limitations.
  • The build requires SCRIPTC_CC=zigcc, SCRIPTC_TARGET=wasm32-wasi, and zig on PATH; zigcc is not a standalone executable.

If the tool also needs native FFI, sanitizers, or library-mode archives, those capabilities are diagnosed as unsupported for the WASI target.

  • Build WebAssembly: WASI and other cross-target builds require Zig
  • Build WebAssembly: The WASI target supports async/await, promises, generators, timers, stdin/readline events, callback and promise filesystem APIs
  • Build WebAssembly: network sockets/fetch, child processes, OS signals, and filesystem watching fail with `SC3002`
  • Build WebAssembly: `$ SCRIPTC_CC=zigcc SCRIPTC_TARGET=wasm32-wasi scriptc build hello.ts --no-keep-c -o hello.wasm >/dev/null`
$ SCRIPTC_CC=zigcc SCRIPTC_TARGET=wasm32-wasi scriptc build hello.ts --no-keep-c -o hello.wasm >/dev/null
Not stated in the README:The README does not specify compatibility differences among WASI runtimes, hosts, and runners.;It does not describe the exact preopened-directory configuration required when the tool uses the filesystem.
Yes My TypeScript project contains any usage and npm dependencies. Can scriptc tell me which statements compile statically and which sites require dynamic execution before I choose a build mode?
For: A maintainer assessing whether an existing TypeScript project can compile statically, with possible any usage, npm dependencies, and dynamic JavaScript behavior, who needs per-site diagnostics

Yes. coverage is specifically designed to support this decision.

  • The README says scriptc coverage reports the percentage of a program that can compile statically and gives a coded diagnostic for every dynamic or unsupported site.
  • Its example reports statements analyzed, compile statically, and fully static, allowing you to identify whether a dynamic remainder exists before building.
  • The README also states that npm packages and any-typed code generally require --dynamic, so coverage can guide the choice between changing source code, accepting an embedded runtime, or abandoning the native path.
  • Coverage does not prove full Node compatibility; the project remains experimental, and results depend on target-platform and runtime support.

Running coverage first is therefore the documented way to inspect code locations and diagnostics before choosing a static build or a quickjs-ng-backed dynamic build.

  • Check static coverage: `scriptc coverage` shows how much of a program can compile statically
  • Check static coverage: gives a coded diagnostic for every dynamic or unsupported site
  • README: For npm packages and `any`-typed code, `--dynamic` embeds quickjs-ng explicitly
  • README: scriptc is experimental
$ scriptc coverage hello.ts
Not stated in the README:The README does not specify coverage runtime for large projects, project-configuration support, or monorepo resolution boundaries.;It does not provide complete remediation guidance or automated migration tooling for every diagnostic code.
Yes I have a type-safe TypeScript CLI, build with Node.js 24+, and target macOS 15+ arm64. Can I use scriptc to produce a standalone program that does not require Node at deployment time?
For: A developer maintaining a TypeScript CLI with a Node.js 24+ build environment who wants to ship a standalone executable on macOS 15+ arm64 without requiring Node at runtime

Yes, this is a good fit because the README explicitly supports producing standalone native executables, and those executables do not require Node at runtime.

  • The compiler requires Node.js 24 or newer, but that is a build-time requirement rather than a deployment requirement.
  • On macOS 15+ arm64, ordinary LLVM-tier executables use the bundled helper and precompiled runtime pack; clang acts only as the platform linker driver and does not compile the generated program or runtime C.
  • Static builds contain a small native runtime but no Node or JavaScript engine. A typed CLI with little dynamic behavior is the best match for this path.
  • The README demonstrates the standalone workflow with scriptc build hello.ts -o hello followed by ./hello.

However, API coverage and dynamic-language compatibility remain limited, so scriptc should not be treated as a complete Node.js replacement.

  • Installation: The compiler requires Node.js 24 or newer
  • Installation: The executables it produces do not require Node
  • README: On macOS 15+ arm64, ordinary LLVM-tier executables use scriptc's bundled helper and precompiled runtime pack
  • Build a program: `$ scriptc build hello.ts -o hello`
$ scriptc build hello.ts -o hello
Not stated in the README:The README does not specify the CLI's complete dependency tree, use of dynamic reflection, or final executable size.;It does not provide an equivalent helper and deployment guarantee for architectures outside macOS 15+ arm64.
It depends My TypeScript CLI uses an npm package such as picocolors and may contain any-typed code. Can I package it with scriptc without having the final executable read node_modules?
For: A developer maintaining a TypeScript CLI that depends on npm packages such as picocolors and wants to ship without node_modules while accepting an embedded QuickJS runtime

It depends, but the README provides a clear compatibility path if you accept an embedded QuickJS runtime.

  • The README says npm packages and any-typed code should use --dynamic; this mode explicitly embeds quickjs-ng.
  • The resulting executable does not read node_modules at runtime, which fits single-file CLI delivery.
  • The trade-off is that the result is no longer a purely static native program: JavaScript execution comes from embedded QuickJS, so runtime size, performance, and compatibility differ from the static path.
  • The project is experimental and does not promise that every npm package, native extension, or Node-specific behavior will work.

The README demonstrates this exact workflow by installing picocolors and running scriptc build cli.ts --dynamic -o cli, so this is the natural path for such a CLI.

  • README: For npm packages and `any`-typed code, `--dynamic` embeds quickjs-ng explicitly
  • Use npm packages: The result does not read `node_modules` at runtime
  • Use npm packages: `$ scriptc build cli.ts --dynamic -o cli`
  • README: scriptc is experimental
$ scriptc build cli.ts --dynamic -o cli
Not stated in the README:The README does not quantify the size, startup-time, or performance overhead of quickjs-ng for this CLI.;It provides no compatibility matrix for npm packages beyond the picocolors example and does not define the support range for native npm extensions.
It depends I maintain a TypeScript service using `node:http`, Promises, and filesystem APIs. Can I compile it into a native service that does not require Node on Linux or macOS?
For: A service developer using node:http, filesystem, and asynchronous APIs in TypeScript who wants to reduce Node deployment dependencies on Linux or macOS

It depends. The README demonstrates that some Node APIs can use the native runtime, but suitability depends on the service’s actual API boundary.

  • The Use Node APIs example creates an HTTP service with node:http and builds it with scriptc build server.ts -o server.
  • Project insights list support for filesystem, async operations, Promises, generators, timers, and stdin/readline, although coverage depends on the target platform.
  • Static builds contain no Node or JavaScript engine; unsupported or non-statically-compilable code is reported diagnostically.
  • Ordinary LLVM-tier executables still need a platform linker driver and SDK/sysroot. The precompiled runtime pack does not remove those build requirements.

A controlled HTTP service may therefore fit, while a service depending on the full Node ecosystem, dynamic loading, or unsupported APIs should not be migrated directly.

  • Use Node APIs: `import { createServer } from "node:http";`
  • Use Node APIs: `$ scriptc build server.ts -o server`
  • Installation: Ordinary LLVM-tier executable builds need a platform linker driver and SDK/sysroot
  • README: Code that cannot compile statically is reported as a diagnostic
$ scriptc build server.ts -o server
Not stated in the README:The README does not provide a complete compatibility matrix across Linux distributions, architectures, and individual Node APIs.;It does not specify performance or behavioral differences under concurrency, long-lived connections, or error-heavy workloads.

✨ Highlights

  • Supports IR, C, LLVM IR, assembly, and native executables
  • Static artifacts require neither Node nor a JavaScript engine at runtime
  • Can build WASI Preview 1 modules with Zig
  • The project is explicitly experimental and currently at v0.1.7

🔧 Engineering

  • Uses the TypeScript compiler for parsing and type checking, producing typed IR, C, LLVM IR, and native artifacts.
  • `--dynamic` can embed npm package JavaScript and `quickjs-ng` into an executable.
  • `scriptc coverage` reports coded diagnostics for each dynamic or unsupported site.

⚠️ Risks

  • The README calls scriptc experimental; the project has only 4 contributors and 5 releases.
  • `any`, npm packages, and code that cannot compile statically require `--dynamic` or produce diagnostics.
  • WASI Preview 1 does not support networking, child processes, OS signals, or filesystem watching.
  • `--emit=obj` is not a standalone library and contains undefined `scr_*` runtime references.

👥 For who?

  • Developers who need to compile TypeScript CLIs or Node.js APIs into programs that run without Node.
  • Developers experimenting with native compilation for macOS, Linux, Windows, or WASI Preview 1.
  • TypeScript teams that need `scriptc coverage` to inspect static coverage and dynamic-code boundaries.