What is Tauri

Tauri is a cross-platform GUI framework with a similar idea to Electron. The front-end implementation of Tauri is also based on the Web family of languages, and the backend of Tauri uses Rust. Tauri allows you to create smaller, faster, and more secure cross-platform desktop applications.

Why Rust?

Rust is a language that empowers everyone to build reliable and efficient software. It performs particularly well in terms of high performance, reliability and productivity. Rust is fast and memory efficient, and with no runtime or garbage collection, it can handle particularly performance demanding services, run on embedded devices, and easily integrate with other languages. Rust’s rich type system and ownership model ensures memory safety and thread-safety, allowing you to eliminate a wide variety of errors at compile time. Rust also has great documentation, a friendly compiler, and clear error messages, as well as integration with top-notch tools — package manager and build tools…

With this in mind, Rust is a natural choice for developers who can easily use Rust to extend Tauri’s default Api for customization.

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.39 s 0.80 s
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

Environmental installation

macOS

Because 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. Ensure that Xcode has been installed

$ xcode-select --install
Copy the code

2. Node.js

NVM is recommended for Node version management:

$curl - o - https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bashCopy the code
$ nvm install node --latest-npm
$ nvm use node
Copy the code

Yarn is strongly recommended to replace NPM.

3. The Rust environment

Install rustup:

$ curl --proto '=https'- tlsv1.2 - sSf https://sh.rustup.rs | shCopy the code

Verify that Rust is successfully installed:

$ rustc --version

rustc 1.58.1 (db9d1b20b 2022-01-20)
Copy the code

Tips: ifrustcIf the command fails to execute, restart the terminal.

At this point, the Tauri development environment is installed.

Project structures,

1. Create a Tauri project

$ yarn create tauri-app
Copy the code

Press enter to continue…

As you can see, the mainstream Web framework Tauri supports, so we chose create-vite…

Select Y here, install @tauri-apps/ API, then select VUe-ts…

Check tauri-related Settings to make sure everything is in place…

$ yarn tauri info
Copy the code
Yarn Run v1.22.17 $Tauri info Operating System - Mac OS, Version 12.2.0x64 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-stables - 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 ✨ Donein20.72 s.Copy the code

At this point, a new Tauri project is created.

Tips: Tauri also supports integration based on existing front-end projects. The detailed process can be found on the official website and not covered in this article.

Project Catalog Introduction

├ ─ ─ the README. Md ├ ─ ─ dist - web project package directory compiled │ ├ ─ ─ assets │ ├ ─ ─ the favicon. Ico │ └ ─ ─ index. The HTML ├ ─ ─ index. The HTML ├ ─ ─ node_modules ├ ─ ─ package. Json ├ ─ ─ public │ └ ─ ─ the favicon. Ico ├ ─ ─ the SRC - vue project directory page (development) │ ├ ─ ─ App. Vue │ ├ ─ ─ assets │ ├ ─ ─ components │ ├ ─ ─ Env. Which s │ └ ─ ─ main. Ts ├ ─ ─ the SRC directory - tauri - rust related (tauri - API related configuration) │ ├ ─ ─ Cargo. Lock │ ├ ─ ─ Cargo. Toml - rust configuration file │ ├ ─ ─ Build. Rs │ ├── ICONS │ ├─ SRC - Rust Import │ ├─ target-rust ├── Taur.conf Tsconfig. Json ├ ─ ─ tsconfig. Node. Json ├ ─ ─ vite. Config. Ts └ ─ ─ yarn. The lockCopy the code

run

Operating Projects:

$ cd tauri-demo1
$ yarn tauri dev
Copy the code

Waiting for the project to run…

As you can see, a desktop application based on Vue 3 + TypeScript + Vite is already running.

API call and function configuration

Tauri has two kinds of apis: JavaScript Api and Rust Api. This article mainly selects some Rust apis to explain (Rust related knowledge can be learned by yourself). JavaScript related apis are relatively simple. You can learn according to the official documents.

1.Splashscreen

Adding a splash screen is necessary for an application that takes time to initialize and can improve the user experience.

The general principle is to hide the main application view and display the startup screen view during the application initialization phase. After the initialization is complete, the startup screen view is dynamically closed and the main view is displayed.

First of all, create a splashscreen. HTML file in the root directory of the project as a splashscreen view, the specific display content can be customized, the code is as follows:

<! DOCTYPEhtml>
<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>
Copy the code

Next change the taur.conf. json configuration item:

"windows": [{"title": "Tauri App"."width": 800."height": 600."resizable": true."fullscreen": false,
+   "visible": false // Hide the main view by default
  },
  // Add a startup view+ {+"width": 400,
+   "height": 200,
+   "decorations": false,
+   "url": "splashscreen.html",
+   "label": "splashscreen"+}]Copy the code

Set the visible property of the main view under Windows configuration to false so that the main view is hidden during initialization.

Create a startup view under Windows configuration. The size of the view can be customized.

The next step is to dynamically control the display and hiding of the two views.

Open src-tauri/main.rs and add the following Rust code:

use tauri::Manager;

