12k
All articles

使用 deno desktop 构建桌面应用

Deno desktop 将 TypeScript 应用编译为原生桌面二进制,涵盖 webview 或 CEF 后端、框架支持、跨编译和限制。

OpenReplay Team
OpenReplay Team
使用 deno desktop 构建桌面应用

deno desktop 可将一个 Deno 项目编译为自包含的桌面应用:你的代码、Deno 运行时以及渲染引擎会被打包进每个平台各自的单一二进制文件中。该功能随 Deno 2.9 一同发布。它与 Electron 和 Tauri 的区别在于,渲染引擎是一个构建期的选择。你可以选用操作系统自带的 WebView,也可以选用内置的 Chromium,而 Electron、Electrobun、Tauri 和 Dioxus 都不提供这种切换能力。

任何发布过 Electron 应用的人都熟悉那场关于 100 MB 安装包的讨论,而任何发布过 Tauri 应用的人都遇到过只在 WebKitGTK 上才能复现的 bug。这两个框架分处同一条轴线的两端,而在此之前,选择框架就意味着永久性地选定了渲染引擎。

本文是对第三种选择的初步探索,面向已经了解 Electron 与 Tauri 取舍关系的读者。内容涵盖该命令产出什么、能体现其模型的最小程序、每种后端在体积上的代价、可以不加修改直接接受哪些框架项目、交叉编译如何运作,以及目前还缺失什么。Electron 与 Tauri 之争本身,可参阅另一篇Electron 与 Tauri 的对比

要点速览

  • deno desktop 随 2026 年 6 月 25 日发布的稳定版 Deno 2.9.0 一同推出,但发布公告仍将其标注为实验性功能,部分平台特性尚未落地。
  • 默认的 webview 后端在 Windows 上通过 WebView2 渲染,在 macOS 上通过 WKWebView,在 Linux 上通过 WebKitGTK;可选的 cef 后端内置 Chromium,可在所有平台上获得一致的渲染效果。
  • Deno 的对比页面给出的数据是:WebView 后端的应用约 40 MB,CEF 后端的应用约 150 MB,相比之下 Electron 约为 100 MB 以上,Tauri 约为 2–10 MB。
  • 桌面入口文件中的 Deno.serve() 处理函数不接受端口或主机名参数;运行时会将其绑定到窗口所加载的地址。
  • --target--all-targets 可从任意主机交叉编译到五种平台三元组,且无需 Rust 工具链,唯一受主机限制的例外是 macOS 的 .dmg

什么是 deno desktop?

deno desktop 指向一个 Deno 入口文件或某个 Web 框架项目,得到的就是一个在 webview 中绘制界面、在 Deno 中运行逻辑的桌面应用。2.9 发布公告 将其作为头号特性介绍,并在同一节中标注它仍是实验性的、API 表面仍在稳定化过程中。它落地于稳定版 2.9.0,而非 canary 版本,这一点在 v2.9.0 发布说明feat: deno desktop subcommand 条目中有记录。

每个平台的二进制文件中包含三样东西:你的代码、Deno 运行时和一个渲染后端。Deno 侧与 webview 之间的通信走的是进程内通道,而非基于 socket 的 IPC,这与 Electron 的主进程—渲染进程消息传递是不同的模型。诸如 Deno.BrowserWindowDeno.Tray 这类原生能力已内置于运行时中,因此窗口控制和系统托盘图标无需任何第三方包。

最小的 deno desktop 程序

最小的桌面应用就是一个返回 HTML 的 Deno.serve() 处理函数,外加一条命令。

// main.ts
Deno.serve(() =>
  new Response("<!DOCTYPE html><h1>Window one</h1>", {
    headers: { "content-type": "text/html" },
  })
);
deno desktop main.ts

注意这里缺少的参数。在普通的 Deno 服务器中你需要传入端口;而在这里你要省略它,因为在桌面入口文件中,运行时会把窗口已经指向的地址交给该处理函数。编译后的应用会打开一个指向该本地服务器的原生窗口。这是该编程模型中唯一不那么显而易见的部分:HTTP 服务器就是 UI 的传输层,运行时替你把两端连接起来。

应该选择 webview 还是 cef 后端?

渲染引擎是构建期的决定,这也正是 deno desktop 占据了两个竞争者都未填补的位置的原因。Electron 只内置 Chromium,Tauri 只使用系统 WebView。deno desktop 两者皆可,通过 --backend 标志或 deno.json 中的 desktop.backend 字段来选择。

