Vercel 推出的 Scriptc:TypeScript 转原生代码编译器,二进制文件中不含 JavaScript 引擎。
Scriptc by Vercel: TypeScript-to-Native compiler, no JavaScript engine in binary

原始链接: https://github.com/vercel-labs/scriptc

**scriptc** 是一个零运行时的编译器,它将标准 TypeScript 转换为紧凑、高性能的本地可执行文件。通过消除对 Node.js 或 V8 引擎的需求,它实现了近乎即时的启动时间(约 2 毫秒),并显著降低了内存占用,同时保持了与 Node 完全一致的行为。 主要特性包括: * **静态优先方法:** 大多数 TypeScript 代码被直接编译为本地机器码。无法静态编译的应用程序部分(如动态 npm 依赖项)可以通过显式的 `--dynamic` 标志在轻量级嵌入式引擎(QuickJS)中运行。 * **兼容性:** 它支持完整的 TypeScript 语言、Node.js 标准 API(包括服务器和文件系统栈),并利用真实的 `tsc` 进行类型检查。 * **安全与验证:** 编译器通过严格的差异测试确保内存安全和正确性,保证输出结果与 Node.js 完全一致。它还提供针对 `as` 转换的运行时类型验证和精确的错误诊断。 * **性能:** 基准测试显示,其启动速度和内存使用率均优于 Node.js,可直接与 Go 和 Rust 等系统语言相媲美。 scriptc 专为生产效率而设计,为本地化部署提供了一条可预测、类型安全的路径,且无需手动添加代码注解或更改语言方言。

Hacker News 最新 | 往日 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Scriptc by Vercel:TypeScript 转原生编译器,二进制文件中不含 JavaScript 引擎 (github.com/vercel-labs) 21 分,maxloh 发布于 3 小时前 | 隐藏 | 往日 | 收藏 | 2 条评论 帮助 satvikpendem 5 分钟前 | 下一条 [–] 现在很多人都在尝试用 AI 做这件事,比如 TypeScript 原生编译器 https://github.com/PerryTS/pry。这是一个很有吸引力的价值主张,TypeScript 本身已经具备良好的类型系统,除了少数情况外,它可以直接编译成机器码而无需 JS 运行时。 回复 aabhay 11 分钟前 | 上一条 [–] 178kb?!你往里面塞了什么,一个 JVM 吗? 回复 考虑申请 YC 2026 年秋季班!申请截止日期为 7 月 27 日。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系方式 搜索:
相关文章

原文

Zero-runtime TypeScript. scriptc compiles ordinary TypeScript into small, fast native executables — no Node, no V8, no JavaScript engine in the binary.

