summary: 运用 Tauri + Rust 开发桌面端运用
tags: tauri、rust、运用、桌面端、PC
author: 大熊
Tauri 是什么
Tauri 是一个跨渠道 GUI
结构,与 Electron
的思维根本类似。Tauri 的前端完结也是根据 Web 系列语言,Tauri 的后端运用 Rust
。Tauri 能够创立体积更小、运转更快、更加安全的跨渠道桌面运用。
为什么挑选 Rust?
Rust
是一门赋予每个人构建牢靠且高效软件能力的语言。它在高性能、牢靠性、出产力方面体现尤为超卓。Rust 速度惊人且内存利用率极高,因为没有运转时和废物收回,它能够担任对性能要求特别高的服务,能够在嵌入式设备上运转,还能轻松和其他语言集成。Rust 丰厚的类型体系和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的过错。Rust 也拥有超卓的文档、友好的编译器和清晰的过错提示信息,还集成了一流的工具——包办理器和构建工具……
根据此,让 Rust 成为不贰之选,开发人员能够很容易的运用 Rust 扩展 Tauri 默许的 Api
以完结定制化功用。
Tauri VS Electron
Detail | Tauri | Electron |
---|---|---|
Installer Size Linux | 3.1 MB | 52.1 MB |
Memory Consumption Linux | 180 MB | 462 MB |
Launch Time Linux | 0.39s | 0.80s |
Interface Service Provider | WRY | Chromium |
Backend Binding | Rust | Node.js (ECMAScript) |
Underlying Engine | Rust | V8 (C/C++) |
FLOSS | Yes | No |
Multithreading | Yes | Yes |
Bytecode Delivery | Yes | No |
Multiple Windows | Yes | Yes |
Auto Updater | Yes | Yes |
Custom App Icon | Yes | Yes |
Windows Binary | Yes | Yes |
MacOS Binary | Yes | Yes |
Linux Binary | Yes | Yes |
iOS Binary | Soon | No |
Android Binary | Soon | No |
Desktop Tray | Yes | Yes |
Sidecar Binaries | Yes | No |
环境装置
macOS
因为装置进程比较简略,作者运用的是 macOS,本文只介绍 macOS 装置过程, Windows 装置过程可自行检查官网。
1. 保证 Xcode 现已装置
$ xcode-select --install
2. Node.js
建议运用 nvm
进行 node 版别办理:
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
$ nvm install node --latest-npm
$ nvm use node
强烈推荐装置 Yarn
,用来替代 npm。
3.Rust 环境
装置 rustup
:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
验证 Rust
是否装置成功:
$ rustc --version
rustc 1.58.1 (db9d1b20b 2022-01-20)
tips:假如 rustc
指令履行失败,能够重启一下终端。
至此,Tauri 开发环境已装置完毕。
项目建立
1.创立一个 Tauri 项目
$ yarn create tauri-app
按一下回车键,持续……
能够看出,现在干流的 Web 结构 Tauri 都支撑,
咱们挑选 create-vite
……
此处挑选 Y
,将 @tauri-apps/api
装置进来,
然后挑选 vue-ts
……
检查 Tauri 相关的设置,保证一切安排妥当……
$ yarn tauri info
yarn run v1.22.17
$ tauri info
Operating System - Mac OS, version 12.2.0 X64
Node.js environment
Node.js - 14.17.0
@tauri-apps/cli - 1.0.0-rc.2
@tauri-apps/api - 1.0.0-rc.0
Global packages
npm - 6.14.13
pnpm - Not installed
yarn - 1.22.17
Rust environment
rustc - 1.58.1
cargo - 1.58.0
Rust environment
rustup - 1.24.3
rustc - 1.58.1
cargo - 1.58.0
toolchain - stable-x86_64-apple-darwin
App directory structure
/dist
/node_modules
/public
/src-tauri
/.vscode
/src
App
tauri.rs - 1.0.0-rc.1
build-type - bundle
CSP - default-src 'self'
distDir - ../dist
devPath - http://localhost:3000/
framework - Vue.js
✨ Done in 20.72s.
至此,一个新的 Tauri 项目已创立完结。
tips:Tauri 也支撑根据已存在的前端项目进行集成,详细流程可检查官网,本文不做介绍。
项目目录介绍
├── README.md
├── dist - web 项目打包编译目录
│ ├── assets
│ ├── favicon.ico
│ └── index.html
├── index.html
├── node_modules
├── package.json
├── public
│ └── favicon.ico
├── src - vue 项目目录(页面开发)
│ ├── App.vue
│ ├── assets
│ ├── components
│ ├── env.d.ts
│ └── main.ts
├── src-tauri - rust 相关目录(tauri-api 相关装备)
│ ├── Cargo.lock
│ ├── Cargo.toml - rust 装备文件
│ ├── build.rs
│ ├── icons - 运用相关的 icons
│ ├── src - rust 入口
│ ├── target - rust 编译目录
│ └── tauri.conf.json - tauri 相关装备文件
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
运转
运转项目:
$ cd tauri-demo1
$ yarn tauri dev
等候项目 run 起来……
能够看到,一个根据 Vue 3 + TypeScript + Vite
的桌面端运用现已运转起来了。
API 调用及功用装备
Tauri 的 Api 有 JavaScript Api
和 Rust Api
两种 ,本文主要挑选一些 Rust Api
来进行解说(Rust 相关知识可自行学习),JavaScript 相关的 Api 相对简略一些,可按照官方文档进行学习。
1.Splashscreen(发动画面)
增加发动画面关于初始化耗时的运用来说对错常有必要的,能够提高用户体验。
大致原理是在运用初始化阶段先躲藏主运用视图,展现发动画面视图,等候初始化完结今后动态封闭发动画面视图,展现主视图。
首要在项目根目录创立一个 splashscreen.html
文件作为发动画面视图,详细展现内容可自行装备,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
</head>
<body style="background-color: aquamarine;">
<h1>Loading...</h1>
</body>
</html>
其次更改 tauri.conf.json
装备项:
"windows": [
{
"title": "Tauri App",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false,
+ "visible": false // 默许躲藏主视图
},
// 增加发动视图
+ {
+ "width": 400,
+ "height": 200,
+ "decorations": false,
+ "url": "splashscreen.html",
+ "label": "splashscreen"
+ }
]
将 windows
装备项下的主视图 visible
特点设置为 false
,这样初始化阶段,主视图就会躲藏;
在 windows
装备项下新建一个发动视图,视图大小能够自定义装备。
接下来便是动态控制两个视图的显现和躲藏了。
翻开 src-tauri/main.rs
文件,增加以下 Rust 代码:
use tauri::Manager;
// 创立一个 Rust 指令
#[tauri::command]
fn close_splashscreen(window: tauri::Window) {
// 封闭发动视图
if let Some(splashscreen) = window.get_window("splashscreen") {
splashscreen.close().unwrap();
}
// 展现主视图
window.get_window("main").unwrap().show().unwrap();
}
fn main() {
tauri::Builder::default()
// 注册指令
.invoke_handler(tauri::generate_handler![close_splashscreen])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
以上 Rust 代码的履行逻辑是创立一个 close_splashscreen
函数用来封闭发动视图并展现主视图,并将这个函数注册为一个 Rust 指令,在运用初始化时进行注册,以便在 JavaScript 中能够动态调用该指令。
接下来,在 src/App.vue
中增加以下代码:
// 导入 invoke 办法
import { invoke } from '@tauri-apps/api/tauri'
// 增加监听函数,监听 DOM 内容加载完结事情
document.addEventListener('DOMContentLoaded', () => {
// DOM 内容加载完结之后,通过 invoke 调用 在 Rust 中现已注册的指令
invoke('close_splashscreen')
})
咱们能够看一下 invoke
办法的源码:
/**
* Sends a message to the backend.
*
* @param cmd The command name.
* @param args The optional arguments to pass to the command.
* @return A promise resolving or rejecting to the backend response.
*/
async function invoke<T>(cmd: string, args: InvokeArgs = {}): Promise<T> {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e)
Reflect.deleteProperty(window, error)
}, true)
const error = transformCallback((e) => {
reject(e)
Reflect.deleteProperty(window, callback)
}, true)
window.rpc.notify(cmd, {
__invokeKey: __TAURI_INVOKE_KEY__,
callback,
error,
...args
})
})
}
invoke
办法是用来和后端(Rust)进行通讯,第一个参数 cmd
便是在 Rust 中定义的指令,第二个参数 args
是可选的配合第一个参数的额定信息。办法内部通过 window.rpc.notify
来进行通讯,返回值是一个 Promise。
至此,增加发动视图的相关逻辑已悉数完结,咱们能够运转检查一下作用。
因为咱们的 demo 项目初始化很快,不容易看到发动视图,因而可通过 setTimeout
推迟 invoke('close_splashscreen')
的履行,方便调试检查:
能够看到,在项目运转起来之后,首要展现的是发动视图,其次发动视图消失,主视图展现出来。
2.Window Menu(运用菜单)
为运用增加菜单是很基础的功用,一起也很重要。
翻开 src-tauri/main.rs
文件,增加以下 Rust 代码:
use tauri::{ Menu, Submenu, MenuItem, CustomMenuItem };
fn main() {
let submenu_gear = Submenu::new(
"Gear",
Menu::new()
.add_native_item(MenuItem::Copy)
.add_native_item(MenuItem::Paste)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Zoom)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::CloseWindow)
.add_native_item(MenuItem::Quit),
);
let close = CustomMenuItem::new("close".to_string(), "Close");
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
let submenu_customer = Submenu::new(
"Customer",
Menu::new()
.add_item(close)
.add_item(quit)
);
let menus = Menu::new()
.add_submenu(submenu_gear)
.add_submenu(submenu_customer);
tauri::Builder::default()
// 增加菜单
.menu(menus)
// 监听自定义菜单事情
.on_menu_event(|event| match event.menu_item_id() {
"quit" => {
std::process::exit(0);
}
"close" => {
event.window().close().unwrap();
}
_ => {}
})
// 注册指令
.invoke_handler(tauri::generate_handler![close_splashscreen])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
首要咱们引入 Menu
、Submenu
、MenuItem
、CustomMenuItem
。
检查 Menu
以及 Submenu
源码:
/// A window menu.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Menu {
pub items: Vec<MenuEntry>,
}
impl Default for Menu {
fn default() -> Self {
Self { items: Vec::new() }
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Submenu {
pub title: String,
pub enabled: bool,
pub inner: Menu,
}
impl Submenu {
/// Creates a new submenu with the given title and menu items.
pub fn new<S: Into<String>>(title: S, menu: Menu) -> Self {
Self {
title: title.into(),
enabled: true,
inner: menu,
}
}
}
impl Menu {
/// Creates a new window menu.
pub fn new() -> Self {
Default::default()
}
/// Adds the custom menu item to the menu.
pub fn add_item(mut self, item: CustomMenuItem) -> Self {
self.items.push(MenuEntry::CustomItem(item));
self
}
/// Adds a native item to the menu.
pub fn add_native_item(mut self, item: MenuItem) -> Self {
self.items.push(MenuEntry::NativeItem(item));
self
}
/// Adds an entry with submenu.
pub fn add_submenu(mut self, submenu: Submenu) -> Self {
self.items.push(MenuEntry::Submenu(submenu));
self
}
}
Menu
这个结构体便是用来完结运用菜单的,它内置的 new
关联函数用来创立 menu
,add_item
办法用来增加自定义菜单项,add_native_item
办法用来增加 Tauri 原生完结的菜单项,add_submenu
用来增加菜单入口。
Submenu
这个结构体用来创立菜单项的入口。
如图:
箭头所指的 Gear
和 Customer
便是 Submenu
,红框里是 Submenu
下所包含的 MenuItem
项。
咱们创立了一个命名为 Gear
的 Submenu
,并增加了一些 Tauri 原生支撑的 MenuItem
项进去。
咱们也创立了一个命名为 Customer
的 Submenu
,并增加了两个自定义的 CustomMenuItem
项,CustomMenuItem
的事情需求开发者自己定义:
// 监听自定义菜单事情
on_menu_event(|event| match event.menu_item_id() {
"quit" => {
// 逻辑自定义
std::process::exit(0);
}
"close" => {
// 逻辑自定义
event.window().close().unwrap();
}
_ => {}
})
通过 on_menu_event
办法监听自定义菜单项的触发事情,它接收的参数是一个 闭包
,用 match
对菜单项的 事情 id
进行匹配,并增加自定义逻辑。
注意事项
Tauri 原生支撑的 MenuItem 菜单项存在兼容性问题,能够看源码:
/// A menu item, bound to a pre-defined action or `Custom` emit an event. Note that status bar only
/// supports `Custom` menu item variants. And on the menu bar, some platforms might not support some
/// of the variants. Unsupported variant will be no-op on such platform.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum MenuItem {
/// A menu item for enabling cutting (often text) from responders.
///
/// ## Platform-specific
///
/// - **Windows / Android / iOS:** Unsupported
///
Cut,
/// A menu item for pasting (often text) into responders.
///
/// ## Platform-specific
///
/// - **Windows / Android / iOS:** Unsupported
///
Paste,
/// Represents a Separator
///
/// ## Platform-specific
///
/// - **Windows / Android / iOS:** Unsupported
///
Separator,
...
}
能够看出内置的这些菜单项在 Windows
、Android
、iOS
渠道都还不支撑,但是跟着稳定版的发布,信任这些兼容性问题应该能得到很好的处理。
调试
在开发形式下,调试相对容易。以下来看在开发形式下怎么别离调试 Rust
和 JavaScript
代码。
Rust Console
调试 Rust 代码,咱们能够运用 println!
宏,来进行调试信息打印:
let msg = String::from("Debug Infos.")
println!("Hello Tauri! {}", msg);
调试信息会在终端打印出来:
WebView JS Console
JavaScript 代码的调试,咱们可运用 console
相关的函数来进行。在运用窗口右键单击,挑选 Inspect Element
即 检查元素,就能够翻开 WebView 控制台。
控制台相关的操作就不再赘述了。
tips:在一些情况下,咱们或许也需求在最终包检查 WebView 控制台,因而 Tauri 提供了一个简略的指令用来创立 调试包
:
yarn tauri build --debug
通过该指令打包的运用程序将放置在 src-tauri/target/debug/bundle
目录下。
运用打包
yarn tauri build
该指令会将 Web 资源 与 Rust 代码一起嵌入到单个二进制文件中。二进制文件自身将坐落 src-tauri/target/release/[app name]
,装置程序将坐落 src-tauri/target/release/bundle/
。
Roadmap
从 Tauri 的 Roadmap
能够看出,稳定版会在 2022 Q1
发布,包含后续对 Deno
的支撑,以及打包到移动设备的支撑。因而 Tauri 的开展还是很值得等待的。
总结
Tauri 主打的 更小、更快、更安全,相较于 Electron
让人诟病的包太大、内存耗费过大等问题来看,的确是一个很有潜力的桌面端运用开发结构,一起在 Rust
的加持下如有神助,让这款桌面端运用开发结构极具魅力。不过因为 Tauri 到现在为止还没发布稳定版,以及一些功用还存在多渠道兼容性等问题,致使现在还不能在出产环境进行大面积运用。信任跟着 Tauri 的开展,这些问题都会得到处理,今后的桌面端运用开发商场中也会有很大一部分比例会被 Tauri 所占有。作为开发者的咱们,此刻正是学习 Tauri
以及 Rust
的最佳时机,行动起来吧~