deno desktop main.ts                  # webview(默认)
deno desktop --backend cef main.ts    # 内置 Chromium

后端文档页列出了默认后端背后的引擎:macOS 上是 WKWebView,Windows 上是 WebView2,Linux 上是 WebKitGTK。该页面还记录了这一后端的两个限制,对评估者而言尤为重要:DevTools 仅在 CEF 下可用,且 Linux 上的 WebGPU 需要 CEF。cef 后端会在应用包中放入一份 Chromium Embedded Framework,同一页面给出的框架本身体积约为 150 MB。

Deno 的对比页面以近似值列出了最终的应用体积:

工具引擎应用体积(Deno 给出的数据)
deno desktopwebview系统 WebView~40 MB
deno desktopcef内置 Chromium~150 MB
Electron内置 Chromium~100 MB 以上
Tauri系统 WebView~2–10 MB

分发页面提供了另一组数据:WebView 后端的 hello-world 程序约为 66 MB,使用 --compress 后可降至 19 MB。请将这些都视为 Deno 自己给出的数字,而非对你自己应用的实测结果。

发布公告给出的一句话准则是:除非你需要在所有平台上使用完全相同的引擎,否则就坚持用 webview。结合文档中列出的限制加以扩展,可以得到一条可操作的决策路径:默认使用 webview;当你的 UI 依赖 Chromium 特有的渲染行为、无法在三种操作系统引擎上全部测试、开发期需要 DevTools,或 Linux 上需要 WebGPU 时,切换到 cef。切换的代价就是上表两行之间的差距。

deno desktop 能识别哪些框架?

deno desktop . 指向一个现有框架项目时,它会从配置文件或 package.json 中判断出使用的是哪个框架,嵌入构建产物,并以框架的生产服务器作为 Deno.serve() 的处理函数运行。对于 Next.js、Astro、Fresh 和 Nuxt,各框架说明显示所需的操作不过是框架自身的构建命令加上 deno desktop .,无需适配器,也无需额外配置。SvelteKit 也能被识别,但仅限于通过 Deno Deploy 适配器或 Node 适配器的输出;React Router 则需要一个兼容 Deno 的 app/entry.server.tsx

请先运行框架的构建命令。该命令只打包构建产出的内容,它不会替你触发 next buildastro build

npx next build        # 生成 .next/
deno desktop .        # 打包已构建的服务器
deno desktop . --hmr  # 开发模式:框架 dev server + 热重载

使用 --hmr 时,应用窗口直接指向框架自身的 dev server,因此开发体验与在浏览器标签页中工作非常接近:编辑后状态得以保留,fast refresh 正常工作,错误也通过常规的浮层呈现。

如何交叉编译 deno desktop 应用?

--target <triple> 为某一个其他平台构建,--all-targets 则为所有受支持平台构建,可从任意主机执行,且无需 Rust 工具链。分发页面列出了五个目标:aarch64-apple-darwinx86_64-apple-darwinx86_64-pc-windows-msvcaarch64-unknown-linux-gnux86_64-unknown-linux-gnu

deno desktop --target x86_64-pc-windows-msvc main.ts
deno desktop --all-targets main.ts

其机制是下载,而非编译。对于你指定的目标,CLI 会拉取预构建好的 denort 和预构建好的后端归档包,并在使用前校验两者的 SHA-256 哈希。这正是它能做到而 Tauri 和 Dioxus 做不到的原因。后两者的 Rust 工具链必须在目标平台上完成编译,因此每种操作系统都需要各自的构建机器。Electron 可以通过 electron-builder 进行交叉构建,所以这里的对比specifically 针对基于 Rust 的工具。Deno 这边唯一的例外是 macOS 的 .dmg,它会调用 hdiutil,因此必须在 Mac 上生成。

坦率地谈谈成熟度

Electron 支撑着 Slack、Visual Studio Code 和 Notion;Tauri 2 背后有多年的版本迭代和移动端目标支持;而 deno desktop 随 2.9.0 才刚刚问世,此后的每个 2.9 补丁版本都带有桌面相关修复,2.9.6 也不例外。文档对尚未实现的部分表述得很坦诚。

