Vercel 发布 Scriptc:TypeScript 到原生代码编译器,二进制文件不包含 JavaScript 引擎
摘要
Vercel Labs 发布了 Scriptc,这是一款编译器,能将普通 TypeScript 代码转换为小巧、快速的原生可执行文件,无需在二进制文件中包含 Node、V8 或任何 JavaScript 引擎。它支持 TypeScript 的大部分静态特性及 Node 的 API 接口。
查看缓存全文
缓存时间: 2026/07/27 04:38
vercel-labs/scriptc
来源:https://github.com/vercel-labs/scriptc
scriptc
零运行时 TypeScript。 scriptc 将普通的 TypeScript 编译成小巧、快速的原生可执行文件——二进制文件中不含 Node、不含 V8、不含任何 JavaScript 引擎。
$ 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 # 自包含的原生二进制文件,启动约2ms
无需修改你的代码。无需注解、无需方言——你在 Node 上运行的同一种 TypeScript,由真正的 TypeScript 编译器进行类型检查,然后编译成原生代码。编译后的行为与 Node 逐字节一致。
安装
$ npm install -g scriptc
需要 clang(Xcode 命令行工具已预装)。macOS arm64 是主要平台;Linux 和 Windows 二进制文件通过交叉编译生成,每个平台都经过独立的差异测试通道验证。
理念:可见的静态性
大多数 TypeScript 的静态程度远超生态系统所假设的。scriptc 逐个构造地决定哪些可以编译为原生代码——并告知你:
$ scriptc coverage app.ts
分析的语句数 4481
静态编译的语句数 4451 (99%)
阻碍项:
×2 作为值使用的可选参数函数 SC1090
×1 Promise.reject SC2020
三个层次,始终清晰:
- 静态编译——原生代码,无引擎。这是默认模式,也是你未选择退出时的唯一模式。
- 动态运行(
--dynamic)——一个嵌入式 JavaScript 引擎(quickjs-ng (https://github.com/quickjs-ng/quickjs),约620KB)执行无法静态处理的部分:npm 依赖项附带的 JS、any类型代码。每个返回静态代码的值都在运行时被验证——虚假的类型会抛出可捕获的TypeError,而不是破坏内存。 - 拒绝——其他所有情况都会以特定错误码、代码帧和通常的重写提示失败。绝不会出现静默的错误编译。
编译范围
静态覆盖范围包括真实程序使用的语言和标准库:
- 语言——支持单继承和真实动态分发(在可证明安全时去虚拟化)、具有 JS 捕获语义的闭包、泛型(单态化)、由 TypeScript 自身的窄化驱动的带标签值的受歧视联合、基于栈式纤程的异步/等待及 JS 精确调度、带
finally的异常、解构、展开、可选/默认/剩余参数、getter/setter、字符串/数组/Map/Set 的迭代器、模板字符串、正则表达式(引擎是与 QuickJS 使用的相同 ECMAScript 精确字节码解释器,仅链接到使用正则的二进制文件中)。 - 标准库——具有 UTF-16 精确语义的字符串、具有 JS 精确顺序和恒等性的数组/Map/Set、带运行时验证类型转换的
JSON、Math、类型化数组和Buffer、带类型化catch的Error层级。 - Node 的 API 表面——
fs(同步和 promise)、path(字节精确移植)、process、带管道流的child_process、os、crypto、url/URL、zlib、在无依赖事件循环上的定时器和信号处理器——以及服务器栈:net、http、https、tls(内联 mbedTLS)、dgram、dns、fs.watch、readline。真实的代理服务器可以编译。 fetch和 WHATWG 网页子集(流、Headers、AbortSignal)运行在同一原生网络/TLS 栈上——支持重定向、gzip、AbortSignal.timeout、Node 形状的错误原因;没有 libcurl,没有系统 HTTP 依赖。- npm 依赖项(配合
--dynamic)——包使用 Node 自身的算法解析,针对它们附带的.d.ts进行类型检查,它们的 JS 在构建时嵌入到二进制文件中。二进制文件在运行时永远不会读取node_modules。
程序针对 TypeScript 真正的 es2025 库(如果项目有 @types/node 则加上)进行类型检查,你的 tsconfig.json 控制检查器的严格程度。任何没有降级的代码都会生成精确的诊断,绝不会意外出错。
正确性
每次变更都运行两个强制机制:
- 差异测试——每个语料库程序(800+ 个测试)同时在 Node 和原生二进制文件下运行;stdout、stderr 和退出码必须逐字节匹配。数字格式与 JS 精确一致(最短往返,在一百万个双精度浮点数上通过模糊测试与 Node 验证)。服务器通过实时客户端驱动程序对两种实现进行测试。
- 内存安全通道——整个语料库在 AddressSanitizer 下重新运行,并附带引用计数审计;内存泄漏和释放后使用均视为构建失败。
与 Node 的故意差异(有几十个,主要涉及计时内部机制和错误对象属性)都已记录并编号;不会出现静默的差异。
性能
在 Apple M 系列上测量,与 Node、Go、Rust 和 Zig 上的相同工作负载对比(所有输出逐字节相同,已验证):
| 维度 | scriptc | 上下文 |
|---|---|---|
| 启动时间 | ~2.4ms | Node:~47ms;与 Zig 相当,领先于 Go/Rust |
| 二进制大小 | 静态 170–200KB,--dynamic + 嵌入依赖约 3MB | Go:~2MB;Node SEA:60–100MB |
| 内存 (RSS) | 通常 1–4MB | Node:67–116MB |
| 运行时 | 忠实于 JS 的 f64 语义;在大多数工作负载上与系统语言相竞争 | 整数推断和所有权分析已在路线图上 |
逃生舱口
comptime(() => ...)在构建时运行 TypeScript(在编译器内部的一个隔离 VM 中),并将结果以字面量的形式烘焙到二进制文件中。- 原生 FFI(
--ffi) 将仅含签名的 TypeScript 声明绑定到直接的 C ABI 调用,并链接清单中声明的归档文件、对象文件和系统库。边界是显式的且长度定界;请参见原生 FFI 指南 (https://scriptc.dev/ffi)。 --dynamic嵌入引擎以处理 npm 依赖和any代码。scriptc coverage --dynamic精确报告每条语句在何处运行,以及剩余的阻碍项。静态仍然是默认选项:二进制文件绝不会悄无声息地增长出引擎。- 检查类型转换——
JSON.parse(...) as Config会在运行时插入验证,如果类型不符则抛出可捕获的错误,并指明违规路径(例如expected number at $.port, got string)。TypeScript 的as是一种承诺;scriptc 会验证它。
架构
flowchart LR
TS[TypeScript] -->|tsc: parse + typecheck| L[lowering]
L --> IR[typed IR]
IR --> C[C]
C -->|clang| BIN[native executable]
packages/compiler—— 前端(tsc API → IR)、包含验证器/序列化器的 IR、LLVM 和 C 后端。IR 是两端之间的唯一接口;LLVM 是默认代码生成器(对于其层级之外的程序有透明回退),C 是永久参考后端(可通过--backend c输出可读的、带源代码行注释的代码)。packages/runtime—— C 运行时:带循环垃圾收集器的引用计数值、栈式纤程和事件循环(kqueue)、服务器栈、JS 精确数字格式化。特性单元按链接门控:二进制文件只为使用的功能付费。packages/cli——scriptc build | run | coverage。
开发
$ pnpm install && pnpm build
$ pnpm test # 差异测试语料库 + 诊断快照
$ SCRIPTC_SAN=1 pnpm test # 在 ASan + RC 审计下的相同语料库
$ pnpm scriptc build x.ts --emit-ir # 保留 .scriptc/x.c 和 x.ir.json
每个特性都附带差异测试;两条通道均为绿色才能合并。
相似文章
@rauchg: 我使用 scriptc 将 𝚟𝚎𝚛𝚌𝚎𝚕 CLI TypeScript 编译为本机代码。难以置信。✓ 生成的二进制大小:1.28MB ✓ Sta…
Guillermo Rauch 使用 scriptc 将 Vercel CLI TypeScript 编译为本机代码,实现了 1.28MB 的二进制文件、1.5ms 的启动开销和 2.94s 的编译时间。scriptc 是一个用 TypeScript 编写的 TypeScript 到本机代码的编译器,完全兼容 Node.js。
LemmaScript:通过 Dafny 验证 TypeScript 的工具链
LemmaScript 是一套全新工具链,可将 TypeScript 编译为 Dafny 进行形式化验证,无需改动运行时,并已通过验证 Hono 框架中一个 CVE 修复实例加以演示。
Perry 使用 SWC 和 LLVM 直接将 TypeScript 编译为可执行文件
Perry 使用 SWC 和 LLVM 将 TypeScript 直接编译为原生可执行文件,生成无运行时依赖的小型二进制文件,支持所有主流平台和原生 UI 组件。
TypeScript 7
TypeScript 7 是一个重大版本,它将编译器用 Go 语言重写,实现了 8-12 倍的构建速度提升,同时保持完全兼容性,现已在 npm 上可用。
microsoft/TypeScript
TypeScript 是一种用于大规模 JavaScript 应用的语言,它增加了可选类型。该仓库托管了 TypeScript 编译器及相关工具。