// Create a Rust command
#[tauri::command]
fn close_splashscreen(window: tauri::Window) {
  // Close the startup view
  if let Some(splashscreen) = window.get_window("splashscreen") {
    splashscreen.close().unwrap();
  }
  // Display the main view
  window.get_window("main").unwrap().show().unwrap();
}

fn main() {
  tauri::Builder::default()
    // Register the command.invoke_handler(tauri::generate_handler! [close_splashscreen]) .run(tauri::generate_context! ()) .expect("error while running tauri application");
}
Copy the code

The execution logic of the Rust code above is to create a close_splashscreen function to close the startup view and display the main view, and to register this function as a Rust command at application initialization so that this command can be invoked dynamically in JavaScript.

Next, add the following code to SRC/app.vue:

// Import the invoke method
import { invoke } from '@tauri-apps/api/tauri'

// Add a listener to listen for DOM content completion events
document.addEventListener('DOMContentLoaded'.() = > {
  // Once the DOM content is loaded, invoke the commands registered in Rust
  invoke('close_splashscreen')})Copy the code

We can look at the source code for 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 }) }) }Copy the code

The Invoke method is used to communicate with the back end (Rust). The first argument CMD is the command defined in Rust, and the second argument args is optional with additional information from the first argument. The method communicates internally with window.rpc.notify and returns a Promise.

Now that the logic for adding a launch view is complete, we can run it to see the effect.

Since our demo project is quick to initialize and not easy to see the launch view, we can delay the execution of invoke(‘close_splashscreen’) with setTimeout for debug viewing:

As you can see, after the project is up and running, the launch view is displayed first, then the launch view disappears and the main view is displayed.

2.Window Menu

Adding menus to your app is a basic feature, but it’s also important.

Open src-tauri/main.rs 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()
    // Add menu
    .menu(menus)
    // Listen for custom menu events
    .on_menu_event(|event| match event.menu_item_id() {
      "quit" => {
        std::process::exit(0);
      }
      "close"=> { event.window().close().unwrap(); } _ => {}})// Register the command.invoke_handler(tauri::generate_handler! [close_splashscreen]) .run(tauri::generate_context! ()) .expect("error while running tauri application");
}
Copy the code

First we introduce Menu, Submenu, MenuItem, CustomMenuItem.

Check Menu and Submenu source code:

/// 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}}Copy the code

The Menu structure is used to implement the application Menu. Its built-in new correlation function is used to create Menu, the add_item method is used to add custom Menu items, and the add_native_item method is used to add Menu items from the native implementation of Tauri. Add_submenu is used to add menu entries.

The Submenu structure is used to create entry points for menu items.

As shown in figure:

The Gear and Customer indicated by the arrow are Submenu, and the MenuItem items contained under Submenu are in the red box.

We created a Submenu called Gear and added some MenuItem items that Tauri native supports.

We also create a Submenu named Customer and add two custom CustomMenuItem items. CustomMenuItem events need to be defined by the developer:

// Listen for custom menu events
on_menu_event(|event| match event.menu_item_id() {
  "quit"= > {// Logic customization
    std::process::exit(0);
  }
  "close"= > {// Logic customizationevent.window().close().unwrap(); } _ => {}})Copy the code

The on_menu_event method listens for the trigger event of a custom menu item. It receives a closure as a parameter, matches the event ID of the menu item with match, and adds custom logic.

Matters needing attention

MenuItem is a MenuItem MenuItem supported by Tauri.

/// 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,
  ...
}
Copy the code

As you can see, these built-in menu items are not yet supported on Windows, Android and iOS platforms, but with the release of stable versions, we believe that these compatibility issues should be resolved.

debugging

In development mode, debugging is relatively easy. Here’s how to debug Rust and JavaScript code separately in development mode.

Rust Console

To debug Rust code, we can use println! Macro to print debugging information:

let msg = String::from("Debug Infos.")
println!("Hello Tauri! {}", msg);
Copy the code

The debugging information is displayed on the terminal:

WebView JS Console

Debugging JavaScript code can be done using console-related functions. To open the WebView console, right-click in the application window and select Inspect Element.

Console-related operations will not be described.

Tips: In some cases, we may also need to look at the WebView console in the final package, so Tauri provides a simple command to create itDebugging package:

yarn tauri build --debug
Copy the code

Packaged applications through the command will be placed in the SRC – tauri/target/debug/bundle directory.

The application package

yarn tauri build
Copy the code

This command embeds the Web resource in a single binary along with Rust code. Binary file itself will be located in the SRC tauri/target/release / [name] app, the installer will be located in the SRC – tauri/target/release/bundle /.

Roadmap

Tauri’s Roadmap is for a stable release in Q1 2022, including support for Deno and packaging to mobile devices. So it’s worth looking forward to Tauri’s development.

conclusion

Tauri is smaller, faster, and more secure. Compared to Electron’s problems of too big a package and too much memory, Tauri is indeed a promising framework for desktop application development, and Rust’s help makes this framework attractive. However, Tauri has not been released in a stable version so far, and some features are not compatible with multiple platforms, so it cannot be widely used in production environments. With The development of Tauri, these problems will be solved and a significant portion of the desktop application development market will be occupied by Tauri. As developers, there is no better time to learn from Tauri and Rust

More exciting, please pay attention to our public number “100 bottle technology”, there are not regular benefits!