scriptc三档编译模型全解析:静态编译、--dynamic与明确拒绝的设计哲学
【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc
scriptc是一款将 TypeScript/JavaScript 直接编译为原生可执行文件的编译器,产物中不含 Node、不含 V8、不含任何 JavaScript 引擎。它最有辨识度的设计是「三档编译模型」:每个语法结构要么静态编译为原生代码,要么通过--dynamic标志进入内嵌的quickjs-ng动态引擎执行,要么被明确拒绝并给出带错误码的诊断。没有任何代码会被悄悄误编译——这套「看得见静态性」的契约,正是 scriptc 与普通 TS 转原生工具最大的区别。
为什么需要三档:一个编译器的三种承诺
大多数「TS 转原生」方案要么把整个 JS 引擎塞进二进制(体积大、启动慢),要么对无法静态处理的代码默默降级(行为不可预期)。scriptc 选择了第三条路:
| 档位 | 触发条件 | 产物特征 |
|---|---|---|
| 🟢 静态编译 | 默认模式 | 纯原生代码,无引擎,体积约 320KB 量级 |
| 🟡 动态执行 | 显式加--dynamic | 内嵌 quickjs-ng 引擎(约 620KB),运行 npm 依赖与any类型代码 |
| 🔴 明确拒绝 | 既不能静态、也未开--dynamic | 编译期报错:SC 错误码 + 代码框 + 改写提示 |
关键在于「档位就是承诺」:留在第一档的程序,其 stdout、stderr 与退出码和同一文件在 Node 下运行逐字节一致;第二档的每个跨回静态代码的值都会在运行时校验,类型说谎会得到可捕获的TypeError而不是内存损坏。官方介绍页对这一模型的完整描述见 introduction/page.mdx。
第一档:静态编译 —— 默认且唯一的模式
不加任何标志时,scriptc 走的是纯静态路径。覆盖范围相当宽:
- 语言层面:单继承类与动态分派、闭包、泛型函数声明(单态化)、可辨识联合 + 类型收窄、
async/await(与 JS 精确一致的调度)、解构/展开/可选参数、迭代器、模板字符串等; - 标准库:UTF-16 语义的字符串、
bigint、Map/Set(JS 精确顺序)、JSON(带运行时校验的转换)、Math、Buffer与 TypedArray、Error层次结构; - Node API:
fs(同步 + promises)、path、child_process、crypto、url、timers,乃至完整的服务端栈net/http/https/tls/dgram/dns——真实的 HTTP 服务器可以直接编译成交付给客户的单文件二进制。
类型检查由真正的 TypeScript 编译器完成(es2025lib + 可选@types/node),你的tsconfig.json决定严格程度。架构细节(tsc 前端 → 类型化 IR → LLVM IR → 平台链接)在 how-it-works/page.mdx 中有完整图解。
第二档:--dynamic —— 把 JS 引擎变成一座"动态岛"
npm 依赖发布的是无类型、面向 V8 的 JavaScript,这是动态前沿。scriptc 的答案是动态岛(dynamic island):
$ npm install picocolors $ scriptc build cli.ts --dynamic -o cli $ ./cli hello from scriptc它的工作方式值得新手注意:
- 依赖按 Node 自身的解析规则从
node_modules解析,.d.ts作为类型表面供你检查; - 包的 JavaScript 在构建期嵌入二进制——运行时不读
node_modules,产物可在同平台任意机器上直接运行; - 静态值进入岛内按拷贝传递,动态值回到静态一侧时逐个校验,类型不符即抛可捕获的
TypeError。
一个容易误解的点:--dynamic不会「偷偷膨胀」普通构建。静态依然是默认值——没有这个标志,动态站点就是逐条的编译错误;即使开了标志但从未触达动态代码,产物与纯静态构建生成相同的代码。岛屿机制的编译端实现可参考 lower-island.ts。
第三档:明确拒绝 —— 宁可在编译期喊停
scriptc 的立场是「诚实是产品」。任何无法落地的代码都会在编译期被拒绝,附错误码、代码框和通常还有改写提示,常见几类:
SC2011:无--dynamic时出现any—— 提示改用unknown+ 受检转换,或显式开启引擎;SC2013:导入需要动态引擎的 npm 包但当前构建未包含它;SC2020:触达「类型检查可见、但无降级实现」的标准库表面(如部分regex/SymbolAPI),提示会给出受支持的替代写法;SC3002:WASI 目标下缺失能力的 API(网络套接字、子进程、OS 信号等),在链接前直接拒绝。
被拒绝 ≠ 项目失败,而是把「运行时玄学」提前为「编译期清单」。完整的拒绝面与「按设计偏离 Node 的行为」编号清单见 limitations/page.mdx。
用 scriptc coverage 亲眼看见三档分布
判断某个程序落在哪一档,不需要猜——scriptc coverage会逐语句给出带编码的诊断:
$ scriptc coverage cli.ts statements analyzed 4 compile statically 3 (75%) runs with --dynamic 2 sites (embeds a JS engine, ~620KB — static stays the default) ×1 importing 'picocolors' requires the embedded dynamic engine ... SC2013 ×1 values from the 'picocolors' package run in the embedded dynamic engine SC2013加--dynamic参数则回答另一个问题:如果开启动态引擎,这个构建会编译什么、还剩什么被挡住。覆盖率报告的实现入口在 coverage/report.ts。
新手三步上手:从静态到动态
安装(需要 Node.js 24+):
npm install -g scriptc静态构建并运行:
$ scriptc build hello.ts -o hello $ ./hello hello, world遇到依赖时加
--dynamic,用 coverage 复核边界:$ scriptc build tool.ts --dynamic -o tool $ scriptc coverage tool.ts --dynamic所有命令与选项(
--emit=ir|c|llvm|asm|obj|exe、--backend、--strip等)的完整参考见 cli/page.mdx。
设计哲学小结:为什么「拒绝」是特性
三档模型背后是一条清晰的原则链:
能静态就静态 → 不能静态就显式付费(引擎体积)→ 两者都不行就编译期喊停。
静态档位保证零引擎、毫秒级启动、逐字节与 Node 一致的行为;动态档位保证「真实世界依赖」无需改造即可嵌入;拒绝档位保证永远不存在沉默的误编译——每一处没编译的东西,都以编号、可复查的形式摆在台面上。对一个把「正确性当方法论」的编译器来说,明确拒绝不是能力的缺失,而是可信度的来源。
延伸资料
- 项目总览:README.md
- 三档模型与静态覆盖范围:docs/src/app/introduction/page.mdx
- 编译管线与运行时架构:docs/src/app/how-it-works/page.mdx
- npm 依赖与动态岛细节:docs/src/app/dependencies/page.mdx
- 拒绝清单与设计性偏离:docs/src/app/limitations/page.mdx
- 覆盖率分析器源码:packages/compiler/src/coverage/
【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考