3
头图

公众号名片
作者名片

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

DetailTauriElectron
Installer Size Linux3.1 MB52.1 MB
Memory Consumption Linux180 MB462 MB
Launch Time Linux0.39s0.80s
Interface Service ProviderWRYChromium
Backend BindingRustNode.js (ECMAScript)
Underlying EngineRustV8 (C/C++)
FLOSSYesNo
MultithreadingYesYes
Bytecode DeliveryYesNo
Multiple WindowsYesYes
Auto UpdaterYesYes
Custom App IconYesYes
Windows BinaryYesYes
MacOS BinaryYesYes
Linux BinaryYesYes
iOS BinarySoonNo
Android BinarySoonNo
Desktop TrayYesYes
Sidecar BinariesYesNo

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

创建 Tauri 项目

Hit enter to continue...

Web 框架选择

It can be seen that the current mainstream Web framework Tauri supports it.
We choose create-vite

Web 框架选择

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:

Rust 调试信息

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.

JavaScript 调试

WebView 控制台

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

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~

more exciting news, please pay attention to our public account "Hundred Bottles Technology", there are irregular benefits!


百瓶技术
127 声望18 粉丝

「百瓶」App 技术团队官方账号。