$ cat fib.ts
function fib(n: number): number {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
console.log(fib(30));

$ scriptc run fib.ts
832040

$ scriptc build fib.ts && ls -la fib
-rwxr-xr-x  178K  fib        # a self-contained native binary, ~2ms startup

No changes to your code. No annotations, no dialect — the same TypeScript you run on Node, type-checked by the real TypeScript compiler and compiled to native. What compiles behaves byte-for-byte like Node.

Requires clang (preinstalled with Xcode Command Line Tools). macOS arm64 is the primary platform; Linux and Windows binaries build by cross-compilation, each verified by its own differential test lane.

The idea: staticness you can see

Most TypeScript is far more static than the ecosystem assumes. scriptc decides, construct by construct, what can compile to native code — and tells you:

$ scriptc coverage app.ts

  statements analyzed   4481
  compile statically    4451  (99%)

  blockers:
      ×2  functions with optional parameters as values   SC1090
      ×1  Promise.reject                                 SC2020

Three tiers, always explicit:

  1. Compiled statically — native code, no engine. The default, and the only mode unless you opt out.
  2. Runs dynamically (--dynamic) — an embedded JavaScript engine (quickjs-ng, ~620KB) executes what can't be static: npm dependencies' shipped JS, any-typed code. Every value crossing back into static code is validated at runtime — a lying type throws a catchable TypeError instead of corrupting memory.
  3. Rejected — everything else fails with a specific error code, a code frame, and usually a rewrite hint. Nothing is ever silently miscompiled.

The static surface covers the language and the standard library real programs use:

  • The language — classes with single inheritance and true dynamic dispatch (devirtualized when provably safe), closures with JS capture semantics, generics (monomorphized), discriminated unions as tagged values driven by TypeScript's own narrowing, async/await on stackful fibers with JS-exact scheduling, exceptions with finally, destructuring, spread, optional/default/rest parameters, getters/setters, iterators over strings/arrays/Maps/Sets, template literals, regular expressions (the engine is the same ECMAScript-exact bytecode interpreter QuickJS uses, linked only into regex-using binaries).
  • The standard library — strings with UTF-16-exact semantics, arrays/Maps/Sets with JS-exact ordering and identity, JSON with runtime-validated casts, Math, typed arrays and Buffer, Error hierarchies with typed catch.
  • Node's API surfacefs (sync and promises), path (byte-exact port), process, child_process with piped streams, os, crypto, url/URL, zlib, timers and signal handlers on a dependency-free event loop — and the server stack: net, http, https, tls (vendored mbedTLS), dgram, dns, fs.watch, readline. Real proxy servers compile.
  • fetch and the WHATWG web subset (streams, Headers, AbortSignal) over the same native net/TLS stack — redirects, gzip, AbortSignal.timeout, Node-shaped error causes; no libcurl, no system HTTP dependency.
  • npm dependencies (with --dynamic) — packages resolve with Node's own algorithm, typecheck against their shipped .d.ts, and their JS is embedded into the binary at build time. Binaries never read node_modules at runtime.

Programs typecheck against TypeScript's real es2025 lib (plus @types/node when your project has it), and your tsconfig.json governs checker strictness. Anything reached that has no lowering is a precise diagnostic, never a surprise.

Two enforcement mechanisms run on every change:

  • Differential testing — every corpus program (800+ tests) runs under Node and as a native binary; stdout, stderr, and exit codes must match byte-for-byte. Number formatting is JS-exact (shortest-roundtrip, fuzz-verified against Node on a million doubles). Servers are tested with live client drivers against both implementations.
  • Memory-safety lane — the entire corpus re-runs under AddressSanitizer with a reference-count audit; leaks and use-after-free are build failures.

The deliberate divergences from Node (there are a few dozen, mostly around timing internals and error-object properties) are documented and numbered; nothing diverges silently.

Measured on Apple M-series against the same workloads in Node, Go, Rust, and Zig (all byte-identical output, verified):

dimension scriptc context
startup ~2.4ms Node: ~47ms; on par with Zig, ahead of Go/Rust
binary size 170–200KB static, ~3MB with --dynamic + embedded deps Go: ~2MB; Node SEA: 60–100MB
memory (RSS) 1–4MB typical Node: 67–116MB
runtime JS-faithful f64 semantics; competitive with the systems languages on most workloads integer inference and ownership analysis are on the roadmap

Escape hatches, honestly priced

  • comptime(() => ...) runs TypeScript at build time (in an isolated VM inside the compiler) and bakes the result into the binary as a literal.
  • Native FFI (--ffi) binds signature-only TypeScript declarations to direct C ABI calls and links manifest-declared archives, objects, and system libraries. The boundary is explicit and length-delimited; see the Native FFI guide.
  • --dynamic embeds the engine for npm deps and any code. scriptc coverage --dynamic reports exactly which statements run where and what the remaining blockers are. Static stays the default: a binary never silently grows an engine.
  • Checked castsJSON.parse(...) as Config inserts a runtime validation that throws a catchable error naming the offending path (expected number at $.port, got string). TypeScript's as is a promise; scriptc verifies it.
flowchart LR
    TS[TypeScript] -->|tsc: parse + typecheck| L[lowering]
    L --> IR[typed IR]
    IR --> C[C]
    C -->|clang| BIN[native executable]
Loading
  • packages/compiler — frontend (tsc API → IR), the IR with validator/serializer, the LLVM and C backends. The IR is the only interface between the ends; LLVM is the default code generator (with a transparent fallback for programs outside its tier), and C is the reference backend forever (readable, source-line-annotated output via --backend c).
  • packages/runtime — the C runtime: refcounted values with a cycle collector, stackful fibers and the event loop (kqueue), the server stack, JS-exact number formatting. Feature units are link-gated: binaries pay only for what they use.
  • packages/cliscriptc build | run | coverage.
$ pnpm install && pnpm build
$ pnpm test                      # differential corpus + diagnostics snapshots
$ SCRIPTC_SAN=1 pnpm test        # the same corpus under ASan + RC audit
$ pnpm scriptc build x.ts --emit-ir   # keep .scriptc/x.c and x.ir.json

Every feature lands with differential tests; both lanes green is the merge bar.

联系我们 contact @ memedata.com