2026 年 6 月,Deno 在 v2.9.0 中正式发布了 deno desktop——一条命令把任意 TypeScript 项目(单文件脚本、Next.js 应用、乃至一个 HTTP server)打包成 macOS .app、Windows .exe 和 Linux AppImage。HN 上拿了 997 分、365 条评论,Lobsters △34。分数高不只是因为 Deno 的名气——评论区最激烈的讨论集中在 Deno 做出的一个技术选择:默认捆绑 CEF(Chromium Embedded Framework),而不是像 Tauri 那样依赖系统 WebView。
这个选择在社区里同时收获了喝彩和质疑。喝彩来自那些在 Linux 上用 webkitgtk 受过伤的开发者;质疑来自那些认为「系统自带浏览器引擎为什么不用」的人。两种声音都有道理,而 Deno 的选择恰好站在它们中间。
Electron 的 200MB 诅咒
Electron 统治桌面应用开发的理由很简单:你用 HTML/CSS/JS 写 UI,用 Node.js 调用系统 API,一份代码跑 Windows/macOS/Linux。代价也简单:每个 Electron 应用都是一个独立发行的 Chromium 浏览器副本,加上 Node.js 运行时,起步 150MB,上探 250MB。 Slack、VS Code、Discord、Figma——你硬盘上可能有五个 Electron 应用,意味着你存了五份 Chromium。
这不仅仅是磁盘空间的浪费。每个 Electron 应用各自启动一套浏览器进程——GPU 进程、渲染进程、网络进程——内存占用线性叠加。一个 Chrome 标签页的内存开销约 100MB,三个 Electron 应用同时在跑,轻松吃掉 1.5GB 以上。用户感知是「为什么我的笔记软件比 IDE 还吃内存」,真相是你的笔记软件就是一个 IDE 级别的浏览器宿主,只不过它在跑一个 <textarea>。
Electron 团队不是不知道这个问题。他们做过 electron-shared-library 的实验,试图让多个 Electron 应用共享一份 Chromium 动态库,但最终没有落地。根本障碍不在技术——在于应用各自的版本依赖地狱。 App A 依赖 Electron 28、App B 依赖 Electron 31,共享库的 ABI 兼容性在 Chromium 的更新频率下(每四周一个大版本)几乎无法维持。Linux 发行版的包管理器用「整个发行版锁在一个版本快照上」来解决这个问题,但桌面应用没有这个奢侈——你不能要求用户因为 Discord 更新而把 VS Code 也强制升级。
Tauri 的系统 WebView 路线:理念正确,实践残酷
Tauri 走了另一条路。它的核心洞察是:既然操作系统已经内置了浏览器引擎,为什么还要再发一份? macOS 有 WKWebView,Windows 有 WebView2,Linux 有 webkit2gtk。Tauri 的二进制体积极小——起步不到 10MB——因为渲染引擎完全外包给了操作系统。后端用 Rust 写,前端可以是任何 JS 框架,IPC 通过 Tauri 自定义的 bridge 完成。
这个理念在 Windows 上运行得不错。WebView2 基于 Edge Chromium,微软通过 Windows Update 推送更新,版本较新,兼容性好。问题出在 macOS 和 Linux。
macOS 上的 WKWebView 与操作系统版本绑定。这意味着如果你的用户还停留在 macOS 13,那你的 Tauri 应用跑的就是 macOS 13 对应的 WebKit 版本——这个版本可能落后最新 Safari 两到三个大版本。 新 CSS 特性不支持、新的 Web API 不暴露、某些 Canvas/WebGL 行为与 Chrome 不一致。Tauri 开发者对此无能为力——他们没有能力也不会在用户机器上替换系统 WebKit。苹果的 WKWebView 更新节奏完全不受应用开发者的控制。
Linux 的情况更糟。Linux 没有「系统浏览器引擎」这个概念——不同的桌面环境、不同的 GTK 版本、不同发行版打包的 webkit2gtk 版本各不相同。在 HN 的 Deno Desktop 评论区,一位长期使用 Tauri 的开发者 echelon 写了一段被高频引用的评价:webkitgtk「又慢又吃内存」。这不是个人抱怨——Tauri 的 GitHub Issue #3988 和 #7021 记录了 Linux 上 webkit2gtk 在大量 DOM 元素场景下的严重性能退化,包括滚动卡顿、渲染掉帧、以及 WebKit 2.40 引入的已知性能回归。
Tauri 在 Linux 上面临的真正问题是:根本没有一个可靠的渲染引擎可选。 webkit2gtk 由 WebKitGTK 社区维护,开发资源远不如 Chromium 团队——Chromium 有 Google 全职工程师团队和安全研究员,WebKitGTK 的核心维护者一只手数得过来。这不是在贬低 WebKitGTK 开发者的能力——他们做了令人尊敬的工作——但兵力对比是客观事实。
Deno 的选择:捆绑 CEF,但不独占
Deno Desktop 选了第三条路。它默认使用 CEF(Chromium Embedded Framework)——和 Electron 一样基于 Chromium,但有两个关键区别。
第一,CEF 是纯浏览器引擎,不含 Node.js。 Electron 的捆绑包同时包含 Chromium 渲染引擎和 Node.js 运行时,两者通过 libnode 深度耦合。Deno Desktop 的架构不同:Deno 本身就是 JS/TS 运行时(基于 V8),CEF 只负责渲染 HTML/CSS/JS 前端页面。Deno 进程不跑在 CEF 内部——它作为独立进程启动一个本地 HTTP server,CEF 窗口加载 http://localhost:<port> 来渲染 UI。前后端通信走的是普通的 HTTP/WebSocket,不是 Electron 那种通过 ipcMain/ipcRenderer 的进程内桥接。
这个架构选择的一个直接后果是:Deno Desktop 应用可以切换到其他渲染后端。 Deno 支持三种 backend:cef(默认)、webview(系统 WebView)、winit(纯 Rust 窗口,适合游戏/图形应用)。CEF 是官方推荐的默认选项,但如果你不在乎兼容性,可以切到 webview 享受更小的二进制体积。这种灵活性是 Electron 没有的——Electron 的 Chromium 绑定太深,不可能「切出去」。
第二,Deno 的公开路线图里有一条写得很清楚:共享 CEF runtime。 当前每个 Deno Desktop 应用仍然各自捆绑一份 CEF 动态库,但 Deno 团队计划在未来实现「托管共享 runtime」——多个 Deno Desktop 应用共享机器上的一份 CEF 安装。这条路线和 Electron 尝试过但放弃的 shared-library 实验方向相同,但 Deno 有一个 Electron 没有的优势:所有 Deno Desktop 应用运行在同一个 runtime 版本管理框架下。 Deno 的版本更新机制可以确保「如果你的机器上装了两个 Deno Desktop 应用,它们使用的 CEF 版本由 Deno 统一管理」,就像系统级的包管理器管理共享库版本一样。这不是一个已经解决的问题——路线图上的条目不等于交付——但方向是对的。
CEF 的技术特征
CEF 本身是一个成熟度极高的项目。Spotify 桌面客户端、Adobe 的部分 Creative Cloud 组件、Epic Games Launcher、OBS Studio 的浏览器源——都是用 CEF 嵌入 Chromium 的。它的多进程架构和 Chrome 一致:一个 browser 进程管理窗口和网络,每个页面实例跑在独立的 renderer 进程中,GPU 进程负责合成和硬件加速。这种架构带来的隔离性恰好与 Deno 的安全模型互补——Deno 默认禁止文件系统/网络/环境变量访问,CEF 的沙箱 renderer 进程进一步限制了前端代码的逃逸面。
CEF 还支持离屏渲染(Off-Screen Rendering, OSR)。普通模式下 CEF 创建原生窗口并在其中渲染;OSR 模式下渲染结果输出到内存缓冲区,由宿主应用决定如何显示。这个能力对 Deno Desktop 的 winit backend 很重要——如果将来 Deno 想支持完全自定义的 UI 框架(如 GPU 驱动的 UI),CEF 的 OSR 模式可以直接把网页内容作为纹理输入到渲染管线中。
但 CEF 也不是没有代价。一个 CEF 动态库(libcef.so)的大小约 150MB,加上 Chromium 的资源文件(.pak、icudtl.dat、locales),总磁盘占用在 200MB 左右。 这和 Electron 差不多。单看二进制体积,Deno Desktop + CEF 并没有比 Electron 更轻——它的优势不在这里。它的优势在于两点:一是 CEF 可以共享,而 Electron 的 Node.js+Chromium 耦合体很难共享;二是 Deno 允许你降到 webview backend 来追求极致体积,Electron 没有这个选项。
三方案对比
把三种方案放在同一个表格里,各自的取舍会更清楚。
| 维度 | Electron | Tauri | Deno Desktop (CEF) |
|---|---|---|---|
| 渲染引擎 | 捆绑 Chromium | 系统 WebView | 捆绑 CEF(可切换到系统 WebView) |
| 后端语言 | Node.js (JS) | Rust | Deno (JS/TS) |
| 二进制体积 | 150-250 MB | 3-15 MB | 200 MB(CEF 模式)/ 15 MB(webview 模式) |
| macOS 兼容性 | 最新 Chromium,不受 OS 限制 | 受系统 WKWebView 版本限制 | 最新 CEF,不受 OS 限制 |
| Linux 兼容性 | 一致 | 依赖 webkit2gtk,性能和兼容性波动大 | 一致(自有 CEF) |
| 进程模型 | Main + Renderer(Node.js 与 Chromium 深度耦合) | Rust 主进程 + 系统 WebView 进程 | Deno HTTP server 进程 + CEF browser/renderer 进程 |
| 共享引擎潜力 | 低(版本碎片化严重) | 天然共享(用系统引擎) | 中(路线图中有共享 runtime 计划) |
| 前端框架支持 | 任意 JS 框架 | 任意 JS 框架 | 任意 JS 框架(包括 Next.js 等全栈框架) |
| 更新机制 | 需自建 | 需自建 | 内置(Deno Deploy 风格的热更新) |
这张表格里最容易被误读的是「二进制体积」那一行。 Deno Desktop 在 CEF 模式下 200MB 的体积看起来和 Electron 一样糟糕,但关键差异在于这 200MB 中哪部分是「可变代码」、哪部分是「可共享引擎」。Electron 的 200MB 里,Chromium + Node.js 占了大约 180MB,且每个应用独立打包。Deno Desktop 的 200MB 里,CEF 也是约 150MB,但 Deno 的共享 runtime 路线图意味着这部分未来可能只需存一份。在共享 runtime 落地之前,Deno Desktop 在体积上没有赢 Electron;在共享 runtime 落地之后,它有潜力把每个应用的增量体积压到个位数 MB——就像 Tauri 今天做到的那样,但不牺牲渲染引擎的一致性。
共享依赖:一个被桌面应用遗忘的 Linux 智慧
Linux 发行版用包管理器解决共享依赖问题已经三十年了。libssl.so、libgtk.so、libc.so——系统上永远只有一份,所有应用链接到同一份。版本升级由包管理器协调,ABI 兼容性在发行版级别保证。这套机制运行得如此之好,以至于 Linux 用户对「每个应用自带一份 OpenSSL」这件事有天然的排斥。
为什么桌面应用要重新发明这个轮子? 根源在信任模型的差异,不在技术幼稚。Linux 包管理器能运作的前提是有一个中央权威(发行版维护者)对所有包的版本一致性负责。桌面应用生态没有这个中央权威——VS Code 是微软发的,Discord 是 Discord 公司发的,Figma 是 Figma 公司发的,它们之间没有任何协调机制。每个应用开发者只能假设「用户机器上有什么我不知道」,于是选择了最保守的策略:把我需要的所有东西都打进去。
Deno Desktop 的共享 CEF runtime 计划试图在这两个极端之间找到一个中间点:不做系统级的全局共享(那需要操作系统层面的协调),只做 Deno 生态内的托管共享。 所有通过 deno desktop 构建和分发的应用,其 CEF 版本由 Deno 的统一版本管理器控制。这有点像 Flatpak 的 runtime 机制——多个 Flatpak 应用共享一个 KDE/GNOME runtime——但粒度更小,只共享浏览器引擎。
这条路能不能走通取决于两个变量:一是 Deno Desktop 生态能不能长出足够多的应用让共享变得有意义(如果只有三个 Deno Desktop 应用,共享 runtime 的收益微乎其微);二是 CEF 的 ABI 稳定性能不能撑住「多个应用依赖同一份 CEF 但更新频率不同」的局面。CEF 的 API 稳定性比 Chromium 本身好——CEF 的 API wrapper 层做了很多缓冲——但也不是铁板一块。CEF 大版本升级时,共享 runtime 管理器如何处理「应用 A 兼容新版、应用 B 只兼容旧版」的情况,目前 Deno 团队还没有公开技术方案。
这场赌注的胜负手
echelon 在 HN 上的评论点出了为什么 Deno 选 CEF 是正确方向:Tauri 在 macOS 和 Linux 上的系统 WebView 经历太痛了。Tauri 的理念是干净的——用系统原生的东西,不发重复的二进制——但现实是 macOS 的 WKWebView 更新节奏由苹果决定,Linux 的 webkit2gtk 质量由一个小型开源社区保证。理念的干净不能补偿实现的粗糙——用户只会记住「这个应用在 Linux 上卡得没法用」,不会在意「这个应用用了系统 WebView 所以更省空间」。
Deno 的 CEF 路线放弃了理念的纯粹性,换取了实现的可控性。它承认了一个尴尬但真实的工程事实:在跨平台桌面应用领域,可控性比体积更重要。 一个渲染引擎如果在你无法控制的平台上表现不一致,省下的磁盘空间会被兼容性问题吃掉的开发时间成倍反噬。
但 Deno 也没有放弃体积的优化——共享 runtime 路线图是它和 Electron 的根本差异。如果共享 CEF runtime 能成功落地,Deno Desktop 将同时拥有「渲染引擎一致性」(来自捆绑 CEF)和「小体积增量」(来自共享架构),而这是 Electron(只有一致性、没有小体积)和 Tauri(只有在 Windows 上的小体积,没有 macOS/Linux 的一致性)各自都没能做到的事。
当然,路线图上的东西不能当已交付的东西来评价。Deno Desktop 目前仍是 canary 版本,API 未稳定,共享 runtime 更是「未来计划」。这个领域从来不缺美好的架构图——缺的是能把共享引擎版本管理这个看似简单实则地狱级困难的问题真正工程化落地的团队。 Deno 团队有这个能力储备(Deno 自身的版本管理和远程模块缓存系统就是一套可借鉴的基础设施),但能力和交付之间的距离,就是赌注本身。<|end▁of▁thinking|>