R Raycast 技术博客 · 中文译本
全文翻译 原作 by Raycast Team
A TECHNICAL DEEP DIVE

新版 Raycast 的技术深潜

Raycast 跨平台重写背后的故事——以及那些让它依然感觉快、惊喜、亲切的工程细节。

原作者 Petr Nikolaev、Thomas Paul Mann 发布日期 2026 年 5 月 14 日 原文 raycast.com

我们刚刚发布了 Raycast 2.0 的公测版。这是自 2020 年 Raycast 最初发布以来最大的一次版本更新,也是第一个同时在 macOS 和 Windows 上运行的版本。

Raycast 2.0 on Windows Raycast 2.0 on macOS
Raycast 2.0 在 macOS 和 Windows 上的样子

为了做到这一点,我们把整个应用从头重写了一遍。新的架构、新的技术栈,混合了 TypeScript、Swift、C#、Rust、Node 和 React。Web 技术从一开始就是 Raycast 的一部分,扩展和 Notes 都基于它构建;在 v2 里,我们进一步加大了对 Web 技术的投入,同时让整个应用依然保持一贯的原生感与速度。

如果说发布文讲的是"有哪些新东西",那这篇要讲的就是"它是怎么造出来的"——重写背后的故事、过程中做过的取舍,以及完成一次这么大规模的重写都付出了什么。难的不是让 Raycast 跑起来,难的是让它感觉对

01起点

Raycast v1 在内核上是一个用 Swift 在 AppKit 上构建的纯 macOS 原生应用。我们几乎不用标准 UI 组件——它们不是为我们关心的那种"键盘优先、面向高级用户"的工作流而设计的,所以我们自己造了一套。每一行列表、每个快捷键、每个默认行为,都是我们自己实现的。SwiftUI 我们也没怎么用。它和 Raycast 几乎是同期成长的,但一直没有达到我们对性能和控制力的标准。SwiftUI 在 v1 里唯一存在的地方是每年的 Wrapped 功能,并且它和应用的其他部分被完全隔离了。

Raycast v1 总览
Raycast v1 的样子

扩展生态系统则建立在完全不同的栈上。React、TypeScript 和 Node.js,UI 用声明式描述,由原生应用渲染。Felix 详细写过这套架构:Raycast API 扩展是怎么工作的。给第三方开发者选一套熟悉的技术栈,是 Store 如今能聚集几千个扩展、覆盖人们使用的几乎所有工具的重要原因。这套 API 在设计上也是可移植的——扩展本身不会假设自己跑在 macOS 上,这让我们去年得以把目录里的一大部分扩展带到了 Windows。

Raycast Notes 是我们第一次在应用里把 WebView 用作主要功能。它的编辑器是一个 React 应用,挂载在一个原生窗口的 WebView 里。这是一次试验——看看我们能不能用纯 Web 技术构建一个完整界面,而不破坏应用其他部分的手感。结果证明可行,Notes 现在是 macOS 和 iOS 用户每天大量使用的功能。

虽然 v1 在核心上是一个原生应用,但只要 Web 技术合适,我们就会用。说到底,人们喜欢 Raycast 是因为它的手感,不是因为它底下用了什么。

02为什么要重写

2023 年末,我们开始考虑把 Raycast 带到 Windows。这其实从一开始就是计划好的,但在早期我们想专注一个平台,先把那里的体验做到位,再考虑扩张。

到那个时候,Raycast 已经从一个启动器成长成了一个更广义的生产力平台,有 AI Chat、Notes、扩展、同步、文件搜索等等。最初为启动器设计的架构,开始限制我们下一步能造什么。编译时间在变长,AppKit 越来越频繁地挡路,能深入做 macOS 原生开发的人也越来越难招。即便 Windows 不在计划里,我们也得重新想清楚大部分东西。

所以我们开始规划新 Windows 客户端以及现有 macOS 客户端的技术栈。但首先,任何这种规模的项目都得有个好代号。我们叫它"X-Ray",代表 cross-platform Raycast。

03选技术栈

