一、背景:尝试使用 Rust 构建桌面应用
昨天我基于 Rust 开始搭建自己的桌面 Markdown 编辑器项目(mimenote)1,最初选用了开源的 GPUI Kit2 作为 UI 框架。
最初选择 GPUI Kit,主要是看中了它的原生渲染能力、性能潜力,以及相对完善的桌面 UI 组件体系。
从实际体验来看,GPUI Kit 已经能够完成大部分基础 UI 的构建,并且拥有一些很吸引人的设计理念:
- 现代化 UI 设计: 借鉴了 shadcn/ui 的设计哲学,提供统一的组件风格与设计规范。
- 主题化支持: 提供主题系统与设计 Token,方便构建一致的视觉体验。
- 国际化支持: 提供 i18n 相关能力,适合开发面向不同语言用户的应用。
- 桌面应用组件: 包括 Dock、面板布局、数据表格、表单、输入组件,以及代码编辑器等。
- Rust 原生能力: 结合 GPUI 的 GPU 渲染与原生应用架构,具有性能和资源控制方面的潜力。
某种程度上,我觉得它更像是一个具有 VS Code 部分基础能力的桌面 UI 框架,可以快速构建一套带有侧边栏、编辑区域、命令面板以及可扩展布局的应用。
但它距离一个功能完整的 VS Code 或 Obsidian,仍然存在明显差距。
这里需要说明的是:GPUI Kit 并非完全没有编辑器能力。 它已经具备代码编辑、语法高亮、折叠、搜索,以及 Markdown 渲染等基础设施3。
真正的问题在于,我想开发的并不是普通的代码编辑器,而是一款类似 Obsidian 的 Markdown 笔记应用。
这两者看起来相似,但在编辑体验、交互复杂度和底层能力上的要求并不相同。
二、项目目标:一个类似 Obsidian 的 Markdown 编辑器
这个项目的目标不是简单地打开、修改和保存 Markdown 文件,而是构建一套完整的笔记编辑体验。
理想情况下,它应该具备:
- 完善的 Markdown 编辑能力: 标题、列表、引用、代码块、表格、图片、任务列表等。
- 所见即所得或混合编辑模式: 能够在 Markdown 源码与可视化内容之间自然切换,甚至支持 Live Preview。
- 复杂的编辑交互: 光标定位、文本选区、快捷键、撤销与重做、拖拽、粘贴、输入法等。
- 可扩展的内容系统: 自定义 Markdown 语法、插件、嵌入内容和交互组件。
- 良好的桌面体验: 文件系统集成、多个工作区、主题系统、快速搜索与性能优化。
其中真正复杂的并不是 UI,而是编辑器内核。
一个看起来简单的 Markdown 编辑区域,背后可能涉及文档模型、增量解析、选区映射、输入法合成、编辑事务、历史记录以及渲染状态同步。
例如,在 Obsidian 类似的 Live Preview 模式下,用户输入:
**Hello World**
界面可能需要将其显示为加粗文本,同时仍然保持 Markdown 源码的可编辑性。
当光标进入或者离开这一段内容时,Markdown 标记可能还需要动态显隐。
这已经不是普通文本输入框加上 Markdown 渲染器能够轻松完成的事情。
GPUI Kit 当前面临的问题
GPUI Kit 提供了源码编辑组件和 Markdown 文本渲染组件,但这些能力并不等于一个成熟的富文本 Markdown 编辑器内核。
如果继续沿着当前方向开发,就意味着许多高级功能需要自行实现,或者对已有组件进行深度改造。
对于一个个人项目或者需要快速迭代的产品而言,这部分工作会占据大量精力。
因此,我目前的判断是:GPUI Kit 更适合构建具有复杂桌面布局、基础输入交互和代码编辑需求的应用,而不一定适合作为现阶段 Obsidian 类编辑器的主要技术基础。
这不是说 GPUI 无法实现这些功能,而是实现成本与项目目标之间可能存在不匹配。
三、最大的痛点:AI Agents 驱动开发时的端到端调试
相比编辑器功能本身,我在实际开发过程中还发现了另一个影响更大的问题:
Rust 原生 GUI 在 AI Agents 自动化开发场景下,缺少一条足够低成本、成熟且标准化的视觉反馈闭环。
3.1 目前的开发反馈方式
在这次尝试中,很多功能的验证主要依赖单元测试、集成测试以及编译反馈。
例如,Agent 完成一个 UI 组件后,可以执行:
cargo check:验证代码是否通过类型和借用检查。cargo test:验证单元测试与部分集成逻辑。cargo clippy:检查潜在问题和代码质量。
这些工具都很有价值,尤其是 Rust 编译器提供了相当严格的静态检查能力。
但是,这些检查并不能回答一个关键问题:
界面实际渲染出来之后,到底是不是我想要的样子?
比如:
- 元素是否发生了重叠?
- Dock 布局是否正常?
- 主题切换之后颜色是否正确?
- Markdown 编辑区域的光标是否错位?
- 弹出菜单是否被其他组件遮挡?
- 文本选区是否与实际渲染位置一致?
这些问题并不能单纯依靠编译通过或单元测试通过来判断。
对于 AI Agent 而言,如果没有图形化反馈,就容易出现:
flowchart LR A[生成代码] --> B[编译通过] B --> C[假设 UI 正确] C --> D[继续开发]
但实际上,程序能够运行并不意味着界面或交互符合预期。
3.2 为什么这会产生额外的 Token 消耗?
如果 Agent 无法直接获取可靠的界面状态,它就需要更多的中间步骤来确定问题。
例如:
- 阅读组件实现,尝试推断布局结果。
- 修改 UI 代码。
- 编译并启动应用。
- 获取截图或人工反馈。
- 重新分析视觉问题。
- 修改代码并重复验证。
对于复杂编辑器而言,一个很小的视觉异常都可能涉及布局、文本测量、事件分发和编辑状态等多个模块。
Agent 为定位问题可能需要读取大量上下文,进行多轮推理和代码修改。
这就意味着较高的 Token 消耗和较慢的迭代速度。
当然,Token 成本并不是由 Rust 语言本身直接决定的。
真正影响成本的是:
Agent 为完成一次有效修改,需要经历多少次“观察—推理—修改—验证”的循环,以及每次循环需要消费多少上下文。
如果能够通过测试工具直接告诉 Agent 哪个 DOM 节点出现问题、哪个元素位置不正确,通常就能减少盲目推断。
这也是我重新认识 TypeScript 技术栈优势的重要原因。
四、TypeScript 为什么可能更适合 AI Agents 开发?
过去在技术选型时,我往往会优先考虑性能、内存占用、类型安全和应用体积。
但在 AI Coding 越来越普遍的情况下,我认为需要增加一个新的维度:
技术栈对 AI Agents 是否足够友好。
这不仅仅是大模型会不会写这门语言,更重要的是它能否独立完成开发、运行、观察、验证和修复。
4.1 成熟的浏览器自动化能力
这是 TypeScript + Web 技术栈非常突出的优势。
例如使用 Playwright,可以进行4:
- 根据 DOM 结构定位元素。
- 模拟点击、键盘输入、拖拽等操作。
- 读取页面属性、文本内容和组件状态。
- 对页面进行截图和视觉对比。
- 记录测试过程、DOM 快照、控制台日志与 Trace。
- 自动化验证实际用户操作流程。
这意味着 Agent 不必完全依赖阅读源代码来猜测 UI 结果。
它可以执行真实操作,然后检查应用呈现出的状态。
例如,开发 Markdown 编辑器时,可以自动输入一段 Markdown,检查标题是否渲染成功、文本是否符合预期,再通过截图判断视觉效果。
虽然 DOM 断言不能代替所有视觉检查,但它至少提供了更直接、更结构化的反馈渠道。
相比只能得到“测试通过”或者“测试失败”的结果,Agent 能获得更多可用于定位问题的信息。
4.2 更丰富的编辑器生态
TypeScript 的另一个显著优势在于成熟的编辑器基础设施。
例如:
- CodeMirror 6: 适合 Markdown 源码编辑,具备语法解析、编辑状态、扩展系统等能力5。
- ProseMirror: 提供结构化文档模型、事务和复杂富文本编辑基础6。
- Tiptap: 基于 ProseMirror 的编辑器框架,提供更高层次的扩展与集成接口7。
- Monaco Editor: 适合代码编辑器或 IDE 类型场景。
这些项目并不能开箱即用地复制 Obsidian 的编辑体验,但它们能够显著减少自行开发编辑器底层能力的工作量。
对于 AI Agent 而言,生态成熟还意味着:
- 有更多 API 文档、示例与已知问题可供参考。
- 常见交互问题存在相对成熟的解决路径。
- 可以针对已有扩展接口开发,而不是反复修改编辑器内核。
- 测试方法和调试工具更容易标准化。
4.3 更快的反馈循环
在 Web 技术栈中,Vite 等开发工具能够提供较快的热更新与构建反馈。
对于大部分 UI 修改,不一定需要重新编译完整的桌面应用。
Agent 可以利用较短的反馈路径:
修改 TypeScript → 热更新 → 自动化操作 → 读取 DOM / 截图 → 验证结果
相比之下,Rust 原生 UI 的修改通常涉及编译、启动以及专门的 GUI 测试设施。
即便 Rust 也有增量编译和热重载方案,整个工具链的成熟程度与可复用性仍然可能存在差异。
4.4 类型信息更容易进入 Agent 的工作流
TypeScript 能够通过类型定义、编译诊断、语言服务器和 IDE 提供丰富的静态信息。
例如:
tsc --noEmit可以快速反馈类型错误。.d.ts文件可以帮助 Agent 理解第三方库的 API。- ESLint 可以提供统一的代码规范。
- Vitest 可以快速执行逻辑测试。
- Playwright 可以覆盖浏览器交互测试。
Rust 在类型检查和编译器诊断方面同样很强,甚至在很多关键约束上更加严格。
因此,TS 的优势并不是“类型系统比 Rust 更适合 AI”,而是其 Web 应用开发链路已经将编辑、构建、调试、测试和 UI 自动化高度整合。
4.5 对多 Agent 协作更加友好
当多个 Agent 同时参与开发时,可以将任务划分为:
- 一个 Agent 开发组件。
- 一个 Agent 实现业务逻辑。
- 一个 Agent 编写测试。
- 一个 Agent 执行端到端验证。
如果项目具备稳定的组件接口、自动化测试和类型约束,这种分工就更容易形成闭环。
当然,多 Agent 协作的效果仍然取决于架构设计、任务边界和并发修改管理,并非 TypeScript 天然保证。
五、TypeScript 对 AI Agents 的劣势
虽然 TypeScript 的开发生态很有优势,但它也存在一些容易被忽略的问题。
5.1 编译通过不代表运行正确
TypeScript 的静态类型系统主要作用于编译阶段。
类型检查通过,并不意味着运行时状态一定正确。
例如:
- 外部 API 返回了不符合预期的数据。
- 异步请求出现竞态条件。
- 组件状态发生不一致。
- 某个依赖在运行时出现异常。
- 类型断言掩盖了实际问题。
Agent 可能生成一段看起来符合类型规范、实际上却存在逻辑缺陷的代码。
相比之下,Rust 的所有权、借用检查和类型约束能在编译期间阻止更多类别的问题。
对于复杂的并发任务、文件操作和核心数据处理,Rust 可能有更明显的优势。
5.2 过度依赖第三方生态
TypeScript 的库非常丰富,但丰富也意味着更多选择和依赖。
在 AI 编程中,一个常见问题是:Agent 很容易为了实现一个小功能引入新的依赖。
这可能导致:
- 项目依赖数量迅速增加。
- 不同组件之间出现重复功能。
- 依赖版本冲突。
- 使用过时 API。
- 代码结构不统一。
- 长期维护成本增加。
尤其在编辑器项目中,如果同时混用多套状态管理、Markdown 解析和富文本编辑方案,很容易引发架构混乱。
因此,成熟生态虽然能够帮助 Agent 快速实现功能,但也会增加约束技术选型的必要性。
5.3 容易产生只解决表面问题的代码
当 AI Agent 能够快速修改 DOM 和 CSS 时,也更容易采用局部修补的方式解决问题。
例如:
- 不合理地增加
z-index。 - 通过绝对定位掩盖布局问题。
- 使用额外状态变量绕开原有状态管理。
- 添加重复事件监听。
- 通过
setTimeout规避异步状态问题。
这些修改可能暂时让测试通过,却留下长期维护隐患。
反馈速度快,不代表架构质量一定更高。
如果缺少明确的工程约束,Agent 可能让项目逐渐积累技术债务。
5.4 浏览器环境与桌面环境不完全一致
即便使用 Tauri + TypeScript,浏览器中测试通过,也不能证明最终桌面应用完全正常。
例如:
- WebView 在不同操作系统上的渲染差异。
- 原生文件系统权限问题。
- 应用窗口生命周期。
- WebView 与 Rust 后端之间的 IPC。
- 系统菜单和快捷键冲突。
- 平台特定的输入法行为。
这些问题仍然需要在真实桌面运行环境中验证。
因此,Web 自动化能够显著改善 UI 开发体验,但不能完全代替桌面端到端测试。
5.5 性能与资源控制不如原生方案直接
对于大型笔记库、长文档、高频编辑以及大量交互组件,性能仍然是需要关注的问题。
Web 技术栈可能涉及:
- JavaScript 主线程阻塞。
- 大量 DOM 节点的渲染开销。
- 垃圾回收导致的延迟。
- React 等框架的额外状态更新成本。
- WebView 自身的资源占用。
不过,这些问题并不意味着 Web 编辑器一定性能差。
CodeMirror 等成熟编辑器已经针对大文档和频繁编辑进行过大量优化。
最终性能取决于具体实现,而不是简单地由 Rust 或 TypeScript 的语言标签决定。
六、GPUI Shell:一个值得保留的技术方向
在研究 GPUI Kit 时,我还注意到官方提供了 GPUI Shell8。
这一方案值得单独讨论。
GPUI Shell 的核心目标并不是把 GPUI 改造成另一个 Electron 或 Tauri,而是:
让 Rust 编写的 GPUI 应用能够通过 JavaScript 进行扩展。
它主要有两个使用场景:
- 插件扩展: 在现有 Rust 应用中动态加载 JavaScript 插件,扩展面板、界面或业务逻辑。
- 完整应用: 利用 JavaScript 编写应用层,通过 GPUI Shell 提供的接口构建整个应用。
官方更强调第一个场景,也就是插件化能力。
这种设计非常有吸引力,因为它试图兼顾 Rust 原生应用和动态脚本扩展的优势。
例如,主应用可以继续使用 Rust 处理渲染、文本编辑以及系统能力,JavaScript 则负责 UI 组合和业务逻辑。
另外,GPUI Shell 已经提供热重载、类型定义生成和脚本检查等能力,对开发迭代也有帮助。
但是这里存在一个重要区别:
GPUI Shell 支持 JavaScript,不代表它自动拥有 Web 生态的全部优势。
它不是浏览器环境,也没有 DOM、HTML、CSS 或完整的 Node.js 运行环境。
因此,Playwright、Tiptap、CodeMirror 等依赖浏览器或 DOM 的方案,并不能直接作为 GPUI Shell 的原生组件使用。
换句话说,采用 GPUI Shell 并不能直接解决我当前最关注的两个问题:
- 缺乏成熟的 Obsidian 类 Markdown 编辑内核。
- 缺少可直接复用的 Web UI 自动化调试闭环。
尽管如此,它仍然是一个值得保留的技术方向。
如果未来希望构建性能优先、具有高度插件化能力的原生桌面应用,GPUI Shell 依然很有研究价值。
七、重新考虑技术选型
经过这次尝试,我认为应该重新评估构建 Markdown 桌面应用的技术路线。
| 技术方案 | 主要优势 | 主要挑战 |
|---|---|---|
| Rust + GPUI Kit | 原生渲染、性能潜力、Rust 类型与内存安全 | 高级 Markdown 编辑器能力需要较多定制开发 |
| Rust + GPUI Shell | 原生 UI、JS 扩展、插件架构 | 不具备浏览器 DOM 和 Web 编辑器生态 |
| Tauri + TypeScript | Rust 原生能力与 Web 编辑器生态结合,调试体系成熟 | 仍需处理 WebView 与 Rust 间的集成和跨平台差异 |
| Electron + TypeScript | 浏览器环境统一、生态完善、开发反馈快 | 安装体积、运行时资源占用和进程管理成本 |
对于当前项目,我更倾向于继续采用 Tauri + TypeScript9。
原因不是 Rust 原生 UI 技术路线不好,而是当前的核心需求决定了项目最重要的部分是 Markdown 编辑器,而不是 UI 渲染框架本身。
更合理的职责划分
TypeScript 负责应用层与编辑器交互:
- Markdown 编辑器。
- 富文本与 Live Preview。
- 工作区 UI。
- 主题、国际化及交互。
- 编辑器插件和扩展。
- 前端自动化测试。
Rust 负责性能敏感与系统级能力:
- 文件系统访问。
- 本地文件索引与搜索。
- 大型笔记库的数据处理。
- 操作系统集成。
- 后台任务。
- 必要的性能优化模块。
如此一来,可以将两种语言各自的优势结合起来,而不是为了实现完全原生的 UI 而承担大量编辑器基础设施开发成本。
八、一个新的技术选型指标:Agent 开发友好度
这次尝试让我意识到,以前评价技术栈时,主要考虑以下维度:
- 运行性能。
- 内存占用。
- 应用体积。
- 跨平台支持。
- 生态成熟度。
- 长期可维护性。
但现在还应该加入一个重要指标:
Agent 开发友好度(Agent Development Friendliness)。
我认为这个指标可以从以下几个方面衡量:
| 维度 | 核心问题 |
|---|---|
| 代码可理解性 | Agent 能否准确理解 API、项目架构和类型约束? |
| 反馈速度 | 修改代码后能否快速构建和运行? |
| 可观测性 | Agent 能否直接读取应用状态、日志和界面结构? |
| 可测试性 | 是否容易构建单元、集成和端到端测试? |
| 自动修复能力 | 失败信息是否足够明确,使 Agent 能自主定位问题? |
| 上下文成本 | 完成一次有效修改需要读取多少代码和输出? |
| 生态支持 | 是否有成熟的文档、组件和自动化工具? |
在 AI Agents 主导大量重复开发工作的情况下,技术栈的生产力不仅取决于开发者写代码的速度,也取决于 Agent 能否自主完成验证。
例如,两个技术方案在运行性能上可能相差明显,但如果其中一个能够让 Agent 更快地完成开发、测试和修复,那么在产品初期,它带来的整体收益可能反而更高。
不过,这仍然是一种需要实际测量的工程假设。
要验证它,可以选择相同的 UI 功能,分别记录 Rust 原生和 TypeScript 实现过程中的开发耗时、失败次数、工具调用次数、Token 消耗以及最终缺陷数量。
这样才能避免仅凭主观体验得出过于绝对的结论。
九、总结与反思
这次尝试并不意味着 Rust 不适合桌面应用,也不意味着 GPUI Kit 不够优秀。
相反,GPUI Kit 在原生 UI、组件设计和插件化探索方面具有独特价值。
但我认为技术选型首先应该服务于产品需求。
对于一款类似 Obsidian 的 Markdown 编辑器来说,最复杂、最重要的部分是编辑器本身。
成熟的富文本编辑基础设施,以及能够被 AI Agents 自动化验证的开发环境,比单纯追求原生 UI 更符合项目当前阶段的需求。
这次经历让我形成了三个新的认识:
第一,能构建 UI,不等于适合构建复杂编辑器。
基础 UI 组件的完善程度,与富文本编辑器内核的成熟程度是两个不同的问题。
第二,语言性能不等于开发效率。
Rust 能够提供优秀的性能和安全性,但如果为了实现产品的核心功能,需要投入大量底层开发工作,那么这种优势在短期内未必能够抵消工程成本。
第三,AI Agents 改变了技术选型的评价标准。
技术栈是否具备成熟的自动化测试、清晰的错误反馈、可访问的界面状态和较短的开发闭环,正在成为一个越来越重要的工程因素。
最终结论
对于当前的 Markdown 编辑器项目:
- 以 Tauri + TypeScript 作为主要技术路线。
- 优先采用成熟的 Markdown 编辑器基础设施。
- 使用 Rust 实现系统能力和性能敏感模块。
- 将 GPUI Kit / GPUI Shell 保留为未来的技术探索方向。
- 在开发过程中,优先建设 Agent 能够自主执行的测试与调试闭环。
这次最大的收获,不是发现了某个框架的不足,而是重新理解了技术选型的核心:
最适合项目的技术,并不一定是运行速度最快、架构最先进的技术,而是在当前需求、开发资源和工程约束下,能够以合理成本持续交付可靠产品的技术。
尤其在 AI Coding 逐渐成为日常开发方式的今天,让 Agent 看得见问题、理解得了问题、验证得了修改,可能比单纯让 Agent 写得出代码更加重要。
hencter/mimenote — 本地优先 Markdown 知识库桌面应用. https://github.com/hencter/mimenote ↩︎
GPUI Kit 官方文档. https://gpui-kit.com/docs/ ↩︎
GPUI Kit Editor 组件. https://gpui-kit.com/zh-CN/component/editor/ ↩︎
Playwright Trace Viewer. https://playwright.dev/docs/trace-viewer ↩︎
CodeMirror. https://codemirror.net/ ↩︎
ProseMirror. https://prosemirror.net/ ↩︎
Tiptap. https://tiptap.dev/ ↩︎
GPUI Shell 官方中文文档. https://gpui-kit.com/zh-CN/shell/ ↩︎
Tauri. https://tauri.app/ ↩︎
