← 返回 关于

新版 Raycast 技术深潜

2026-05-15 · 原文链接

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

macOS 和 Windows 上的 Raycast 2.0

为了做到这一点,我们从头重写了应用。新的架构和技术栈混合了 TypeScript、Swift、C#、Rust、Node 和 React。Web 技术从一开始就是 Raycast 的一部分,支撑着扩展和 Notes。在 v2 中,我们进一步加码,同时保持应用一贯的原生感和速度。

如果发布文章讲的是新功能,那么这篇讲的是它如何被构建。包括重写背后的故事、一路上做出的取舍,以及完成这样规模重写所需的工作。难点不是让 Raycast 跑起来。难点是让它感觉对。

我们从哪里开始

Raycast v1 的核心,是一个基于 AppKit、用 Swift 构建的原生 macOS 应用。我们几乎从不使用标准 UI 组件。它们不是为我们关心的键盘优先、面向高阶用户的工作流而设计的,所以我们自己构建。每一行列表、每个快捷键、每个默认行为都由我们处理。我们也没有大量使用 SwiftUI。它和 Raycast 并行成熟,但在性能和控制力上始终没有达到我们的标准。v1 中唯一使用它的地方,是每年的 Wrapped 功能,而且它和应用其他部分隔离得很好。

扩展生态则完全位于另一套技术栈上:React、TypeScript 和 Node.js,UI 以声明式方式描述,并由原生应用渲染。Felix 在这里详细写过这套架构。为第三方开发者选择熟悉的技术栈,是商店现在拥有数千个扩展、覆盖人们几乎所有常用工具的重要原因。API 也被设计为可移植。扩展本身不会假设自己运行在 macOS 上,这让我们去年得以把目录中很大一部分带到 Windows。

Raycast Notes 是我们第一次在应用中为一个主要功能使用 web view。编辑器是一个挂载在原生窗口 web view 内的 React 应用。这是在测试我们能否完全用 Web 技术构建一个界面,同时不破坏应用其他部分的手感。它成功了,Notes 也被大量 macOS 和 iOS 用户每天使用。

虽然 v1 的核心是原生应用,但只要 Web 技术合适,我们一直会使用它。归根结底,人们喜欢 Raycast,是因为它的感觉,而不是因为底层用了什么。

为什么要重写

2023 年末,我们开始思考把 Raycast 带到 Windows。其实这从第一天起就是计划的一部分,但早期我们想专注于单个平台,先把体验打磨到位,再考虑扩展。

到那时,Raycast 也已经从一个 launcher 发展成更广义的生产力平台,拥有 AI Chat、Notes、扩展、同步、文件搜索等能力。最初为 launcher 构建的架构,开始限制我们接下来能构建什么。编译时间越来越长,AppKit 越来越频繁地挡路,能深入原生 macOS 的人也越来越难找。即使不考虑 Windows,我们也需要重新思考其中大部分内容。

于是我们开始为新的 Windows 客户端和现有 macOS 客户端一起寻找技术栈。不过首先,任何这样的项目都需要一个好代号。我们称它为“X-Ray”,意思是 cross-platform Raycast。

选择技术栈

我们先研究了 Windows 上构建原生应用有哪些选择——坦率说,那里原生 UI 框架的状态远谈不上好。Microsoft 有不断引入 UI 框架然后转向下一套的历史:WPF、UWP,以及现在仍然较年轻、没有被广泛实战检验的 WinUI 3。如果用 AppKit 在 macOS 上构建一个精致的原生应用已经很有挑战,那么用 WinUI 3 在 Windows 上做这件事,风险看起来大得多。此外,想到要运行两个独立的原生应用,我们也不太放心,因为 Raycast 的大多数扩展都应该在两个平台上以相同方式工作。维护两套独立 UI 技术栈意味着两倍工作量,却不会让我们更快。

这很快排除了完全原生路线。又因为 Raycast 代码库的大多数都是 UI,我们不能只是共享一个后端,然后为每个平台构建独立前端。这把我们指向了基于 Web 的技术栈。它默认提供跨平台 UI、巨大的库生态、很好的开发者体验,以及比原生桌面大几个数量级的人才池。Raycast 扩展已经构建在 Web 技术栈上,并且一直运行得很好,所以探索把它用于整个应用显得很自然。即使我们只为 Windows 构建,Web 也是合理选择(Microsoft 也把混合应用列为构建桌面应用的推荐路径之一)。它天然跨平台这一点,也让它值得被用于 macOS。

于是我们评估了三个选项:Electron、Tauri,以及构建自己的混合技术栈。

Electron 是显而易见的选择。说实话,对大多数公司来说,它可能就是发布桌面应用的正确选择。它维护良好、经过实战检验,并且拥有庞大生态。VS Code、Linear、Superhuman 这样的应用证明了你可以用它构建优秀产品。Apple 和 Microsoft 并没有让大团队为各自平台构建复杂桌面应用变得容易,所以 Electron 填补了这个空白。真心说,我们认为这是件好事。

