scriptc 技术解读:Vercel 的 TypeScript 原生编译器,零运行时二进制

scriptc: TypeScript-to-Native Compiler

Vercel Labs 在 2026 年 7 月发布了 scriptc,一个将普通 TypeScript 编译为原生可执行文件的编译器。不需要 Node,不需要 V8,二进制文件里不嵌入任何 JavaScript 引擎。一段递归求斐波那契数列的 TypeScript,编译后生成的原生二进制只有 178KB,启动时间约 2 毫秒。同样的代码在 Node 上运行,需要加载约 120MB 的运行时,启动需要约 35 毫秒。

这个项目的核心论点在于:大部分 TypeScript 代码远比生态系统想象的要"静态"。scriptc 逐条语句地判断哪些可以编译为原生代码,哪些不行,并且把结果告诉你。

三层编译策略

scriptc 将程序中的每一个构造明确归入三层之一,层与层之间不存在灰区:

第一层:静态编译(默认)。普通的 TypeScript 代码直接编译为原生机器码,二进制文件中不含任何 JavaScript 引擎。覆盖范围包括:

  • 语言层面:带单继承和动态分发的类(在可证明安全时去虚化)、带 JS 捕获语义的闭包、泛型(单态化)、判别联合类型(作为标签值,由 TypeScript 自身的类型窄化驱动)、基于栈式协程的 async/await(保持与 JS 完全一致的调度语义)、带 finally 的异常处理、解构、展开、可选参数/默认参数/rest 参数、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 web 子集(streams、Headers、AbortSignal)运行在同一套原生网络/TLS 栈之上,支持重定向、gzip、AbortSignal.timeout、Node 格式的错误 cause。不依赖 libcurl,不依赖系统 HTTP 库。

第二层:动态运行(可选)。通过 --dynamic 标志启用。一个内嵌的 JavaScript 引擎(quickjs-ng,约 620KB)负责执行无法静态化的部分:npm 依赖的 JavaScript 代码、any 类型的代码。每个从动态层回流到静态代码的值都会在运行时被验证,一个类型撒谎会抛出一个可捕获的 TypeError,而不是导致内存损坏。

第三层:编译期拒绝。所有其他代码在构建时失败,附带一个具体的错误代码、一个代码框、通常还有一个重写提示。没有任何东西会被静默地错误编译。

覆盖率报告:看得见的静态化

scriptc 提供了 coverage 命令,让开发者看到代码中每一条语句的归属:

text
$ scriptc coverage app.ts

  statements analyzed   4481
  compile statically    4451  (99%)

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

在一个 4481 条语句的测试程序中,4451 条(99%)可以静态编译。剩余 3 条被明确标记,每条都附带错误代码。这种设计意味着二进制文件不会静默膨胀——动态层是可选的,覆盖率报告会点明每一个需要它的位置。

架构设计

mermaid
flowchart LR
    TS[TypeScript] -->|tsc: parse + typecheck| L[lowering]
    L --> IR[typed IR]
    IR --> C[C]
    C -->|clang| BIN[native executable]

scriptc 的编译管线分为三个包:

  • packages/compiler:前端(通过 tsc API 解析和类型检查 → IR),IR 及其验证器和序列化器,LLVM 和 C 后端。IR 是前后端之间的唯一接口。LLVM 是默认代码生成器(对不在其层级的程序有透明回退),C 是永久参考后端(通过 --backend c 输出可读的、带源代码行注释的产物)。

  • packages/runtime:C 运行时。引用计数的值(带循环回收器)、栈式协程和事件循环(kqueue)、服务器栈、JS 精确的数字格式化。功能单元按链接门控:二进制文件只为它用到的东西付费。

  • packages/cliscriptc build | run | coverage

性能数据

scriptc 在 Apple M 系列芯片上与 Node、Go、Rust、Zig 运行相同工作负载(所有输出逐字节验证一致)进行对比:

维度scriptc对比基准
启动时间~2.4msNode: ~47ms;与 Zig 相当,领先 Go/Rust
二进制大小静态 170-200KB,--dynamic + 嵌入依赖约 3MBGo: ~2MB;Node SEA: 60-100MB
内存占用 (RSS)典型 1-4MBNode: 67-116MB
运行时JS 忠实的 f64 语义,大多数工作负载上与系统语言竞争整数推理和所有权分析在路线图上

一个 hello-world 二进制约 320KB,启动约 4 毫秒,只链接 libSystem。Node 需要约 120MB 运行时和约 35 毫秒才能打印同一行。

正确性保证

scriptc 用两套机制在每次代码变更时强制正确性:

差分测试。所有语料库程序(800+ 测试)同时在 Node 和原生二进制下运行,stdout、stderr 和退出码必须逐字节匹配。数字格式化是 JS 精确的(最短往返,在百万个双精度浮点数上对 Node 进行模糊测试验证)。服务器用实时客户端驱动程序针对两种实现进行测试。

内存安全通道。整个语料库在 AddressSanitizer 下重新运行,并附带引用计数审计。内存泄漏和释放后使用是构建失败。

对 Node 的有意分歧(有几十个,主要围绕时序内部和错误对象属性)被文档化并编号,没有任何分歧是静默的。

逃生舱口

scriptc 提供了三个高级功能来处理静态编译覆盖不到的场景:

comptime(() => ...) 在构建时运行 TypeScript(在编译器内部的隔离 VM 中),将结果作为字面量烘焙进二进制文件。

Native FFI (--ffi) 将仅含签名的 TypeScript 声明绑定到直接的 C ABI 调用,并链接清单声明的归档文件、目标文件和系统库。边界是显式的、长度限定的。

Checked castsJSON.parse(...) as Config 会插入运行时验证,抛出一个可捕获的错误并指出出错的路径(如 expected number at $.port, got string)。TypeScript 的 as 是一个承诺,scriptc 验证它。

与 TypeScript 7 的关系

2026 年 7 月 8 日,微软发布了 TypeScript 7.0,用 Go 重写的原生编译器,在完整构建上通常带来 8-12 倍的加速。scriptc 与 TypeScript 7 是正交的方向:TypeScript 7 让编译器本身更快,scriptc 把 TypeScript 程序编译成原生可执行文件。scriptc 使用 TypeScript 自身的 es2025 lib 进行类型检查(当项目有 @types/node 时也会使用),tsconfig.json 控制检查器的严格度。

安装和使用

bash
$ npm install -g scriptc

需要 clang(macOS 上随 Xcode Command Line Tools 预装)。macOS arm64 是主要平台,Linux 和 Windows 二进制通过交叉编译构建,各有独立的差分测试通道验证。

基本用法:

bash
$ scriptc run fib.ts          # 直接运行
$ scriptc build fib.ts -o fib # 编译为原生二进制
$ scriptc coverage app.ts     # 查看静态化覆盖率

技术路线

scriptc 目前的短板在整数推理和所有权分析上(路线图中)。当前的运行时使用 JS 忠实的 f64 语义,这在需要精确浮点行为的场景下是优势,但在整数密集型计算上可能不如原生系统语言高效。

项目采用 Apache 2.0 许可证,仓库地址 github.com/vercel-labs/scriptc。这是 Vercel Labs 的实验性项目,截至发文时创建仅 5 天,star 约 500,还没有广泛的生产采用案例。但它提出的"把 TypeScript 当静态语言来编译"的思路,以及差分测试 + AddressSanitizer 的正确性工程实践,对 TypeScript 生态和原生编译领域都有参考价值。