9.4 项目结构规范
> **本节目标**:建立可扩展的 RML 项目目录约定,让“代码该放哪里”有标准答案。
9.4.1 推荐目录结构
一个中等规模 RML 应用的推荐结构:
my-app/
├── Cargo.toml
├── build.rs # 集成 rml-compiler
├── src/
│ ├── main.rs # 应用入口
│ ├── app.rml # 根视图
│ ├── app.rml.rs # 根 ViewModel
│ │
│ ├── views/ # 页面级视图(每个对应一个路由 / 窗口)
│ │ ├── login/
│ │ │ ├── login.rml
│ │ │ ├── login.rml.rs
│ │ │ └── mod.rs
│ │ ├── dashboard/
│ │ │ ├── dashboard.rml
│ │ │ ├── dashboard.rml.rs
│ │ │ └── mod.rs
│ │ └── mod.rs
│ │
│ ├── components/ # 可复用组件(跨视图)
│ │ ├── button/
│ │ │ ├── button.rml
│ │ │ ├── button.rml.rs
│ │ │ └── mod.rs
│ │ ├── dialog/
│ │ │ ├── dialog.rml
│ │ │ ├── dialog.rml.rs
│ │ │ └── mod.rs
│ │ └── mod.rs
│ │
│ ├── models/ # 纯数据 Model(无 GPUI 依赖)
│ │ ├── user.rs
│ │ ├── todo.rs
│ │ └── mod.rs
│ │
│ ├── services/ # I/O 层:网络、文件、数据库
│ │ ├── auth.rs
│ │ ├── todo_service.rs
│ │ └── mod.rs
│ │
│ ├── styles/ # 全局样式与主题
│ │ ├── theme.rmlcss # 主题变量
│ │ ├── reset.rmlcss # 重置样式
│ │ └── mod.rs
│ │
│ ├── context/ # 全局状态 / Context
│ │ ├── auth_context.rs
│ │ ├── theme_context.rs
│ │ └── mod.rs
│ │
│ └── utils/ # 工具函数
│ ├── format.rs
│ └── mod.rs
│
└── tests/ # 集成测试
├── login_flow.rs
└── todo_crud.rs
9.4.2 分层约定
views/ —— 页面级视图
- 每个视图一个独立目录,包含 `.rml` + `.rml.rs` + `mod.rs` - 视图对应“一个路由 / 一个窗口 / 一个独立功能页” - 视图之间通过 Context 事件或路由跳转通信,**不直接互调**
// views/login/mod.rs
mod login;
pub use login::LoginViewModel;
components/ —— 可复用组件
- 跨视图复用的 UI 单元 - 每个 component 自带 `.rml` + `.rml.rs` - 组件应当**无业务知识**:只接受 props、触发事件,不直接调用 Service
// components/button/mod.rs
mod button;
pub use button::Button;
models/ —— 纯数据
- `#[derive(Model, Clone, Debug)]` 的结构体 - 可包含 `impl` 中的纯函数式方法 - **不依赖** `gpui::*`,可独立编译为 crate
services/ —— I/O 层
- 网络、文件、数据库、第三方 SDK 调用 - 通常以 `async fn` 形式暴露 - 返回 `Result<T>`,错误由 ViewModel 处理
// services/auth.rs
pub async fn login(email: &str, password: &str, cx: &mut AsyncApp) -> Result<Token> { ... }
styles/ —— 全局样式
- 主题变量、重置样式、全局工具类 - 组件级样式放在组件目录内(`components/button/button.rmlcss`)
context/ —— 全局状态
- 跨视图共享的状态:登录态、主题、用户配置 - 通过 `cx.set_global` / `cx.global` 访问
9.4.3 命名约定
| 类型 | 命名规则 | 示例 | | ----------- | ----------------------------- | --------------------------- | | 视图文件 | 与视图名同源,全小写下划线 | `user_profile.rml` | | ViewModel | 大驼峰 + `ViewModel` 后缀 | `UserProfileViewModel` | | 组件 | 大驼峰,无后缀 | `Button`、`Dialog` | | 命令方法 | 动词开头,小驼峰 | `on_submit`、`load_data` | | 计算属性 | 名词或 is/has 开头 | `full_name`、`is_loading` | | Model | 大驼峰,无后缀 | `User`、`TodoItem` | | Service 模块 | 名词 + `_service` 或领域名 | `auth_service.rs` |
9.4.4 模块边界规则
依赖方向
views ──▶ components ──▶ models
│ │
▼ ▼
services ◀─────
│
▼
context / utils
- `views` 可依赖 `components`、`services`、`models`、`context` - `components` 可依赖 `models`、`context`,**不依赖** `views` 或 `services` - `models` 不依赖任何上层 - `services` 可依赖 `models`、`context`,**不依赖** `views` 或 `components`
违反依赖方向 = 循环依赖 = 编译失败或测试困难。
禁止的依赖
- ❌ `components` 调用 `services`:组件应当无业务知识 - ❌ `models` 依赖 `gpui`:Model 必须可独立编译 - ❌ `views` 之间互相 `use`:通过 Context 事件通信
9.4.5 大型项目的拆分
当单 crate 膨胀到编译缓慢时,按下列维度拆 crate:
my-app/
├── Cargo.toml # workspace 根
├── crates/
│ ├── app/ # 主应用 crate
│ ├── ui-kit/ # 组件库 crate(可独立发布)
│ ├── domain-models/ # Model crate(无 GPUI 依赖)
│ ├── services/ # Service crate
│ └── theme/ # 主题 crate
拆分原则:
1. **ui-kit** 可独立发布,被多个项目复用 2. **domain-models** 无 GPUI 依赖,可被 CLI / 服务端复用 3. **services** 依赖 `domain-models`,可被多端复用 4. **app** 组装一切,是唯一可执行 crate
9.4.6 资源文件组织
非代码资源(图标、字体、本地化字符串)放在 `assets/`:
assets/
├── icons/
│ ├── logo.svg
│ └── ...
├── fonts/
│ └── inter.woff2
└── i18n/
├── zh-CN.json
└── en-US.json
通过 `cx.asset("icons/logo.svg")` 引用,编译期校验路径存在。
在 `on_launch` 中初始化 i18n(命令式或声明式入口均支持):
impl IAppLifecycle for AppBootstrap {
fn on_launch(&mut self, cx: &mut App) {
cx.use_i18n("zh-CN");
// 命令式:先打开登录/欢迎窗,成功后再 open 主窗口
// LoginWindow::default().open(cx);
}
}
声明式单主窗口:`RmlApplication::new().lifecycle::<AppBootstrap>().main_window::<MainWindow>().run()` —— `on_launch` 结束后自动打开主窗口。
9.4.7 测试组织
tests/ # 集成测试(跨模块)
├── login_flow.rs
└── todo_crud.rs
src/views/login/login.rml.rs # 单元测试在模块内 #[cfg(test)]
- **单元测试**:放在模块内 `#[cfg(test)] mod tests`,测试 ViewModel 纯逻辑 - **集成测试**:放在 `tests/`,测试跨模块流程 - **快照测试**:组件 `.rml` 渲染结果快照,放在 `tests/snapshots/`
9.4.8 项目结构检查清单
- [ ] 每个视图有独立目录,含 `.rml` + `.rml.rs` - [ ] 组件目录无业务逻辑代码 - [ ] `models/` 中无 `gpui::*` import - [ ] `services/` 中无 `cx.notify()` 调用 - [ ] 视图之间无直接 `use`,通过 Context 通信 - [ ] 全局样式在 `styles/`,组件样式在组件目录 - [ ] 命名遵循约定,无 `ViewModel2`、`temp` 等模糊命名 - [ ] 资源文件在 `assets/`,不散落在源码目录
下一节 → [9.5 可测试性设计](./testability.md)