但对 Raycast 来说,它不是最合适的选择。我们的应用和操作系统深度集成。我们依赖全局快捷键、剪贴板管理、辅助功能 API、窗口管理、可以浮在其他应用上方但不抢焦点的自定义面板,等等。我们需要访问低层原生代码,才能细粒度控制应用行为。甚至内部面板的半透明这样的小细节,对我们也很重要。Electron 可以实现其中一部分,但 Web 和原生代码之间的边界可能很痛苦。我们也不想在 macOS 上打包 Chromium,因为可以使用系统自带的 WebKit。简单说,我们需要确保自己控制技术栈的每个部分,并能在需要时轻松退回原生。Electron 在这方面不是最佳选择。

Tauri 有类似限制。它在原生侧给你的控制更少,而且当时还足够年轻,我们不想把公司押在它上面。所以我们很快排除了它。

剩下的就是混合方案。事实证明,构建自己的原生 shell 来包裹系统 WebView,正好能给我们需要的东西:macOS 上一个正规的 Xcode 项目,Windows 上一个 Visual Studio 项目;完整访问平台 API;用系统自己的 WebView 做 UI;并完全控制各部分之间如何通信。为了验证这是否真的可行,我们早期做了一个原型。我们能做出半透明窗口吗?能在 WebView 内容上显示原生 tooltip 吗?它会看起来和感觉起来像 Raycast 吗?原型最终看起来几乎和原生应用一样。透明 web view 与窗口背景融合,tooltip 和 action panel 等元素使用原生 overlay。基本上就是我们花多年构建的同一套视觉语言。

不过,这不是银弹。这个方案有真实开销。除了构建应用本身,你本质上还在构建并维护 Electron 开箱即用提供的基础设施。WebView、原生 shell 和 Node.js 后端之间的 IPC,需要在每个平台上设置、调试和优化。没有社区替你解决这些问题。我们选择它,是因为 Raycast 的工作方式。这个取舍对大多数其他桌面应用并不划算。Electron 能把这些处理得足够好,并省下几个月基础设施工作。

我们还看过其他几个选项:Flutter、Qt、React Native for Desktop,以及在两个平台上都运行 Swift(向 The Browser Company 的勇气致敬,但我们没那么冒险)。不过我们很早就排除了它们。它们要么缺少我们需要的原生控制,要么对我们的用户规模来说还不够成熟,或者两者兼有。

它是如何构建的

从高层看,Raycast 2.0 由四部分组成:

