最近两年桌面应用开发领域有个很明显的趋势:越来越多的开发者开始用前端技术栈来构建跨平台桌面应用。我去年接手一个监控系统项目时,就面临技术选型的难题。传统Electron方案虽然成熟,但打包体积大、内存占用高的问题一直让我很头疼。直到发现了Tauri这个新秀,用Rust做后端,搭配现代前端框架,简直是为性能敏感型应用量身定制的方案。
Tauri2.0相比1.x版本有几个杀手级改进:首先是启动速度提升了40%,这在我们做工具类软件时特别重要;其次是插件系统更完善,像系统通知、全局快捷键这些功能都能优雅实现。实测下来,同样功能的安装包,Tauri版本只有Electron的1/10大小,内存占用更是只有三分之一。
搭配Vue3的组合更是绝配。Vue3的Composition API写起来特别顺手,特别是用<script setup>语法时,代码简洁度直接上了一个档次。有次我需要快速实现一个实时日志展示界面,用Vue3的响应式系统配合Tauri的后台Rust线程,不到200行代码就搞定了双向通信和渲染优化。
先确保你的开发环境有Node.js 18+和Rust工具链。这里有个小技巧:建议用rustup安装Rust时选择nightly版本,因为Tauri某些新特性需要nightly支持。安装命令很简单:
bash复制npm create tauri-app@latest my-app --template vue-ts
这个命令会自动帮你搭建好Vue3+TypeScript+Tauri的基础结构。第一次编译可能会花点时间,因为要下载Rust依赖。我遇到过国内网络环境下载慢的问题,解决方案是在~/.cargo/config里添加国内镜像源:
toml复制[source.crates-io]
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
经过几个项目实践,我总结出一套比较合理的目录结构:
code复制src/
├─ assets/ # 静态资源
├─ components/ # 公共组件
├─ stores/ # Pinia状态管理
├─ views/ # 页面视图
├─ utils/ # 工具函数
├─ styles/ # 全局样式
└─ router/ # 路由配置
特别要注意的是src-tauri目录,这是Tauri的核心配置区。有次我忘记配置权限,调试窗口置顶功能花了两个小时。建议一开始就在tauri.conf.json里把常用权限加上:
json复制"permissions": [
"core:window:allow-set-always-on-top",
"core:window:allow-close",
"core:window:allow-maximize"
]
窗口置顶是很多工具软件的刚需功能。在Tauri中实现比Electron简单很多,主要是通过@tauri-apps/api的window模块。先看完整代码实现:
vue复制<script setup lang="ts">
import { Window } from '@tauri-apps/api/window'
import { ref } from 'vue'
const appWindow = new Window('main')
const isAlwaysOnTop = ref(false)
const toggleAlwaysOnTop = async () => {
await appWindow.setAlwaysOnTop(!isAlwaysOnTop.value)
isAlwaysOnTop.value = !isAlwaysOnTop.value
}
</script>
<template>
<button @click="toggleAlwaysOnTop">
{{ isAlwaysOnTop ? '取消置顶' : '窗口置顶' }}
</button>
</template>
这里有几个实战经验值得分享:
现代桌面应用基本都采用SPA架构。我们结合Vue Router和TDesign组件库搭建布局:
ts复制// router.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('@/views/layout/index.vue'),
children: [
{
path: '',
component: () => import('@/views/dashboard.vue')
}
]
}
]
})
布局组件有个坑要注意:Tauri的窗口拖动区域需要特殊标记。这是我优化过的布局代码:
vue复制<!-- layout/index.vue -->
<template>
<t-layout>
<t-header class="drag-region">
<Header />
</t-header>
<t-layout>
<t-aside width="200px">
<Menu />
</t-aside>
<t-content>
<router-view />
</t-content>
</t-layout>
</t-layout>
</template>
<style scoped>
.drag-region {
-webkit-app-region: drag;
}
</style>
Tauri默认打包已经比Electron小很多,但通过这几个技巧还能再瘦身:
tauri.conf.json中开启UPX压缩:json复制"build": {
"upx": true
}
移除未使用的API权限,每个权限都会增加包体积
使用tauri build --release生成生产环境包,比dev包小30%
混合开发容易产生内存泄漏,我常用的排查组合拳:
jemalloc作为全局分配器:rust复制#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
不同平台UI表现常有差异,推荐这些处理方式:
css复制.header {
padding-top: env(safe-area-inset-top);
}
rust复制// src-tauri/src/main.rs
tauri::Builder::default()
.setup(|app| {
let window = app.get_window("main").unwrap();
window.set_decorations(true)?;
Ok(())
})
系统托盘是桌面应用的标志性功能。Tauri实现起来非常简洁:
rust复制// src-tauri/src/main.rs
tauri::Builder::default()
.system_tray(SystemTray::new().with_menu(
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("show", "显示窗口"))
.add_item(CustomMenuItem::new("quit", "退出"))
))
前端侧监听菜单事件:
ts复制import { appWindow } from '@tauri-apps/api/window'
import { listen } from '@tauri-apps/api/event'
listen('tauri://menu', ({ payload }) => {
if (payload === 'quit') {
appWindow.close()
}
})
Tauri的更新机制比Electron简单很多。先在配置中启用更新:
json复制"updater": {
"active": true,
"endpoints": ["https://your-update-server.com/api/updates"]
}
然后在前端添加更新检查逻辑:
ts复制import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'
const update = await checkUpdate()
if (update.shouldUpdate) {
await installUpdate()
}
有次部署后页面白屏,排查发现是路由basePath配置问题。解决方案:
ts复制createWebHistory(import.meta.env.BASE_URL)
同时在tauri.conf.json中配置:
json复制"build": {
"devPath": "http://localhost:1420",
"distPath": "../dist"
}
首次加载时窗口会闪一下,这是WebView初始化的通病。我的优化方案:
rust复制.set_transparent(true)
rust复制window.show().unwrap();
Tauri默认安全策略很严格,需要正确配置CSP。建议这样设置:
json复制"security": {
"csp": "default-src 'self'; img-src https://*; style-src 'self' 'unsafe-inline'"
}
如果需要加载远程资源,要明确指定域名白名单。