CLI tool
vercel-labs/scriptc avatar
vercel-labs/scriptc

scriptc: Compiling Real TypeScript to Native Code Without Annotations

TypeScript-to-Native Compiler. No annotations, no dialect, the same TypeScript you run on Node, type-checked by the real TypeScript compiler and compiled to native.

4,834 stars125 forksTypeScriptApache-2.0

At a glance

What is it?
scriptc from Vercel Labs compiles standard TypeScript to native executables, C, LLVM IR, and WebAssembly. It relies on the real TypeScript compiler for type checking and a small native runtime, with dynamic fallback through quickjs-ng for npm packages.
Who is it for?
Adopt scriptc if you want to ship standalone native executables from ordinary TypeScript, especially on macOS 15+ arm64 where the bundled helper removes the need for a compiler toolchain. Avoid it if you need full Node API coverage, dynamic npm packages without the quickjs-ng embedded engine, or mature production stability, given its experimental status and frequent releases.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 14, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What scriptc Solves and Who It Targets

scriptc addresses a specific gap: TypeScript developers who want native executables without rewriting their code in Rust, Go, or C. The project description promises "no annotations, no dialect," meaning you write the same TypeScript you would run on Node, and the compiler handles the rest. The target audience includes CLI tool authors and server-side developers who want to distribute a single binary without requiring users to install Node. The README emphasizes that the compiler uses the real TypeScript compiler for parsing and type checking, which is a significant departure from many transpilers that implement their own type system or ignore types entirely. This positions scriptc for projects where type safety is non-negotiable and where the existing TypeScript toolchain must remain the source of truth.

The Compilation Pipeline and Runtime Model

The core mechanism is a multi-stage compiler that lowers TypeScript to a typed IR, then to C, LLVM IR, assembly, object files, or a native executable. The README shows that `--emit` accepts `ir`, `c`, `llvm`, `asm`, and `obj`, each producing a corresponding artifact in a `.scriptc/` directory. For executable builds, the compiler uses a bundled helper on macOS 15+ arm64 that generates assembly and object files with a deployment target of `arm64-apple-macosx14.0.0`. Crucially, clang is only used as a linker driver, not to compile program or runtime C. The runtime is a small native component that implements supported Node APIs, and static builds include this runtime but no JavaScript engine. For code that cannot compile statically, such as npm packages or `any`-typed code, the `--dynamic` flag embeds quickjs-ng, a JavaScript engine, directly into the executable. This dual-path approach means the compiler must decide at build time whether each piece of code can be statically compiled or needs dynamic interpretation, which is a fundamental design trade-off.

Getting Started: Commands and Configuration

Installation is straightforward via npm: `npm install -g scriptc`. The compiler requires Node.js 24 or newer. A minimal example from the README creates `hello.ts`, reads `process.argv`, and prints a greeting. Running `scriptc run hello.ts` compiles and executes it in one step. To produce a standalone executable, use `scriptc build hello.ts -o hello` and then run `./hello`. For source-level artifacts, you can stop at any stage: `--emit=ir`, `--emit=c`, `--emit=llvm`, `--emit=asm`, or `--emit=obj`. The README notes that `--emit=obj` writes a relocatable object with undefined `scr_*` references and requires a `scr_runtime_abi_v1` marker; for a self-contained archive, you must use `scriptc build --lib --profile`. The build command supports environment variables like `SCRIPTC_CC` and `SCRIPTC_TARGET` for cross-compilation, as shown in the WebAssembly example. These are real, documented commands that any user can run.

WebAssembly and Cross-Target Support

scriptc can target WebAssembly via WASI Preview 1, but this requires Zig, not just Node. The README shows setting `SCRIPTC_CC=zigcc` and `SCRIPTC_TARGET=wasm32-wasi` before building or running. The resulting module is a standard WASI binary, verified with the `file` command. The WASI target supports the same executable language tiers as native targets, including async/await, promises, generators, timers, and filesystem APIs. However, APIs that require capabilities absent from portable WASI Preview 1, such as network sockets, child processes, OS signals, and filesystem watching, fail before linking with a specific diagnostic code `SC3002`. This is a clear limitation: you cannot build a full server or CLI that relies on networking for WASI. The README also notes that sanitizer builds, native FFI, and library-mode archive builds are not supported on this target. This makes WASI a viable option only for compute-heavy or filesystem-only workloads.