当多个运行时同时存在(Swift/C#、Node、WebView)时,不同层需要相互通信。我们混合使用平台 message handlers 和 stdio transport 来连接一切。为了让这件事安全可维护,接口在一个地方声明,并为每一侧生成类型化 client。这让我们在四个运行时之间获得编译期保证。

实践中,团队大多数人在 Web 前端和 Node 后端工作。功能主要在那里构建。只有当我们需要暴露新的 OS 能力,或为原生手感做优化(下面会讲)时,才会触碰原生 shell。一旦四部分之间的边界设定好,大多数产品工作就不需要跨越它们。

新文件索引器

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

在 Windows 上,用常规方式遍历 NTFS 文件系统太慢,达不到我们需要的扫描时间。所以我们构建了专门的 NTFS 扫描器,直接读取 Master File Table——这是在几秒而不是几分钟内索引整块硬盘的唯一实用方式。

索引器是最能体现 Rust 性能价值的地方之一。扫描数十万个文件并构建搜索索引,需要在后台完成,同时不影响应用其他部分。可预测的内存使用和没有 GC pause,让这成为可能。

让它感觉像回到家

当你的 UI 运行在 WebView 中时,“原生感”到底意味着什么?对我们来说,它归结为一个简单测试:如果有人不知道 Raycast 是用什么构建的,他会不会以为这是一个普通 Mac 应用?如果任何东西感觉不对——错误的动画、不合适的 hover 状态、被窗口边缘裁切的 popover——那就是我们没做好。

我们的一位 Windows 工程师说得很好:我们不是一个撒了些原生 hook 的 Web 应用。我们是一个使用 Web 做 UI 的原生应用。这个区别决定了我们把时间花在哪里。下面大多数工作不是为了让东西看起来对,而是为了让东西行为对。

平台惯例

让一个 Web 应用在桌面上感觉不对,最简单的方法就是在已有原生惯例的地方沿用 Web 惯例。以下是我们刻意匹配或避免的一些事情:

这些都是显而易见的部分。更不显眼的工作在下面。

与 WebKit 协作,也绕过它

WebKit 是一个很好的渲染引擎,但它是为网页浏览构建的,不是为每天显示和隐藏数百次的桌面应用构建的。开箱状态下,它会做出一些对 Safari 完全合理、但会给我们造成问题的假设。我们花了很多时间学习如何绕过它们。

我们还构建了基础设施,可以在运行时切换 WebKit Feature Flags(也就是 Safari Develop 菜单中可用的那些)。内部我们用它来解锁 60 FPS 上限,并启用 requestIdleCallback 来调度非关键工作。

在 Windows 上

WebView2 基于 Chromium,而 Chromium 对节流、渲染和进程管理有自己的想法。要让 acrylic blur-behind 效果和自定义标题栏协同工作,需要原生 shell 和 WebView2 runtime 之间非常细致的配合。我们自己控制所有初始化参数,这让我们可以避免 WebView2 应用启动时常见的白色矩形闪烁。

管理多个窗口也比 macOS 更复杂——每个窗口都需要自己的 WebView2 environment,并配置正确组合的 acrylic 效果、自定义 chrome 和输入处理。我们还必须做专门工作,确保当窗口没有焦点时 Chromium 不会节流 WebView,因为 Raycast 经常需要在其他应用后方时仍然更新。

内存与性能

对基于 Web 的桌面应用最常见的批评,是它们慢、臃肿、吃内存。这是合理担忧,我们也想诚实回应。

简短版本:是的,Raycast v2 比 v1 使用更多内存。增长是真实的,但也是有边界、可衡量,并且我们可以持续改进的。团队把性能和内存视为一等优先级,而不是以后再处理的东西。

数字

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

这更高,我们不想掩盖它。这些数字也不是最终结果,因为内存优化是当前重点,我们预计随着 beta 结束还会进一步降低。下面是 v2 在主窗口隐藏时的粗略内存拆分(Raycast 大多数时间都处于这个状态):

原生 shell 很轻。窗口隐藏时,WebKit GPU 进程会降到 20 MB 以下(你主动使用 Raycast 时可能飙得更高,但关闭窗口后这部分内存会释放)。两个主要成本是 WebView 和 Node 后端。

作为对比,一个没有内容的空 WebView 基线成本约为 50 MB,一个没有 imports 的裸 Node.js 进程约为 12 MB。这些基线是取舍的一部分。其余部分是我们的应用代码、已加载模块、图标和缓存资源,这是我们可以控制并持续优化的。

不是所有内存都一样

这并不意味着更高的占用无关紧要,但它有助于解释你在 Activity Monitor 中看到的数字。当你在 Mac 上打开 Activity Monitor 时,每个进程显示的数字并没有看起来那么直接。macOS 会积极使用可用 RAM:缓存文件、压缩不活跃页面,并把东西留在内存里,让系统更快。

有几件值得知道的事:

这些都不是粗心使用内存的借口。我们跟踪 phys_footprint(最接近 Activity Monitor 显示值的指标),并积极降低它。开发过程中我们已经显著降低了 v2 的 footprint——早期构建比今天高得多。我们也特别在低内存机器上测试,因为那里最重要。但我们希望读者在看这些数字时拥有正确心智模型。

撇开内存,v2 有些地方明显快于 v1。

我们还没完成。内存和性能仍是活跃重点,我们知道还有改进空间。团队正在进一步降低稳态 footprint,让更多前端和后端懒加载,优化图标和图片处理,并收紧 V8 heap。毕竟,它还在 beta。

取舍

没有免费的重写。下面是变好的地方和变难的地方。

变好的地方

先说积极面。下面是我们觉得 Raycast 第二版中得到改善的部分:

变难的地方

并非一切都完美,下面是更复杂技术栈的缺点:

我们认为这些取舍是值得的。不是因为缺点不重要,而是因为开发速度、跨平台覆盖和招聘上的收益,会随时间直接转化为更好的产品。更难的部分可以通过工程努力解决。更好的部分则很难通过其他方式获得。

接下来去哪里

如果你读到了这里,可能期待我们给出哪种方案“最好”的判决。我们其实不这样思考。我们把代码视为达成目的的手段。对我们重要的是产品,而不是技术栈。我们是自己的用户,每天在自己拥有的每台机器上使用 Raycast;如果感觉不对,我们就不会发布。这就是标准,也是这次重写花了这么久的原因。

Raycast 2.0 现在已经进入公开 beta。如果有什么感觉不对、感觉慢,或感觉不像 Raycast,请告诉我们。这正是我们现在需要的反馈。

简单感谢一下完成这件事的团队。从一个原型开始,如今它已经到了每个想尝试它的人手里。没有大量努力和对细节的反复打磨,这不可能发生。

我们做这件事,是为了继续推动桌面生产力的意义,尤其是在 AI 正在改变人们与机器交互方式的当下。有了这个新代码库,我们可以快速前进,在两个平台上发布高质量应用,并始终贴近用户真正需要的东西。后面还有很多。很快再见!