- Published on
Deno 2.9 — deno desktop 实验、原样迁移锁文件,以及 24 小时供应链默认值
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 — 6 月 25 日的 Deno 2.9,三周的补丁
- deno desktop — 走进 Electron 位置的实验品
- 锁文件播种 — 本次发布里最实用的功能
- Node 26 兼容 — "兼容目标"这个说法的确切含义
- 精确地读厂商基准测试
- 供应链默认值 — 24 小时等待成了默认设置
- 升级时会踩到的地方
- 那么,要不要迁移
- 结语
- 参考资料
引言 — 6 月 25 日的 Deno 2.9,三周的补丁
去年 5 月,Deno 2 时代梳理文章讲过 Deno 的方向转变 — 一个曾经拒绝 Node 的运行时,把 Node 兼容接纳为一等公民。这篇是它的后续。Deno 2.9 于 2026 年 6 月 25 日发布,按 GitHub 发布标签算,v2.9.0(6 月 25 日)之后又跟了 v2.9.1(7 月 1 日)、v2.9.2(7 月 8 日)、v2.9.3(7 月 15 日),每周一个补丁。上一个次要版本 2.8.0 是 5 月 22 日,也就是说这是一个五周一次的发布节奏。
发布说明的头条是 deno desktop,但在我看来,对实务开发者更重要的是另外两处变化:直接读取现有 npm、pnpm、yarn、Bun 锁文件的迁移路径,以及默认开启的供应链防御功能。下面逐一拆解,厂商给出的性能数字,也会连同测量条件和遗漏之处一起读。顺带一提,TypeScript 编译器那边的巨变已经在TS 7 GA 与原生编译器一文中单独讲过 — 本文只谈运行时这一层。
deno desktop — 走进 Electron 位置的实验品
deno desktop 是用 Web 技术栈构建原生桌面应用的新子命令(PR #33441,6 月 16 日合并)。UI 跑在 webview 里,逻辑跑在 Deno 里,产出是单一的可分发二进制文件。最简单的形式是入口点直接负责提供 UI — 桌面入口点里的 Deno.serve() 会自动绑定到 webview 打开的端口,不需要手动布线端口。
// main.ts — 就这么多
Deno.serve(() =>
new Response(
"<!DOCTYPE html><h1>Hello from Deno desktop</h1>",
{ headers: { "content-type": "text/html" } },
)
);
deno desktop main.ts # 弹出一个原生窗口
deno desktop # 自动检测当前目录的 Web 框架
deno desktop --hmr # 开发时的 HMR
框架自动检测用的是和 deno compile 相同的机制,按官方发布说明支持 Next.js、Astro、Fresh、Remix、Nuxt、SvelteKit、SolidStart、TanStack Start、Vite SSR。运行时新增了 Deno.BrowserWindow(窗口控制与 webview-Deno 桥接)、Deno.Tray(系统托盘)、macOS 专用的 Deno.Dock、以原生对话框渲染的 prompt()/alert()/confirm(),以及在后台应用二进制补丁的 Deno.autoUpdate()。
渲染引擎用 --backend 选择。默认的 webview 使用 OS 自带引擎 — Windows 上是 WebView2,macOS、Linux 上是 WebKit — 二进制体积小、启动快,但渲染效果取决于宿主 OS 的引擎版本。--backend cef 通过 Chromium Embedded Framework 整体打包 Chromium,保证所有平台渲染一致,代价是按发布说明的说法要多出几十 MB,构建时还需要下载。这恰好是把 Tauri(webview)与 Electron(打包引擎)之间的取舍,塞进了同一个工具里由你来选。
部署这一侧挺有意思。按 --output 的扩展名,macOS 产出 .app、.dmg,Windows 产出 .exe、.msi,Linux 产出 .AppImage、.deb、.rpm,而且因为 MSI 和 deb、rpm 安装包是纯 Rust 写的,所以不需要各平台专属的打包工具链,用一台 Linux CI 加 --all-targets 就能出全平台的二进制。支持的目标和 deno compile 一样是五个 — Linux x64/arm64、Windows x64、macOS x64/arm64 — Windows arm64 还没有。
什么时候不该用。 厂商自己明确写了 2.9 中这还是 experimental,表面 API 仍在稳定中。把生产环境的桌面应用架在实验性标志之上,这个决定本身已经有答案了,如果今天就要发布产品,Electron、Tauri 这些经过验证的选项依然摆在那里。选 webview 后端就会带上 Tauri 用户早已熟悉的痛点 — "渲染效果会随用户 OS 的 WebKit、WebView2 版本而变";选 cef 则 Electron 式的体积又回来了。这个实验真正的意义,不在于眼下的生产采用,而更像是一个方向性信号 — 运行时、编译器、打包已经合为一体的工具,如今连桌面端也要吃下。
锁文件播种 — 本次发布里最实用的功能
迁移成本最大的一块,永远是"丢掉精心固定下来的依赖图"。2.9 直接瞄准了这一点。在没有 deno.lock 的项目里运行 deno install,它会读取 package-lock.json、pnpm-lock.yaml、yarn.lock、bun.lock,用完全相同的解析版本和完整性哈希来播种 deno.lock(bun.lock 支持见 PR #35394)。官方的说法是,因为没有重新解析,所以也不会有版本悄悄升高。播种是单向的 — 读取外部锁文件生成 deno.lock,之后的管理交给 deno.lock。不会写回(write-back)。
$ deno install
Seeded deno.lock from package-lock.json
pnpm 的 monorepo 也照顾到了。npm、yarn、Bun 用的 package.json 里的 workspaces 字段原本就会读,但 pnpm 单独用了另一个文件,现在发现 pnpm-workspace.yaml 时,会在不动原有字段的前提下把 packages、catalog、catalogs 配置迁移过来(#34993)。而在没有安装真正 node 二进制的环境里,它会在 PATH 里放一个替身 node,转发给自己并翻译 Node CLI 参数(#34969) — 这是为像 Turbopack 工作线程池那样直接调用 node 的工具准备的措施,如果真的存在 node,绝不会遮蔽它,也可以用 DENO_DISABLE_NODE_SHIM=1 关掉。
这一组变更的含义很清楚。"迁移到 Deno" 不再是重建依赖图的一场赌博,而变成了可以保留现有锁文件、用两条命令试一试、不行还能退回来的可逆实验。不过锁文件格式兼容的边缘情况 — 比如 pnpm 把 peer 内联编码进依赖键的方式 — 一直到发布前都还在靠后续 PR 打磨,所以如果是复杂的 pnpm monorepo,稳妥起见应该先确认播种结果的版本号是否和原始文件一致,再继续往下走。
Node 26 兼容 — "兼容目标"这个说法的确切含义
2.9 把 Node.js 兼容目标提升到了 Node 26。process.version 现在报告 v26.3.0(#34747),Deno 跑的 node-compat 测试套件也升到了 26.3.0(#34746)。补充一下背景 — 按 endoflife.date 的说法,Node 26 是 2026 年 5 月 5 日发布的 Current 系列,LTS 转正预计在 2026 年 10 月 28 日,而实际最新版是 7 月 8 日的 26.5.0。也就是说,兼容目标落后于实际 Node 两个补丁版本。
也有一些实质性的改进。像 import "fs" 这样的裸(bare)内置模块标识符,现在无需任何标志就无条件解析为 node:fs;node_modules 里的包会遮蔽内置模块的那个 bug,也按 Node 的规则(内置模块永远优先)修好了。Node-API 现在报告版本 10,node:test 加上了 mock.module()、mock.timers 和文件快照,process.resourceUsage() 这类此前缺失的 API 也补齐了。
得诚实指出的一点是:所谓"兼容目标 26",意思是 Deno 把自己跑的兼容测试套件的基准版本对齐到了 26.3.0,而不是"Node 26 生态系统 100% 能跑"的认证。原生插件、依赖 V8 内部机制的工具,或者依赖 Node 内部实现细节的包,仍然需要逐个确认。这条边界具体在哪里,发布说明并没有说,除了在候选项目上亲自跑一遍测试套件,没有别的捷径。
精确地读厂商基准测试
现在看发布说明里的性能数字。全部是厂商自测,测量条件写得很明确 — 一台专用的 x86_64 Linux 机器,服务器和负载生成器固定在互不重叠的核心上,并发数 100,取 oha 三次运行的中位数,冷启动取 hyperfine 150 次运行的平均值,对比对象是 Deno 2.8.0。
| 项目 | 2.8 | 2.9 | 变化 |
|---|---|---|---|
| 冷启动(hello world) | 34.2 ms | 17.3 ms | 快 1.98 倍 |
| Deno.serve "realworld"(JSON POST + Bearer 请求头回显) | 56.8k req/s | 72.4k req/s | 1.27 倍 |
| Deno.serve plaintext | 77.0k req/s | 85.6k req/s | 1.11 倍 |
| Deno.serve 1 MiB 响应 | 1,617 req/s | 1,907 req/s | 1.18 倍 |
| 峰值 RSS,realworld | 142 MB | 64 MB | 减少 2.2 倍 |
| 峰值 RSS,1 MiB 响应 | 197 MB | 63 MB | 减少 3.1 倍 |
这些倍数和原始数字算术上是对得上的(我自己除了一遍确认过)。改进的来源,发布说明也讲清楚了 — 把快照里的 node: 全局对象改成懒加载、把 Node 引导过程限定在 Node 工作线程内、给懒加载的 ESM 挂上 V8 代码缓存、再加上快照瘦身,这几项合起来贡献了冷启动提升的一半;HTTP 吞吐量则是靠 Deno 自有的 HTTP/1.1 服务路径(#34446),加上 crypto.subtle 和 console 从 JS 移植到 Rust,以及 RSS 的平坦化,贡献了剩下的部分。
这些数字没说出来的东西,同样明确。
- 对比对象是它自己。 是 2.9 对 2.8,不是和 Node 或 Bun 的比较。"1.27 倍"是升级的理由,不是迁移的理由。
- 这是回环流量。 负载是在同一台机器上、仅仅分了不同核心打出来的。1 MiB 负载下 1,907 req/s 换算带宽约合 16 Gbps,这个数字只有在不经过网卡的回环条件下才可能出现。一旦引入真实网络或 TLS 终止,情况就会变。
- 这是 HTTP/1.1 明文路径。 新服务路径本身就是针对 HTTP/1.1 的,没有给出 TLS 或 HTTP/2 的数据。
- 只有吞吐量和 RSS。 没有延迟分布 — 没有 p99 这样的尾部延迟。只取三次的中位数,也就没有方差信息。
- 是一个回显工作负载。 名字叫 "realworld",但其实是没有数据库、没有磁盘的 JSON 回显。真实应用的瓶颈通常在运行时之外。
如果只能从这张表里挑一项来看,我会看内存而不是吞吐量。在 2.8 上,RSS 会随工作负载在约 94 MB 到 197 MB 之间波动;到了 2.9,在这些被测的工作负载下,都被压平到 62 MB 左右。这意味着同一台机器上能塞进更多实例,对那些按容器密度计算成本的团队来说,这比 1.1 到 1.3 倍的吞吐量提升更实在 — 但同样要保留那句限定语:这只是在这些具体工作负载下的结果。
供应链默认值 — 24 小时等待成了默认设置
2.9 里悄悄发生的最大政策变化是这个。2.6 引入的 min-release-age — 拒绝安装发布时间还不够久的 npm 包版本的功能 — 现在以 24 小时的窗口默认启用了(#35458,发布当天合并)。理由很简单。npm 供应链攻击中相当一部分,恶意版本会在发布后一两天内被发现并撤下,所以光是等待,就能过滤掉一大类攻击。就像上周的 axios 事件梳理里看到的那样,即便有 provenance 也存在被突破的漏洞,因此时间延迟是一层简单但确实有效的额外防御。
# .npmrc — 显式的值会覆盖默认值
min-release-age=72h # 延长到三天
min-release-age=0 # 完全关闭
值得指出可能在实际运维中踩坑的地方 — 一条把自家包发布到 npm、随即安装的流水线,现在在默认配置下会失败 24 小时。如果你的工作流是发布后立刻把补丁推上线,就需要在 .npmrc 里调整这个值,而这个权衡(延迟新鲜补丁 vs 堵住攻击窗口)需要各团队自己去算。这里也说清楚:这个功能作用于 npm 包。
在此之上还叠加了一层可选启用的防御。trust-policy=no-downgrade 沿用了 pnpm 的设计思路,给每个版本的发布方式 — 带 2FA 实时批准的 staged publishing、带 provenance 证明的 trusted publishing、单独的 provenance — 划定信任等级,把用比之前版本证据更弱的方式发布的新版本,解析时直接报硬错误。它专门用来抓一种典型的维护者令牌被盗攻击痕迹(发布说明举了 2025 年 8 月的 s1ngularity 事件为例):一个原本一直用 provenance 稳定发布的包,突然变成了普通令牌发布。发布说明也自曝了限制 — 由于整个注册表的 provenance 采用率还不均衡,所以默认是关闭的,这句话我原样保留。
升级时会踩到的地方
有一处行为变化。Deno.serve 的自动响应压缩现在默认关闭。如果你之前依赖旧版本的自动压缩来省带宽,升级到 2.9 的那一刻起,响应就会变成不压缩地发出去 — 可以按服务器指定 automaticCompression: true,或用 DENO_SERVE_AUTOMATIC_COMPRESSION=1 环境变量在整个进程范围内恢复它。如果在 CDN 后面,大概感觉不到,但如果是直接对外服务,这一条应该列进升级检查清单。
其余的都是带着实验标签进来的。CSS 模块导入(通过 with { type: "css" } 拿到 CSSStyleSheet 的 Web 标准方式)藏在 --unstable-raw-imports 标志后面;deno compile --bundle — 先做 tree-shaking 再嵌入以缩小二进制体积 — 是个实验性标志,厂商自测给出的数字是 lodash hello world 从 11.6 MB 缩小到 1.5 MB(当然 npm 依赖越重的项目效果越明显,能否推广要在各自的构建上确认)。
测试运行器明显变厚了。t.assertSnapshot() 的内置快照、Deno.test.each 的参数化测试、CI 分片用的 --shard、只挑被改动文件影响到的测试的 --changed、应对不稳定测试的 --retry,还有覆盖率阈值 — 一项项在抹掉当初要引入 Vitest 或 Jest 的理由。除此之外,这次发布里还有 deno task 的基于输入的缓存、相当于 npm ls 的 deno list,以及 ML-KEM、ML-DSA、SLH-DSA 等 NIST 后量子算法的 Web Crypto 实现(和OpenSSH 10.4 的 ML-DSA 故事同一条线索的运行时版本)。
那么,要不要迁移
总结一下。
值得试一试的情况
- 是现有的 Node 项目,现在可以在不丢锁文件的情况下可逆地实验了 — 迁移成本的结构性改变,是这次发布的核心。
- 想把格式化工具、linter、测试运行器、任务运行器、编译器合并成一个的团队 — 2.9 测试运行器的增强,确实减少了对外部工具的依赖。
- 想把 npm 供应链防御拿来当工具默认值的组织 — 24 小时等待默认开启,是目前主流运行时里最激进的默认设置。
暂时还不必
- 深度依赖原生插件、Node 内部 API 的代码库 — "兼容目标 26" 不覆盖这块领域。
- 生产环境的桌面应用 — deno desktop 还是实验性的,还没到取代 Electron、Tauri 的阶段。
- 建立在 Bun 专属 API(
bun:sqlite、Bun.serve等)之上的代码 — 锁文件会读,但 API 不会帮你翻译。 - 已经在 Node 26 上跑得好好的,也说不出迁移能带来什么具体收益的团队 — 正如基准测试表所示,2.9 的数字并不是在主张相对 Node 的优势。
顺带留一句关于竞争运行时的情况 — 按 Bun 的 GitHub 发布记录,最新的稳定版是 5 月 13 日的 v1.3.14,两个月没出新的稳定版了,但仓库里的提交今天还在继续,读成停滞并不合适。不过,和每周出补丁的 Deno 相比,发布节奏上的这种差异,是值得记下来的事实。关于 Bun 那边的状态评估,可参考5 月的 Bun 回顾。
结语
贯穿 Deno 2.9 的逻辑是"结构性地降低迁移成本"。原样读取锁文件、补上 node 二进制的位置、迁移 pnpm 工作区配置、跟上 Node 26 兼容目标 — 全都指向同一个方向。两年前接受 Node 兼容这一自我否定,如今已经走到了连竞争对手的锁文件格式都要读的地步。架在这之上的 deno desktop 虽然还是实验性的,但也是工具整合这同一逻辑的延伸,而 min-release-age 的默认开启,则是一个信号,说明运行时本身来设定供应链防御默认值的时代已经到来。
厂商给出的数字,连同条件一起读的时候是有用的 — 在回环微基准测试的局限之内,冷启动减半和 RSS 的平坦化是有机制解释支撑的、可信的改进。但那张表里没有 Node 和 Bun,这个事实恰恰准确地说明了这次发布的性质。2.9 不是一个"我们更快"的发布,而是一个"我们降低了迁移成本"的发布,而这个主张,证明它的不是数字,是功能列表。
参考资料
- Deno 2.9 官方发布说明
- denoland/deno GitHub 发布记录(确认 v2.9.0~v2.9.3 的日期)
- PR #33441 — feat: deno desktop subcommand
- PR #35458 — feat: enable default minimum dependency age
- PR #34746 — node-compat 测试套件升到 26.3.0 · PR #34747 — process.version v26.3.0
- PR #34446 — Deno 自有的 HTTP/1.1 服务路径
- Node.js 发布计划 — endoflife.date
- Deno 2 时代梳理 — Node 兼容的时代(相关文章)
- axios npm 入侵事件 — provenance 遗漏了什么(相关文章)