一开始我们看了 Windows 上有什么可以用来构建原生应用的——坦白说,那边原生 UI 框架的状况远谈不上好。微软一直有"推出 UI 框架然后放弃"的传统。WPF、UWP,到现在的 WinUI 3,后者还相当年轻,没经过大规模实战检验。在 macOS 上用 AppKit 做一个打磨精良的原生应用已经够有挑战,用 WinUI 3 在 Windows 上做这件事感觉风险要大得多。另外,考虑到 Raycast 的大部分扩展应该在两个平台上完全一致地工作,让两个完全独立的原生应用并行运行也让我们不太安心。维护两套独立 UI 栈意味着两倍的工作量,却换不来任何效率提升。

这就很快排除了纯原生路线。又因为 Raycast 的代码库大部分都是 UI,所以也不能简单地"共享一个后端 + 每平台独立写前端"。这把我们引向了基于 Web 的方案。它默认就是跨平台 UI、有海量的库生态、出色的开发体验,并且这块的人才储备比桌面端原生开发要多上几个数量级。Raycast 的扩展本来就建立在 Web 栈上,效果一直很好,所以"把它扩展成整个应用的栈"对我们来说很自然。哪怕只为 Windows 构建,Web 也是合理选择——微软自己就把混合应用列为构建桌面应用的推荐路线之一。它天然跨平台,这让我们觉得也值得在 macOS 上也考虑。

所以我们评估了三个选项:Electron、Tauri,以及自己造一套混合栈。

Electron

Electron 是最显然的选择。说实话,对大多数公司来说,要发桌面应用,它可能就是最合适的那个。维护良好、久经考验、有庞大的生态。VS Code、Linear、Superhuman 这些应用都证明了用它可以做出优秀的产品。Apple 和 Microsoft 没让"用一个大团队为它们的平台造复杂桌面应用"这件事变容易,Electron 正好补上了这个缺口——坦白讲,这是好事。

但对 Raycast 来说,它并不是最合适的。我们的应用和操作系统深度集成。我们依赖全局快捷键、剪贴板管理、辅助功能 API、窗口管理、能浮在其他应用之上但不抢焦点的自定义面板,等等。我们需要触达底层原生代码来精细控制应用行为。哪怕是内层面板的半透明这种小细节,对我们都很重要。Electron 让其中一部分变得可能,但 Web 和原生之间的边界往往很痛苦。我们也不想在 macOS 上塞一份 Chromium,明明可以用系统的 WebKit。一句话——我们需要确保对栈的每一层都有控制权,并且在必要时能轻松回退到原生。Electron 不是干这事的最好选择。

Tauri

Tauri 有类似的约束。它在原生侧给的控制更少,并且当时还年轻到我们不愿意拿公司去赌。所以也很快被排除了。

混合方案(最终选择)

那剩下的就是混合方案。自己造原生 shell 包住系统 WebView——结果发现这恰好给了我们想要的:macOS 上一个正经的 Xcode 工程、Windows 上一个 Visual Studio 工程、完整的平台 API 访问、用系统自己的 WebView 渲染 UI,以及对各部分如何相互通信的完全掌控。为了验证这真的可行,我们早期就做了一个原型。能做出半透明窗口吗?能让原生 tooltip 浮在 WebView 内容之上吗?看起来、用起来会像 Raycast 吗?结果原型出来看起来跟原生应用几乎一模一样——透明的 WebView 和窗口背景融合在一起、tooltip 和动作面板这些用原生覆盖层做出来——本质上就是我们花了多年构建的同一套视觉语言。

不过,它并不是银弹。这条路有真实的额外成本。在你的应用之上,你基本上是在造和维护一份 Electron 开箱即用的基础设施。WebView、原生 shell、Node.js 后端之间的 IPC 必须自己搭、自己调、按平台自己优化。没有社区在帮你解决这些问题。我们选它,是因为 Raycast 的特殊需求。对大多数其他桌面应用,这种权衡并不划算——Electron 处理得已经够好,能给你省下几个月的基础设施工作。

我们还看了几个别的选项:Flutter、Qt、React Native for Desktop、两边都用 Swift(向 The Browser Company 这种勇敢的选择致敬,但我们没那么爱冒险)。它们都被很快排除了——要么缺我们需要的原生控制力,要么对我们这个用户量级来说还不够成熟,或者两者皆有。