输出格式的完善程度其实超过对比页面列出的缺口清单所暗示的水平。分发页面记录了 macOS 上的 .app.dmg,Windows 上的应用目录或 .msi,以及 Linux 上的应用目录、.AppImage.deb.rpm,具体由 --output 的扩展名决定。根据发布说明,.msi.deb.rpm 安装包在 2.9.0 中就已随附。

已确认的缺口包括:

  • Windows 自动更新。 只有 macOS 和 Linux 才真正完成更新流程:它们会应用已暂存的补丁,并在新版本启动失败时回退到旧版本。Windows 会下载并暂存补丁,但从不真正切换,因此下次启动时不会有任何变化。自动更新页面指出,目前应将 Windows 自动更新视为不受支持。
  • 公证(Notarization)。 在 macOS 主机上签名会自动执行,但代码签名章节指出你仍需手动完成公证,xcrun notarytool submit 是单独的一步。
  • 移动端。 没有 iOS 或 Android 目标,而 Tauri 2 两者皆有。
  • 安全存储、运行时权限提示、共享 CEF 运行时。 对比页面将这三项都列为缺失或在路线图中;其中共享运行时是能够缩减 CEF 应用体积的关键项,但目前只是承诺,尚未发布。

现在谁该尝试 deno desktop?

如果你已经在使用 Deno、团队写 TypeScript 而非 Rust,且应用是内部工具或是对现有 Next.js、Astro、Fresh 或 Nuxt 代码库的桌面封装,同时 40 MB 的 WebView 构建体积可以接受、目标用户集中在 macOS 或 Linux(自动更新可用),那就现在试试。如果你面向需要就地更新的 Windows 用户、需要用同一套代码库支持移动端,或需要一条背后有多年签名与安装包工具积淀的分发流水线,那就再等等。发布公告中的”实验性”标签是准确的:模型是成熟可靠的,命令也如文档所述正常工作,但其 API 表面在补丁版本之间仍可能变动。

结论

deno desktop 之所以是一个真正的第三选项,是因为它将渲染引擎的决策与框架的决策解耦,并为这一选择的两端各自标明了明确的代价。评估它最省事的方式,就是在你已有的框架项目中运行 deno desktop .,分别用默认后端和 --backend cef 各跑一次,然后对照上文列出的缺口比较两份产物。

常见问题

在 deno desktop 应用中,webview 如何调用 Deno 代码?

通过 bindings。在 Deno 侧,你用 win.bind(name, handler) 把处理函数挂载到某个窗口上;页面 JavaScript 随后调用 bindings.name(args),得到一个携带处理函数返回值的 promise。每个窗口维护自己的一组 bindings,因此注册在某个 Deno.BrowserWindow 上的处理函数对另一个窗口是不可见的。调用走的是进程内通道而非 socket IPC;当处理函数抛出异常时,传到 webview 的是一个携带 name、message 和 stack 的普通对象,而不是真正的 Error。

deno desktop 中的 raw 后端是什么,什么时候该用它?

它运行一个完全不带 Web 引擎的桌面应用。你仍然拥有窗口、输入事件和原生 API 表面,但没有任何地方可以渲染 HTML:没有 webview,没有自动的 Deno.serve() 绑定,也没有 bindings 代理。它适合那些用 WebGPU、Skia 或自定义绘制代码自行绘制界面的应用。选择它的唯一方式是 deno.json 中的 desktop.backend 字段,因为 --backend 标志只接受 cef 和 webview。

我能在不改代码的情况下让 deno desktop 应用在 webview 和 cef 后端之间切换吗?

可以。Deno 的后端文档页将 CEF 与 WebView 视为可互换的:窗口、bindings、事件、导航和 JS 执行在两者上的行为完全一致,只有 raw 后端会打破这种可移植性。为一个你此前未使用过的后端或目标构建时,会先下载一份预构建归档包,CEF 的情况下有数百 MB,经过校验和验证后保存在 Deno 缓存目录中,后续构建即可跳过下载。

Deno 权限在 deno desktop 应用内部生效吗?

生效,但权限来自编译进二进制文件中的设定,而非运行时提示。binding 在 Deno 运行时内部执行,拥有进程被授予的权限,因此一个读取文件的处理函数需要在启动时就获得读取权限,webview 的任何行为都无法扩大这一权限范围。文档明确指出运行时不会弹出单独的权限提示,因此请把 binding 从页面接收的任何内容都当作不可信输入并加以校验。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.