scriptc与Node行为差异清单:每个迁移团队必须知道的10个文档化分歧
【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc
scriptc 是一个 TypeScript 转原生可执行文件的编译器(TypeScript-to-Native Compiler),它把普通 TypeScript 直接编译为不依赖 Node 和 V8 的原生二进制,启动只要几毫秒、体积约 320KB。正因为代码离开了 V8 运行,scriptc 与 Node 之间必然存在行为差异——好消息是,每一个差异都被文档化、编号化,并锁定在差异测试(differential testing)中,绝不存在"悄悄不一致"。本文整理出迁移团队必须知道的 10 个核心分歧。
差异清单在哪里查?3 个权威入口
在逐条拆解之前,先告诉你去哪验证:
- 官方限制页:limitations/page.mdx 的 "What diverges by design" 一节是差异清单的原文,每一项都有"为什么"。
- Node 兼容性台账:internal/compatibility/README.md 维护了针对 Node v24.15.0(见 node-v24.json 中的版本与 commit 固定)的逐项 API 支持状态,其中 static-support.json 为每个 API 附上了仓库内的测试证据路径。
- 差异测试套件:语料库里的每个程序都会同时在 Node 和 scriptc 原生二进制下运行,stdout、stderr、退出码必须逐字节一致,逻辑见 differential.test.ts。
10 个文档化分歧逐项拆解
1️⃣ "撒谎的 cast"会抛错,而不是给你垃圾数据
这是 scriptc 最核心的设计分歧。在 Node 中,JSON.parse(s) as Config类型不匹配时会静默地把垃圾值交给你;在 scriptc 中,这类动态数据上的类型断言会插入运行时校验(checked cast),数据不符时抛出可捕获的错误,并明确指出出错路径:
caught: expected number at $.port, got string迁移提示:如果代码依赖"先强转、后运行时报错"的 Node 习惯,迁移后错误会更早、更具体地暴露——这是特性,不是缺陷。
2️⃣ 普通数组与 TypedArray 的越界行为不同
- 普通数组:scriptc 与 Node 一致——读不存在的下标、对空数组
pop()/shift()都返回undefined,写越界下标会扩长数组并产生"空洞"。 - TypedArray:越界索引触发进程中止(trap),而不是像 Node 那样返回
undefined。
3️⃣ 部分运行时故障不可被 try/catch 捕获
用户throw、JSON 解析错误、checked-cast 失败、fs 错误、正则错误等,scriptc 都抛出真实可捕获的Error对象(与 Node 一致)。但残留的硬 trap——典型如 TypedArray 越界——会直接中止进程,无法用catch兜底。涉及大量数值边界的代码需重点回归。
4️⃣ 记录(Record)是精确结构体,且"宽度子类型"会拷贝
- 把
{a, b}传给期望{a}的位置是编译错误 SC2002,Node 则允许。 - 当编译器接受字段子集流转时,记录会被拷贝而非别名:通过更窄引用的修改对原对象不可见。动态边界同理——值以拷贝跨界,从不传引用。
迁移提示:依赖"同一对象两个引用、改一处见一处"的共享可变对象逻辑,需要审视。
5️⃣ Object.keys / JSON.stringify 按声明顺序,而非插入顺序
Node 按属性插入顺序枚举;scriptc 按记录的声明顺序报告。两种顺序在"对象按声明顺序构建"的场景(绝大多数业务代码)完全一致;只有动态重排键顺序的代码会观察到差异。
6️⃣ 字符串以 UTF-8 存储,比较语义有细微差别
字符串的.length和方法都是精确的 UTF-16 语义,肉眼不可见。但有两处例外:
- 关系比较
<>使用码点顺序(code-point order); - 会拆分代理对的运算(surrogate-splitting)会产出 U+FFFD 替代字符。
涉及 Unicode 排序、字符串截取的工具型代码建议用 tests/corpus 中的字符串用例做对照回归。
7️⃣ JSON.stringify/parse 回调只有"原生子集"
replacer/reviver 函数可静态执行(含嵌套替换、属性删除、抛异常),但有边界:replacer 属性列表、reviver 源上下文、运行时计算的缩进不受支持;回调中的值走 checked-dynamic 边界——记录与数组会变成快照,回调内修改不会回写原容器。数组元素在 reviver 中删除会抛错(动态数组无法表示空洞)。
8️⃣ Date 解析与数据是"有界"的
new Date(单字符串)和Date.parse只接受 ECMAScript 日期形式、带显式Z/数字偏移的日期时间,以及受支持 X509 面返回的 GMT 证书字符串;V8 专有的宽松格式会返回Invalid Date或NaN(Node 可能解析成功)。- 本地日历取本地化时使用操作系统的时区数据库,与 Node 自带时区数据不一致时历史结果可能不同。
9️⃣ process 外观差异:argv、崩溃输出、错误对象
process.argv[0]是"scriptc"而非 Node 路径,argv[1]是二进制自身路径(argv[2]起才是你的参数,位置与 Node 对齐)。- 未捕获异常输出为单行
Uncaught <value>,不是 Node 的堆栈块(退出码与抛错前的 stdout 完全一致)。 - 运行时错误对象带
message和 Node 的code,但没有errno/syscall/path字段。
相关测试证据:tests/corpus/process-versions-storage.ts。
🔟 排序与字符串排序算法不同,localeCompare 语义不同
sort/toSorted的比较器调用序列与 Node 不同(scriptc 用稳定自底向上归并排序,V8 用 TimSort)——但对一致性比较器,排序结果逐字节相同。localeCompare比较的是码元,而非 Node 背后的 ICU 排序规则。依赖 ICU 本地化排序顺序的业务逻辑需人工验证。
迁移前必做:3 步验证流程
- 跑 coverage:
scriptc coverage 你的入口文件会输出逐语句的静态/动态/拒绝分层,每个阻断项都有具体错误码。 - 编译一遍:
scriptc build得到的错误列表永远比文档页更新——编译不过的项都会给SC错误码 + 代码帧 + 改写提示,包括any无--dynamic时的 SC2011(这是编译错误而非运行差异,但同样必须处理)。 - 看差异矩阵:文档站的 Node 兼容性矩阵(node-compatibility-matrix.tsx)按 Node v24 API 章节逐项展示 supported / partial / refused 等状态,状态本身就有证据要求——
supported与partial必须附带测试证据。
总结
scriptc 的态度是"诚实即产品":静态层程序与 Node 产生逐字节一致的 stdout 和相同退出码,所有已知差异都是刻意的、编号的、被差异测试锁定的。对迁移团队而言,这份 10 项清单不是"风险黑箱",而是一张可逐项核对、逐项回归的迁移检查表。
📌核心资料索引
- 差异清单原文:docs/src/app/limitations/page.mdx
- 兼容性台账说明:internal/compatibility/README.md
- 编译器源码(类型世界与降级逻辑):packages/compiler/src/
- 运行时实现:packages/runtime/src/
- 差异测试驱动:tests/harness/differential.test.ts、tests/harness/node-compatibility.test.ts
【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考