04它是怎么搭起来的

从大框架看,Raycast 2.0 由四部分组成:

多种运行时同时存在(Swift/C#、Node、WebView),各层之间必须互相通信。我们混合使用了平台消息处理器和 stdio 传输来把所有东西连起来。为了让这套机制用起来安全,所有接口在一个地方声明,然后为每一侧生成强类型客户端。这给了我们跨所有四种运行时的编译期保证。

Raycast v2 技术栈架构图
Raycast v2 的技术栈

实际操作中,团队大部分人都在 Web 前端和 Node 后端工作——特性就是在那里被构建出来的。原生 shell 只在我们需要从操作系统暴露新东西、或者要为原生手感做优化时才会去碰(下文会讲)。一旦四部分之间的边界定下来,大部分产品工作就不需要跨越它们了。

全新的文件索引器

在 v1 里,文件搜索依赖 Spotlight 的元数据。它大体上能用,但我们被限制在 Spotlight 已索引的内容里,并且它在 Windows 上完全没法用。在 v2 里,我们用 Rust 从零写了自己的文件索引器。它作为独立进程运行,直接扫描文件系统、构建搜索索引,并通过文件系统事件保持索引最新。

在 Windows 上,用常规方式遍历 NTFS 文件系统对我们要求的扫描时间来说太慢了。于是我们专门做了一个 NTFS 扫描器,直接读取主文件表(Master File Table)——这是在几秒钟而不是几分钟内索引完整块硬盘的唯一可行方式。

文件索引器是 Rust 性能价值最明显的地方之一。扫几十万个文件、构建搜索索引这件事得在后台进行,不能影响应用的其他部分。可预测的内存占用、没有 GC 停顿,让这一切成为可能。

05让它"在家里"——感觉像原生

当 UI 跑在 WebView 里时,"感觉原生"到底是什么意思?对我们来说,它归结为一个简单的测试:如果一个人在不知道 Raycast 是用什么写的情况下使用它,他会不会以为这是一个普通的 Mac 应用?只要有任何地方感觉不对——错误的动画、不该有的 hover 状态、popover 在窗口边界处被截断——我们就还没做好。

Raycast v2 总览
Raycast v2 的样子

我们一位 Windows 工程师说得好:我们不是一个"上面洒了点原生钩子的 Web 应用",我们是一个"用 Web 做 UI 的原生应用"。这个区分决定了我们把时间花在哪里。下面要讲的大部分工作不是"让它看起来对",而是"让它表现得对"。

平台规约

让一个 Web 应用在桌面上感觉"不对劲",最容易的方式就是在原生约定该出现的地方继续用 Web 约定。下面是几个我们刻意去匹配或刻意回避的细节:

以上是显而易见的那些。下面是不那么显而易见的工作。

和 WebKit 共事(以及绕开它)

WebKit 是一个很棒的渲染引擎,但它是为"浏览网页"而造的,不是为一个每天显示和隐藏成百上千次的桌面应用而造的。它默认做的一些假设——对 Safari 来说完全合理,但对我们就成了问题。我们花了很多时间学会怎么绕开这些假设。

我们还自建了一套基础设施,可以在运行时切换 WebKit 的 Feature Flags(和 Safari 开发者菜单里那些是同一批)。我们用它在内部解锁 60 FPS 上限、以及为非关键工作启用 requestIdleCallback 调度。

在 Windows 上

WebView2 基于 Chromium,而 Chromium 对节流、渲染和进程管理有自己的一套想法。让 acrylic 毛玻璃背景在自定义标题栏下正确工作,需要原生 shell 和 WebView2 运行时之间小心翼翼地协调。我们自己控制所有初始化参数,从而避免了 WebView2 应用启动时常见的"白色矩形闪一下"问题。

管理多个窗口也比 macOS 上更复杂——每个窗口都需要自己的 WebView2 environment,并搭配合适的 acrylic 效果、自定义 chrome 和输入处理。我们还做了专门的工作,确保 Chromium 在我们的窗口不在前台时不去节流 WebView——因为 Raycast 经常需要在其他应用之后保持更新。

06内存和性能

对基于 Web 的桌面应用最常见的批评就是:慢、臃肿、吃内存。这是个公平的担忧,我们想坦诚地谈谈。

简短版:是的,Raycast v2 比 v1 用更多内存。这个增加是真实的,但它也是有边界的、可度量的、并且我们能持续改进。团队把性能和内存当作一等优先级,不是"等以后再说"的事情。

数字

Raycast v1(完全原生 UI、Node 后端用于扩展)在使用一段时间后通常会停在 200–300 MB 左右。Raycast v2 在类似场景下大约是 350–450 MB。具体数字取决于你装了多少扩展、用哪些特性、加载了多少内容。

是的,这个数字更高,我们不打算藏着掖着。这些数字也还不是最终结果——内存优化是个活跃的工作领域,我们预计在退出测试期前还会进一步压下去。下面是 v2 在主窗口隐藏时(也就是 Raycast 大部分时间所处的状态)的内存粗略拆解:

原生 shell 很轻量。WebKit GPU 进程在窗口隐藏时会降到 20 MB 以下(在你主动使用 Raycast 时它可能会冲高,但你关掉窗口后那部分内存会被释放)。两大主要成本是 WebView 和 Node 后端。

作为对比:一个空的、没有任何内容的 WebView 起步成本大约是 50 MB,一个不 import 任何东西的 Node.js 进程大约是 12 MB。这些基线是这种取舍的一部分。剩下的才是我们的应用代码、加载的模块、图标和缓存的资源——这部分是我们能控制并继续优化的。

不是所有内存都"一样贵"

这并不让更高的内存占用变得无关紧要,但它有助于你理解你在活动监视器里看到的东西。当你在 Mac 上打开活动监视器,每个进程旁边那个数字其实没有看起来那么直接。macOS 会积极使用可用内存——它会缓存文件、压缩不活跃的页、把东西保留在内存里来让系统更快。

几件值得知道的事:

这些都不是"对内存可以不当回事"的借口。我们追踪 phys_footprint(最接近活动监视器显示值的指标),并积极地把它压下去。在开发期间我们已经把 v2 的占用大幅削减——早期构建版本比今天高很多。我们也专门在低内存机器上测试,因为这才是最要紧的地方。但我们希望读者在看到这些数字时有一个正确的心理模型。

抛开内存不谈,有些地方 v2 明显比 v1 快。

我们还没结束。内存和性能是活跃的关注领域,我们清楚还有改进空间。团队在继续压低稳态占用、让更多前端和后端代码懒加载、优化图标和图片处理、收紧 V8 堆。毕竟,它还在测试期。

07取舍

没有任何重写是免费的。下面是什么变好了、什么变难了。

变好的地方

先说积极的。下面这些是我们觉得 Raycast 第二版改善了的地方:

变难的地方

不是所有事情都很完美,所以下面是这套更复杂技术栈带来的不利面:

我们觉得这些取舍是值得的。不是因为缺点不重要,而是因为开发速度、跨平台覆盖、招聘门槛上的收益,会随着时间转化为更好的产品。比较难的部分靠工程努力能解决;比较好的那些部分,用别的方式想拿到会非常难。

08接下来去哪里

如果你看到了这里,你可能在等一个"哪种方案是最好的"的结论。我们其实并不那么思考问题。我们把代码看作手段,不是目的。我们在意的是产品,不是技术栈。我们就是自己的用户,在自己拥有的每台机器上每天使用 Raycast——感觉不对的东西我们就不会发。这是我们的标准,这也是为什么这次重写花了这么久。

Raycast 2.0 现已进入公测。如果有任何地方感觉不对、感觉慢、或者感觉不像 Raycast,告诉我们。这正是我们现在最需要的反馈。

非常感谢实现这一切的团队。一开始还是个原型的东西,现在被送到每一个愿意尝试的人手里。这背后是大量的辛勤工作和对细节的死磕,没有这些就不可能完成。

我们做这件事,是为了继续推动"桌面上的生产力"意味着什么——尤其是在 AI 正在改变人和机器交互方式的当下。有了这套新代码库,我们就能快速行动、在两个平台上发出高质量的应用、紧贴用户真正的需求。还有很多东西在路上。回头见!