☰
为什么不用Deno compile?深入解析scriptc编译策略与内嵌运行时的本质区别
2026/9/29 5:16:50 网站建设 项目流程

为什么不用Deno compile?深入解析scriptc编译策略与内嵌运行时的本质区别

【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc

scriptc是一个把 TypeScript / JavaScript 编译成原生可执行程序的编译器,而 Deno compile 走的是"内嵌 JavaScript 引擎"的路线。两者产出的可执行文件看起来一样,但内部机制、体积和启动方式有本质区别。这篇文章带你用大白话搞清楚:scriptc 的编译策略、它的内嵌运行时(动态岛)到底在什么时候出现,以及为什么它和 Deno compile 不是同一种东西 🧭。

一、先搞清楚:Deno compile 到底做了什么?

deno compile的思路很直接:把你的源码和整个 Deno 运行时(包含 V8 JavaScript 引擎)一起打包进一个二进制文件。

也就是说:

  • 引擎在内,代码在内——程序运行时,仍然是一个 JavaScript 引擎在执行你的代码,只是这个引擎被"冻"进了二进制里;
  • 产物体积通常在几十 MB 量级,启动时需要初始化整个 JS 引擎;
  • 你的代码没有被"编译",它只是被快照化后原样塞进了引擎。

这解决了"不用装 Deno 就能运行"的问题,但并没有把 TypeScript 变成原生代码。

二、scriptc 的路线:把 TypeScript 编译成原生代码

scriptc 选择了一条完全不同的路:借助真正的 TypeScript 编译器做解析和类型检查,然后把你写的代码逐层降低为带类型的中间表示,最终生成LLVM IR → 原生汇编 → 可执行文件。

用官方文档的流水线图来表达就是(见 docs/src/app/how-it-works/page.mdx):

TypeScript ──tsc 解析+类型检查──▶ 类型化 IR ──▶ LLVM IR ──▶ 原生汇编/目标文件 ──▶ 可执行文件

几个值得注意的结果:

  • 产物是一个自包含的原生二进制(典型体积约 320KB),只链接系统 C 库,没有 Node、没有 V8、没有 JavaScript 引擎;
  • 启动时间在毫秒级——官方基准中,同一句console.log,scriptc 二进制约 4ms 启动,Node 约 35ms;
  • 类型在编译期就已确定:泛型被单态化、联合类型变成带标签的值、闭包有显式捕获,运行时不再需要"猜"类型。

编译器和后端的核心实现分别位于 packages/compiler(前端 + 类型化 IR + C/LLVM 后端)和 native/llvm-codegen(LLVM 22 侧车,负责优化与目标代码生成)。

三、内嵌运行时 vs 原生运行时:本质区别

对比维度Deno compilescriptc
核心机制内嵌 V8 引擎执行 JS 快照TypeScript 编译为原生机器码
是否含 JS 引擎✅ 始终包含❌ 默认不含,可选嵌入
典型体积几十 MB 量级~320KB(无引擎时)
启动方式初始化 JS 引擎直接执行原生 main
类型检查运行于引擎中编译期由 tsc 完成并驱动代码生成
不支持的代码极少(引擎什么都能跑)编译期明确报错,拒绝"静默出错"

💡 一句话总结:Deno compile 是"把引擎装进口袋",scriptc 是"把代码变成机器码"。前者换来了极致的语言兼容性,后者换来了极致的体积、启动速度和部署形态。

四、scriptc 如何处理"编译不了"的代码?

纯静态编译最怕的就是"我的代码编不过怎么办"。scriptc 的答案是三层策略(见 docs/src/app/introduction/page.mdx):

  1. 静态编译(默认)——能静态编译的构造直接生成原生代码,这是默认且唯一的基线;
  2. 动态运行(--dynamic)——npm 依赖包的 JS 代码、any类型代码,交给内嵌的轻量 JS 引擎quickjs-ng(约 620KB)在二进制内部执行,这就是 scriptc 的"内嵌运行时",官方称之为动态岛(dynamic island);
  3. 明确拒绝——其余一切在编译期给出带错误码的精确诊断,绝不悄悄错译。

关于动态岛,有三点设计非常关键:

  • 岛是第二世界:它有自己独立的堆和微任务队列,与静态代码之间的一切值都按拷贝传递,并做运行时校验——类型"说谎"会得到可捕获的TypeError,而不是内存损坏;
  • 只嵌入需要的部分:不写--dynamic的二进制,一个引擎字节都不会多;
  • 构建期嵌入:npm 依赖在构建时就打进二进制,运行时不读node_modules,产物在任何同平台机器上即开即用。

动态岛的完整机制可以看 docs/src/app/dependencies/page.mdx。

而静态层的"运行时"是一个用 C 写的原生运行时(packages/runtime):引用计数 + 确定性点回收的内存模型(无 GC 停顿)、与 JS 语义完全一致的纤程(fibers)事件循环、原生实现的http/net/tls服务器栈、以及和 Node 逐字节对齐的数字格式化。

五、用 coverage 命令"看见"静态性

scriptc 提供了一个很实用的透明度工具——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)

对比 Deno compile"一切都进引擎"的黑盒式打包,scriptc 把每一行代码落在哪一层都摊开给你看。

六、正确性如何保证?差分测试

scriptc 的正确性口号不是"我们实现了规范",而是"我们让你的程序和 Node 跑了一遍,逐字节一致":

  • 差分测试语料库:每个测试程序同时在 Node 和编译出的原生二进制上运行,stdout、stderr、退出码必须逐字节一致(测试语料见 tests/corpus);
  • 内存安全泳道:整个语料库在 AddressSanitizer 下重跑并做引用计数审计,任何泄漏或 use-after-free 都算构建失败。

Node API 的兼容清单甚至做成了机器可读的清单(internal/compatibility),静态原生支持和动态岛支持分开追踪,不混淆。

七、怎么选?

  • 选 Deno compile:你需要"任何 JS 都能跑"的极致兼容性,对体积不敏感,想快速把 Deno 项目打包分发;
  • 选 scriptc:你要的是小而快的原生可执行文件(数百 KB 级、毫秒启动、零运行时依赖)、面向 CLI 工具和微服务的部署场景,并且愿意用scriptc coverage明确知道代码落在哪一层。

⚡ 两者并不互斥:scriptc 的--dynamic动态岛本质上就是"按需内嵌运行时"——默认纯原生,需要时才为 npm 依赖和any代码打开那个 620KB 的岛。这才是它与 Deno compile "引擎常驻"模式的本质区别。

📦 想动手试试?clone 仓库后按 AGENTS.md 说明执行pnpm install && pnpm -r build构建,或直接从 npm 安装 CLI:npm install -g scriptc。 仓库地址:https://gitcode.com/GitHub_Trending/sc/scriptc

总结:Deno compile 把 JavaScript 引擎装进了口袋,scriptc 则把 TypeScript 变成了真正的机器码,仅在必要时才按需内嵌一个轻量 JS 引擎(动态岛)。如果你追求极致体积、毫秒级启动和"静态性可见"的编译体验,scriptc 值得放进你的工具箱 🚀。

【免费下载链接】scriptcTypeScript-to-Native Compiler项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询