Desktop 项目结构
01MVP Desktop 模板的前端、runtime adapter、Tauri 和后端接入边界
你将学到
- 桌面模板的四层架构和各自的职责边界
- 每个目录放什么、不放什么
- 桌面端如何和后端通信——不直接 import Web 代码
src/runtime为什么是 Tauri 的唯一入口- 添加新系统能力的正确顺序
目录总览
products/01mvp/apps/desktop
├── src
│ ├── components # 模板 UI 组件
│ ├── config # VITE_DESKTOP_* 校验
│ ├── hooks # 偏好等客户端状态
│ ├── lib # Better Auth、oRPC、QueryClient
│ ├── runtime # Tauri adapter,浏览器预览 fallback
│ ├── App.tsx # 首屏工作台
│ ├── main.tsx
│ └── styles.css # Tailwind v4 入口和最小全局样式
├── src-tauri
│ ├── capabilities # Tauri v2 权限
│ ├── icons # Bundle icon
│ ├── src # Rust commands
│ ├── Cargo.toml
│ └── tauri.conf.json
├── .env.example
├── README.md
├── package.json
├── tsconfig.json
└── vite.config.ts新手最容易犯的错是跨层导入——比如在 React 组件里直接调用 Tauri plugin,或者让桌面端 import Web app 里的模块。下面逐个目录说明边界。
各目录的职责
src/components 只放模板级 UI 组件。不放 API client 调用,不放 Tauri command 细节。组件通过 hooks 获取数据和系统能力。
src/config 集中校验 VITE_DESKTOP_* 环境变量。应用启动时跑一遍,变量缺了或格式错了立刻报错,不会等到运行时才发现。
src/hooks 组合 React state、TanStack Query 和 runtime adapter。比如用户偏好设置的 hook 会调用 runtime adapter 读写本地存储,同时暴露 React 状态给组件。
src/lib 放 Better Auth client、oRPC client 和 TanStack QueryClient 的初始化入口。
src/runtime TypeScript 侧对 Tauri 的唯一入口。每个 adapter 都封装了一个系统能力,并且提供浏览器 fallback——这样用普通 Vite dev server 也能调试 UI,不需要启动 Tauri。
src-tauri/src Rust 侧代码:自定义 commands、托盘逻辑、single instance 控制、原生文件读写。
src-tauri/capabilities Tauri v2 的权限配置。只开放当前需要的权限,不要图省事全部打开。
src-tauri/tauri.conf.json 应用 identity、窗口、CSP、bundle、签名和构建配置。详见配置桌面应用。
后端边界
桌面端不 import products/01mvp/apps/web/src/* 的任何模块。业务数据全部通过公开 HTTP 边界访问:
src/lib/api-client.ts → ${VITE_DESKTOP_SERVER_URL}/rpc (oRPC)
src/lib/auth-client.ts → ${VITE_DESKTOP_SERVER_URL}/auth (Better Auth)两个 client 都使用 credentials: "include",让 WebView 的 cookie 正常携带。
服务端仍然由 Web app 承载:
products/01mvp/apps/web/src/server/hono.ts # HTTP 入口
products/01mvp/packages/auth/src/index.ts # 认证
products/01mvp/packages/api/src/router.ts # 业务 API桌面端只通过 HTTP 请求和这些服务交互,不共享代码模块。这样两边可以独立部署、独立发版。
Runtime 边界
src/runtime/* 是前端代码接触 Tauri 的唯一通道。每个文件封装一个系统能力,同时提供浏览器 fallback:
// src/runtime/example.ts — 概念示意
export async function readPreference(key: string) {
if (window.__TAURI__) {
// 真实 Tauri 调用
return invoke('read_preference', { key })
}
// 浏览器 fallback:用 localStorage 代替
return localStorage.getItem(key)
}Rust 侧目前保留的通用能力:
- 读取和保存本地偏好
- 打开 app data 目录
- 托盘打开/退出
- single instance 控制
- process/opener plugin skeleton
Updater 的 TypeScript 入口已保留,但默认不注册 Rust updater plugin——配好发布端点、签名公钥和产物策略后才启用。截图、录音、全局快捷键、文件系统等能力应在复制模板后按需添加,不放进默认 scaffold。
添加新功能的顺序
要给桌面端加一个系统能力(比如录屏、全局快捷键),按这个顺序做:
- Rust 层 — 在
src-tauri/src/lib.rs新增 command 或注册 Tauri plugin。 - 权限层 — 在
src-tauri/capabilities/default.json只开放需要的权限。 - Adapter 层 — 在
src/runtime/<feature>.ts封装 TypeScript adapter。 - Fallback — 给 adapter 提供浏览器 fallback,至少返回清晰的 unavailable 状态。
- 消费层 — 在
src/hooks或组件里使用 adapter。 - 验证 — 确认浏览器 fallback 和参数处理正确。
这个顺序保证排查问题时,能清楚区分前端状态、API 请求、Tauri IPC 和 Rust 原生逻辑。
下一步
了解项目结构后,进入配置桌面应用设置应用身份、环境变量和窗口。
想和其他创造者交流?
这篇文档有问题?