性能、测试与跨平台发布
GPUI 的底层性能很强,但应用写法仍然会决定真实体验。最后一章把三个工程问题合在一起:如何避免卡顿,如何测试关键逻辑,如何把应用交付给用户。
性能原则
GPUI 的 render 会在需要绘制时运行。你应该让它尽量接近“纯展示”:
- 不做网络和文件 IO。
- 不解析大型 JSON。
- 不在列表渲染中做复杂排序和过滤。
- 不无意义 clone 大字符串。
- 不滥用顶层
cx.notify()。
错误示例:
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let pretty = serde_json::to_string_pretty(
&serde_json::from_str::<serde_json::Value>(&self.response_body).unwrap(),
).unwrap();
div().child(pretty)
}
正确方向是预计算或按需计算。
脏标记模式
HiPoster 用 dirty_response 避免每帧格式化 JSON:
pub struct ApiTab {
pub response: Option<HttpResponse>,
pub dirty_response: bool,
pub active_response_view: ResponseView,
}
收到响应:
self.response = Some(response);
self.dirty_response = true;
cx.notify();
切换 Pretty / Raw:
if self.active_response_view != view {
self.active_response_view = view;
self.dirty_response = true;
cx.notify();
}
渲染前检查:
if self.dirty_response {
self.update_response_display(window, cx);
}
这个模式可以推广到:
- URL 与 Params 双向同步。
- 过滤后的历史记录。
- 语法高亮输入内容。
- 大表格的排序结果。
- Markdown 或 HTML 渲染结果。
大列表
少量历史记录可以直接 .children(...)。如果列表可能增长到几千、几万行,应使用虚拟列表。Zed 主仓的 GPUI examples 中有 uniform_list、data_table 和 list_example,适合作为最新写法参考。
经验规则:
- 50 条以内:普通
children足够。 - 几百条:先确认渲染成本,必要时分页或过滤。
- 几千条以上:使用虚拟列表或只渲染可见区域。
单元测试
最稳定、收益最高的是测试纯逻辑:
HttpRequest序列化。HttpBody枚举行为。- URL 编码、解码、参数解析。
- JSON 格式化。
- 主题颜色转换。
- HTTP builder 的 Header、Auth、Body 构造。
示例:
#[test]
fn test_http_request_serialization() {
let request = HttpRequest {
method: HttpMethod::POST,
url: "https://example.com".to_string(),
body: HttpBody::Raw("{\"key\":\"value\"}".to_string()),
content_type: "application/json".to_string(),
..Default::default()
};
let serialized = serde_json::to_value(&request).unwrap();
assert_eq!(serialized["method"], "POST");
assert_eq!(serialized["url"], "https://example.com");
}
这类测试不依赖窗口,运行快,也最不脆弱。
GPUI 测试
Zed 主仓最新 examples 包含 testing.rs,展示了 #[gpui::test]、TestAppContext、VisualTestContext、Action、Focus 和异步任务测试。
实体测试:
#[gpui::test]
fn test_counter_state(cx: &mut TestAppContext) {
let counter = cx.new(|cx| Counter::new(cx));
counter.update(cx, |counter, _| {
counter.count = 42;
});
let count = counter.read_with(cx, |counter, _| counter.count);
assert_eq!(count, 42);
}
窗口测试:
#[gpui::test]
fn test_counter_in_window(cx: &mut TestAppContext) {
let window = cx.update(|cx| {
cx.open_window(Default::default(), |_, cx| cx.new(|cx| Counter::new(cx)))
.unwrap()
});
let mut cx = VisualTestContext::from_window(window.into(), cx);
let counter = window.root(&mut cx).unwrap();
let count = counter.read_with(&cx, |counter, _| counter.count);
assert_eq!(count, 0);
}
使用这些测试前,需要启用 GPUI 的 test-support feature,并以当前 Zed main 的 example 为准调整 API。
网络测试
不要让普通测试依赖公网。HiPoster 当前有一个 httpbin.org 冒烟测试,这对手动验证有帮助,但 CI 里容易受网络波动影响。
更稳的做法:
- 用本地 mock HTTP server。
- 把 HTTP builder 拆成可测试函数。
- 网络错误测试使用无效 URL 或 mock client。
- 公网测试加
#[ignore],手动运行。
构建脚本
HiPoster 已经为三个平台准备脚本:
scripts/
├── build_macos.sh
├── build_windows.ps1
└── build_linux.sh
macOS 脚本做了:
- 安装/检查
x86_64-apple-darwin和aarch64-apple-darwintarget。 - 分别构建 Intel 与 Apple Silicon。
- 用
lipo合并 Universal Binary。 - 创建
.app目录结构。 - 复制
Info.plist和.icns。 - ad-hoc signing。
- 打包
.dmg。
Windows 脚本做了:
- 检查
cl.exe。 - 设置 MSVC toolchain。
cargo build --release。- 复制
.exe到发布目录。
Linux 脚本做了:
- 根据架构选择 Rust target。
- 构建 release binary。
- 创建 Debian package 目录。
- 复制图标和
.desktop。 - 生成
control文件。 - 调用
dpkg-deb打包。
平台注意事项
macOS:
- 需要 Xcode 和命令行工具。
- 正式分发需要 Developer ID 签名与 notarization。
- Universal Binary 会显著增加包体积。
Windows:
- 必须使用 MSVC,不要用 GNU toolchain。
build.rs可注入图标并隐藏控制台。- 正式分发要考虑代码签名和安装器。
Linux:
- Wayland / X11 feature 要匹配目标环境。
.deb之外还可能需要 AppImage、Flatpak 或 tarball。- 系统图形库和字体依赖要在发行说明中写清楚。
升级检查清单
当你把 GPUI 升级到 Zed 最新 main 时,按这个顺序检查:
Cargo.toml中gpui、gpui_platform、gpui-component是否拉到兼容 revision。gpui_platformfeatures 是否符合目标平台。Application、open_window、WindowOptions是否与最新 example 一致。Context<T>、AsyncApp、cx.spawn写法是否变化。Menu、MenuItem、KeyBinding、Action 注册是否变化。- GPUI Component 的
Root、InputState、Button、TabBar、ThemeAPI 是否变化。 cargo test、cargo build --release和至少一个平台打包脚本是否通过。
GPUI 最大的魅力是把高性能 UI 和 Rust 类型系统放在一起;最大的挑战也在这里:它还年轻,升级时要像对待底层库一样认真。把边界拆清楚、测试补上、依赖锁住,应用就能稳稳向前。