扔掉 Electron,擁抱基於 Rust 開發的 Tauri
Tauri 是什麼
Tauri 是一個跨平臺 GUI
框架,與 Electron
的思想基本類似。Tauri 的前端實現也是基於 Web 系列語言,Tauri 的後端使用 Rust
。Tauri 可以創建體積更小、運行更快、更加安全的跨平臺桌面應用。
爲什麼選擇 Rust?
Rust
是一門賦予每個人構建可靠且高效軟件能力的語言。它在高性能、可靠性、生產力方面表現尤爲出色。Rust 速度驚人且內存利用率極高,由於沒有運行時和垃圾回收,它能夠勝任對性能要求特別高的服務,可以在嵌入式設備上運行,還能輕鬆和其他語言集成。Rust 豐富的類型系統和所有權模型保證了內存安全和線程安全,讓您在編譯期就能夠消除各種各樣的錯誤。Rust 也擁有出色的文檔、友好的編譯器和清晰的錯誤提示信息,還集成了一流的工具——包管理器和構建工具……
基於此,讓 Rust 成爲不二之選,開發人員可以很容易的使用 Rust 擴展 Tauri 默認的 Api
以實現定製化功能。
Tauri VS Electron
環境安裝
macOS
由於安裝過程比較簡單,作者使用的是 macOS,本文只介紹 macOS 安裝步驟, Windows 安裝步驟可自行查看官網。
1. 確保 Xcode 已經安裝
{{content}}nbsp;xcode-select --install
2. Node.js
建議使用 nvm
進行 node 版本管理:
{{content}}nbsp;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
{{content}}nbsp;nvm install node --latest-npm
{{content}}nbsp;nvm use node
強烈推薦安裝 Yarn
,用來替代 npm。
3.Rust 環境
安裝 rustup
:
{{content}}nbsp;curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
驗證 Rust
是否安裝成功:
{{content}}nbsp;rustc --version
rustc 1.58.1 (db9d1b20b 2022-01-20)
tips:如果 rustc
命令執行失敗,可以重啓一下終端。
至此,Tauri 開發環境已安裝完畢。
項目搭建
1. 創建一個 Tauri 項目
{{content}}nbsp;yarn create tauri-app
創建 Tauri 項目
按一下回車鍵,繼續……
Web 框架選擇
可以看出,目前主流的 Web 框架 Tauri 都支持, 我們選擇 create-vite
……
Web 框架選擇
此處選擇 Y
,將 @tauri-apps/api
安裝進來, 然後選擇 vue-ts
……
項目創建完成
檢查 Tauri 相關的設置,確保一切就緒……
{{content}}nbsp;yarn tauri info
yarn run v1.22.17
{{content}}nbsp;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
運行
運行項目:
{{content}}nbsp;cd tauri-demo1
{{content}}nbsp;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 >
<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);
調試信息會在終端打印出來:
Rust 調試信息
WebView JS Console
JavaScript 代碼的調試,我們可使用 console
相關的函數來進行。在應用窗口右鍵單擊,選擇 Inspect Element
即 審查元素,就可以打開 WebView 控制檯。
JavaScript 調試
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
roadmap
從 Tauri 的 Roadmap
可以看出,穩定版會在 2022 Q1
發佈,包括後續對 Deno
的支持,以及打包到移動設備的支持。因此 Tauri 的發展還是很值得期待的。
總結
Tauri 主打的 更小、更快、更安全,相較於 Electron
讓人詬病的包太大、內存消耗過大等問題來看,的確是一個很有潛力的桌面端應用開發框架,同時在 Rust
的加持下如有神助,讓這款桌面端應用開發框架極具魅力。不過由於 Tauri 到目前爲止還沒發佈穩定版,以及一些功能還存在多平臺兼容性等問題,致使目前還不能在生產環境進行大面積應用。相信隨着 Tauri 的發展,這些問題都會得到解決,以後的桌面端應用開發市場中也會有很大一部分份額會被 Tauri 所佔有。作爲開發者的我們,此刻正是學習 Tauri
以及 Rust
的最佳時機,行動起來吧~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/qKGJrg6155oYdPYyKQiToA