Node API Coverage and the Dynamic Fallback

A key selling point is that supported Node APIs compile to the native runtime. The README gives a complete example using `node:http` to create a server, and the built executable runs it directly. This is impressive because it means the runtime implements HTTP server functionality natively, not by embedding Node. However, the README also states that code which cannot compile statically is reported as a diagnostic, and `scriptc coverage` shows the percentage of statements that compile statically. For npm packages, the `--dynamic` flag embeds quickjs-ng, meaning the package's JavaScript is interpreted at runtime inside the executable. The result does not read `node_modules` at runtime, which is good for distribution, but it also means you are shipping a JavaScript engine. This is a trade-off: static compilation offers performance and small binaries, but dynamic embedding adds overhead and complexity. The coverage tool is a practical way to assess whether a given codebase will benefit from static compilation or fall back to dynamic mode.

Limitations and Failure Modes

scriptc is explicitly experimental, and the README lists several concrete limitations. On macOS, the helper only works on arm64 and macOS 15 or newer; on other platforms, you need a C compiler and linker for executable builds. The `--emit=obj` output is not a standalone library and requires a specific ABI marker, which complicates external consumption. Sanitized assembly and object emission is rejected until the helper's AddressSanitizer pipeline matches the executable path, so you cannot use ASan with those artifacts. For WASI, network and process APIs fail with `SC3002`. The dynamic fallback uses quickjs-ng, which may not support all modern JavaScript features or match V8's behavior exactly, so npm packages that rely on edge cases might break. The README also mentions that external object consumption is experimental, with a `--print=native-link-info` option to emit a JSON recipe, but this is clearly not for casual use. These limitations mean scriptc is not a drop-in replacement for Node in all scenarios.

Maintenance, Upgrades, and Licensing

The project is under active development, with three releases within a week (v0.0.33, v0.0.34, v0.0.35) and the latest push in August 2026. This rapid release cadence suggests frequent bug fixes and feature additions, but also means you should expect breaking changes between versions. The README does not provide a migration guide, so upgrading may require adjusting build commands or code. The license is Apache-2.0, which is permissive for use and modification, but you should be aware that the project embeds quickjs-ng, which has its own license (Mozilla Public License 2.0 or GPL, depending on the version). This could have implications for distribution if you use `--dynamic`. The development workflow relies on Vercel's sandbox infrastructure and requires a `VERCEL_OIDC_TOKEN`, which is unusual for an open-source project and may hinder local contributions. Overall, the maintenance cost is moderate: you need to track releases and test your builds against each new version, but the tool itself is designed to be simple to use.

Alternatives and Comparative Approach

The most obvious alternative is to use a different compiler like `tsc` with a bundler like `esbuild` or `bun build` to create a standalone JavaScript executable that embeds Node or a JavaScript runtime. For example, `bun build --compile` produces a native binary that includes a JavaScript engine, but it does not perform static compilation of your code; it just bundles and embeds the runtime. Node.js itself offers `pkg` or `nexe` to package your application into an executable, but again, these embed Node and do not compile your TypeScript to native code. In contrast, scriptc's approach is to compile as much as possible to native code, only falling back to an embedded engine when necessary. This is a fundamental difference: scriptc aims to eliminate the runtime for static code, while alternatives simply relocate it. For projects where startup time and binary size are critical, scriptc's static compilation could offer advantages, but the trade-off is the complexity of the compiler and the need to verify that your code compiles statically.

Editorial conclusion

Adopt scriptc if you want to ship standalone native executables from ordinary TypeScript, especially on macOS 15+ arm64 where the bundled helper removes the need for a compiler toolchain. Avoid it if you need full Node API coverage, dynamic npm packages without the quickjs-ng embedded engine, or mature production stability, given its experimental status and frequent releases. Before adopting, verify your specific Node APIs and npm dependencies against the platform support and limitations pages, and check that your target OS and architecture match the supported set. Run `scriptc coverage` on your entry point to see the static compile ratio and any diagnostic codes. The project is under active development, so pin the version you test with.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Community notes

Community notes