技术栈选型决策
选型背景:Windows 原生 UI 框架的现状
Raycast 团队首先调研了 Windows 平台的原生 UI 框架选项。调研结果令人担忧:
“Microsoft has a history of introducing UI frameworks and then moving on.”
(微软有一系列引入 UI 框架然后又转向其他的历史。)
Windows UI 框架演变史
| 框架 | 时期 | 现状 | 问题 |
|---|---|---|---|
| WPF | 2006-至今 | 维护模式 | 老旧,缺少现代特性 |
| UWP | 2015-2020s | 边缘化 | 微软已转向 WinUI |
| WinUI 3 | 2020-至今 | 新兴 | 相当年轻,未经过大规模实战检验 |
Raycast 团队对 WinUI 3 的评估结论:
“If building a polished native app on macOS with AppKit is already challenging, doing it on Windows with WinUI 3 felt like a much bigger risk.”
(如果基于 AppKit 在 macOS 上构建精致的原生应用已经很具挑战性,那么用 WinUI 3 在 Windows 上做同样的事风险要大得多。)
纯 Native 路线的根本问题
即使不考虑 Windows,纯 Native 路线还存在更深层的结构性问题:
- 无法共享 UI 代码:Raycast 的大部分代码是 UI 层,无法通过 “共享后端 + 独立前端” 的方式实现
- 扩展生态的一致性:Raycast 的扩展应该在两个平台上表现完全一致,维护两套独立的 Native UI 栈会使这一目标难以实现
- 双倍工作量:两个独立的 Native 应用意味着两倍的工作量,但不会带来两倍的速度
评估方案一:Electron
Electron 的优势
Raycast 团队对 Electron 的评价非常客观和尊重:
“Electron would be the obvious choice. And honestly, for most companies it’s probably the right one to ship a desktop app.”
(Electron 是显而易见的选择。说实话,对大多数公司来说,它可能是发布桌面应用的正确选择。)
Electron 的核心优势:
- 成熟稳定:VS Code、Linear、Superhuman 等优秀产品证明了其可行性
- 生态庞大:丰富的 npm 包和社区支持
- 维护良好:持续更新,bug 修复及时
团队进一步指出:
“Apple and Microsoft haven’t made it easy to create complex desktop apps with a big team for their platforms, which is why Electron fills that gap. Genuinely, we think that’s a good thing.”
(苹果和微软都没有让大型团队为它们的平台创建复杂桌面应用变得容易,这就是为什么 Electron 填补了这个空白。我们真心认为这是一件好事。)
为何放弃 Electron
尽管认可 Electron 的价值,Raycast 团队最终判定它不适合自己的产品:
1. 深度 OS 集成的需求
Raycast 深度依赖系统级 API:
- 全局快捷键(Global hotkeys)
- 剪贴板管理
- 无障碍 API(Accessibility APIs)
- 窗口管理
- 可悬浮于其他应用之上的自定义面板(不抢夺焦点)
- 内部面板的透明度控制
“We need access to low-level native code to have fine-grained control over how the app behaves.”
(我们需要访问底层 Native 代码,以对应用行为进行精细控制。)
2. Web-Native 边界的痛苦
虽然 Electron 可以实现上述部分功能,但 Web 代码与 Native 代码的边界是痛苦的:
- IPC 通信的复杂度
- Native 模块的维护负担
- 某些细节(如透明度控制)实现困难
3. 资源使用的考量
“We also didn’t want to bundle Chromium on macOS when we could use the system’s WebKit instead.”
(我们不想在 macOS 上打包 Chromium,当我们可以使用系统自带的 WebKit 时。)
4. 控制权的根本需求
最根本的决策依据:
“To put it simply, we needed to make sure we were in control of every part of the stack and could easily fall back to native where needed.”
(简而言之,我们需要确保对技术栈的每个部分都有控制权,并且可以在需要时轻松回退到 Native。)
评估方案二:Tauri
Tauri 的吸引力
Tauri 作为新兴的 Rust 框架,提供了与 Raycast 团队技术偏好相符的选择:
- Rust 语言(团队已在其他组件中使用 Rust)
- 系统 WebView(与 Raycast 的最终选择一致)
- 较小的资源占用
排除原因
Raycast 团队仅用了简短的一句话解释排除原因:
“Tauri had similar constraints. It gives you less control on the native side, and at the time it was still young enough that we didn’t want to bet the company on it.”
(Tauri 有类似的限制。它在 Native 侧提供的控制权更少,而且当时它还不够成熟,我们不想把公司押注在上面。)
关键风险点:
- Native 侧控制权不足(无法满足 Raycast 的精细控制需求)
- 2023-2024 年期间,Tauri 的生态和稳定性尚未经过大规模商业应用验证
评估方案三:其他快速排除的选项
团队还简要评估了其他几个选项,但都在早期阶段排除了:
| 方案 | 排除原因 |
|---|---|
| Flutter | 缺乏所需的 Native 控制能力 |
| Qt | 生态成熟度不足,且不完全符合团队技术栈偏好 |
| React Native for Desktop | 成熟度不够,无法满足产品质量要求 |
| Swift on Windows | 团队特别提到了 The Browser Company 的勇敢尝试,但明确表示 “we’re not as adventurous”(我们没有那么冒险) |
最终方案:自定义 Hybrid 架构
架构设计
经过多轮评估,Raycast 团队选择了自定义混合架构:为每个平台构建独立的 Native 外壳,内部使用系统 WebView 渲染 UI。
flowchart TB
subgraph "macOS"
M1[Xcode Project<br/>Swift + AppKit] --> M2[WKWebView]
M2 --> M3[React Frontend]
end
subgraph "Windows"
W1[Visual Studio Project<br/>C# + WPF] --> W2[WebView2]
W2 --> W3[React Frontend]
end
M3 <-->|Shared| Backend[Node Backend]
W3 <-->|Shared| Backend
Backend <-->|Shared| Rust[Rust Core]
早期原型验证
在正式决定前,团队构建了一个原型来验证关键假设:
“Could we get translucent windows? Native tooltips over WebView content? Would it look and feel like Raycast?”
(我们能实现透明窗口吗?能在 WebView 内容上显示原生提示吗?看起来和用起来会像 Raycast 吗?)
原型结果:“The prototype came out looking nearly identical to the native app.”(原型看起来几乎和原生应用一模一样。)
原型验证的核心特性:
- 透明 WebView 与窗口背景融合
- 原生覆盖层(提示框、操作面板)
- 与多年打造的视觉语言保持一致
隐性成本:你其实在构建自己的 Electron
Raycast 团队对这一选择的代价有清醒的认识:
“Though, it wasn’t a silver bullet. This approach comes with real overhead. On top of your app, you’re essentially building and maintaining the infra that Electron gives you out of the box.”
(然而,这不是银弹。这种方式带来真实的开销。除了你的应用,你本质上是在构建和维护 Electron 开箱即用的基础设施。)
具体开销包括:
| 开销类别 | 说明 |
|---|---|
| IPC 基础设施 | WebView、Native Shell、Node 后端之间的通信需要自行搭建、调试和优化 |
| 跨平台差异处理 | 每个平台的 WebView 行为差异需要单独处理 |
| 缺乏社区支持 | 没有现成的解决方案,所有问题需要自己解决 |
为何仍然选择这条路
尽管代价高昂,Raycast 团队仍然认为这一选择对自己的产品是正确的:
“We chose it because of how Raycast works. This tradeoff doesn’t make sense for most other desktop apps. Electron handles it well enough and saves you months of infra work.”
(我们选择它是因为 Raycast 的工作方式。这个权衡对大多数其他桌面应用来说没有意义。Electron 处理得足够好,可以为你节省数周的基础设施工作。)
Raycast 的独特性使其值得:
- 对 OS 集成的深度需求
- 对 UI 精细控制的执着追求
- 跨平台一致性对扩展生态至关重要
- 长期产品愿景需要完全掌控技术栈
选型决策框架总结
quadrantChart
title "桌面应用技术方案权衡矩阵"
x-axis "低开发速度 --> 高开发速度"
y-axis "低控制力 --> 高控制力"
quadrant-1 "理想区域:高控制力 + 高速度"
quadrant-2 "高风险:低控制力 + 高速度"
quadrant-3 "不适用:低控制力 + 低速度"
quadrant-4 "专业需求:高控制力 + 低速度"
"纯 Native (Swift/C#)": [0.2, 0.95]
"Electron": [0.85, 0.4]
"Tauri": [0.7, 0.55]
"Flutter": [0.75, 0.45]
"Custom Hybrid (Raycast v2)": [0.6, 0.9]
Raycast 的选择位于 “高控制力” 象限,但相比纯 Native 方案,通过 Web 技术提升了开发速度。这种位置映射解释了为什么 Electron(高速度、中等控制力)和 Tauri(中高速度、中等控制力)都不满足 Raycast 的特定需求。
关键决策启示
- 没有普适的最佳方案:Raycast 明确承认 Electron 对大多数公司是正确选择,但特定需求(深度 OS 集成、精细 UI 控制)使其不适合
- 原型验证降低风险:在全面投入前构建原型验证核心假设(透明窗口、Native 覆盖层、Native feel)
- 清醒认识代价:团队清楚自己 “在构建自己的 Electron”,并认为这一代价对产品长期发展值得
- 控制权优先于便利性:当产品愿景需要精细控制时,愿意承担额外的基础设施维护成本