What is Tauri
Tauri is a cross-platform GUI
framework, which is basically similar in idea to Electron
. The front-end implementation of Tauri is also based on the Web series language, and the back-end of Tauri uses Rust
. Tauri can create smaller, faster, and more secure cross-platform desktop applications.
Why choose Rust?
Rust
is a language that empowers everyone to build reliable and efficient software. It excels in high performance, reliability, and productivity. Rust is blazingly fast and highly memory-efficient, with no runtime and no garbage collection, it can handle particularly performance-hungry services, run on embedded devices, and easily integrate with other languages. Rust's rich type system and ownership model guarantees memory safety and thread safety, allowing you to eliminate all kinds of errors at compile time. Rust also has excellent documentation, a friendly compiler, and clear error messages, and integrates first-class tools - package managers and build tools...
Based on this, Rust is the best choice, and developers can easily use Rust to extend Tauri's default Api
to achieve custom functions.
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 |
Environment installation
macOS
Since the installation process is relatively simple, the author uses macOS. This article only introduces the installation steps of macOS. You can check the official website for the installation steps of Windows.
1. Make sure Xcode has installed
$ xcode-select --install
2. Node.js
It is recommended to use nvm
for node version management:
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
$ nvm install node --latest-npm
$ nvm use node
It is strongly recommended to install Yarn
as an alternative to npm.
3. Rust Environment
Install rustup
:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Verify that Rust
was successfully installed:
$ rustc --version
rustc 1.58.1 (db9d1b20b 2022-01-20)
tips: If rustc
command fails to execute, you can restart the terminal.
So far, the Tauri development environment has been installed.
Project construction
1. Create a Tauri project
$ yarn create tauri-app
Hit enter to continue...
It can be seen that the current mainstream Web framework Tauri supports it.
We choose create-vite
…
Select Y
here, install @tauri-apps/api
in,
Then select vue-ts
...
Check your Tauri related settings to make sure everything is in place...
$ 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.
So far, a new Tauri project has been created.
tips: Tauri also supports integration based on existing front-end projects. The specific process can be viewed on the official website. This article will not introduce it.
Project directory introduction
├── 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
run
Run the project:
$ cd tauri-demo1
$ yarn tauri dev
Waiting for the project to run...
It can be seen that a desktop application based on Vue 3 + TypeScript + Vite
is already running.
API call and function configuration
Tauri's Api has two types: JavaScript Api
and Rust Api
. This article mainly chooses some Rust Api
to explain (Rust-related knowledge can be learned by yourself). JavaScript-related Api is relatively simple and can be learned according to the official documents.
1. Splashscreen
Adding a splash screen is very necessary to initialize time-consuming applications and can improve the user experience.
The general principle is to first hide the main application view in the application initialization phase, display the splash screen view, and wait for the initialization to be completed to dynamically close the splash screen view to display the main view.
First, create a splashscreen.html
file in the project root directory as the splash screen view. The specific display content can be configured by yourself. The code is as follows:
<!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>
Next, change tauri.conf.json
configuration item:
"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"
+ }
]
Set the visible
property of the main view under the windows
configuration item to false
, so that the main view will be hidden during the initialization phase;
Create a new startup view under the windows
configuration item, and the view size can be customized.
The next step is to dynamically control the display and hide of the two views.
Open the src-tauri/main.rs
file and add the following Rust code:
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");
}
The execution logic of the above Rust code is to create a close_splashscreen
function to close the startup view and display the main view, and register this function as a Rust command, which is registered when the application is initialized, so that the command can be dynamically called in JavaScript.
Next, add the following code in src/App.vue
:
// 导入 invoke 方法
import { invoke } from '@tauri-apps/api/tauri'
// 添加监听函数,监听 DOM 内容加载完成事件
document.addEventListener('DOMContentLoaded', () => {
// DOM 内容加载完成之后,通过 invoke 调用 在 Rust 中已经注册的命令
invoke('close_splashscreen')
})
We can look at the source code of the invoke
method:
/**
* 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
method is used to communicate with the backend (Rust). The first parameter cmd
is the command defined in Rust, and the second parameter args
is optional extra information to match the first parameter. The method communicates internally through window.rpc.notify
, and the return value is a Promise.
At this point, the relevant logic for adding the startup view has been completed, and we can run it to see the effect.
Since our demo project is initialized very quickly, it is not easy to see the startup view, so we can delay the execution of invoke('close_splashscreen')
through setTimeout
to facilitate debugging and viewing:
It can be seen that after the project is running, the startup view is displayed first, then the startup view disappears, and the main view is displayed.
2. Window Menu (application menu)
Adding menus to an application is a fundamental feature, but also important.
Open the src-tauri/main.rs
file and add the following Rust code:
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");
}
First we introduce Menu
, Submenu
, MenuItem
, CustomMenuItem
.
View the source code of Menu
and 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
}
}
The Menu
structure is used to implement the application menu, and its built-in new
associated function is used to create menu
, add_item
method is used to add custom menu items, add_native_item
method is used to add Tauri natively implemented menu items, add_submenu
Entrance.
Submenu
This structure is used to create the entry for the menu item.
As shown in the figure:
Gear
and Customer
pointed to by the arrows are Submenu
, and the red box is Submenu
contained in MenuItem
.
We created a Submenu
Gear
added some MenuItem
entries that Tauri natively supports.
We also created a Submenu
Customer
and added two custom CustomMenuItem
items. The CustomMenuItem
event needs to be defined by the developer:
// 监听自定义菜单事件
on_menu_event(|event| match event.menu_item_id() {
"quit" => {
// 逻辑自定义
std::process::exit(0);
}
"close" => {
// 逻辑自定义
event.window().close().unwrap();
}
_ => {}
})
Monitor the trigger event of the custom menu item through the on_menu_event
method. The parameter it receives is a closure, and
match
is used to match the event id of the menu item, and add custom logic.
Notes
There is a compatibility problem with the MenuItem menu items that Tauri natively supports. You can see the source code:
/// 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,
...
}
It can be seen that these built-in menu items are not yet supported on Windows
, Android
, and iOS
platforms, but with the release of the stable version, I believe these compatibility issues should be well resolved.
debugging
In development mode, debugging is relatively easy. Let's see how to debug Rust
and JavaScript
codes respectively in development mode.
Rust Console
To debug Rust code, we can use the println!
macro to print debugging information:
let msg = String::from("Debug Infos.")
println!("Hello Tauri! {}", msg);
Debug information will be printed on the terminal:
WebView JS Console
For JavaScript code debugging, we can use the functions related to console
. Right-click in the application window and select Inspect Element
, the inspect element, to open the WebView console.
The console-related operations will not be repeated here.
tips: In some cases, we may also need to view the WebView console in the final package, so Tauri provides a simple command to create the debug package:
yarn tauri build --debug
Applications packaged with this command will be placed in the src-tauri/target/debug/bundle
directory.
App packaging
yarn tauri build
This command embeds web resources along with Rust code into a single binary. The binaries themselves will be located at src-tauri/target/release/[app name]
and the installer will be located at src-tauri/target/release/bundle/
.
Roadmap
It can be seen from Tauri's Roadmap
that the stable version will be released in 2022 Q1
, including subsequent support for Deno
and support for packaging to mobile devices. Therefore, the development of Tauri is still worth looking forward to.
Summarize
Tauri is smaller, faster and safer. Compared with Electron
, which is criticized for being too large and memory consumption, it is indeed a potential desktop application development framework Rust
With the help of God, this desktop application development framework is very attractive. However, since Tauri has not released a stable version so far, and some functions still have problems such as multi-platform compatibility, it cannot be widely used in the production environment. It is believed that with the development of Tauri, these problems will be solved, and a large share of the desktop application development market will be occupied by Tauri in the future. As developers, this is the best time to learn Tauri
and Rust
, let's act~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。