扔掉 Electron,拥抱基于 Rust 开发的 Tauri

2023-05-16

cover


公众号名片
作者名片

Tauri 是什么

Tauri 是一个跨平台 GUI 框架,与 Electron 的思想基本类似。Tauri 的前端实现也是基于 Web 系列语言,Tauri 的后端使用 Rust。Tauri 可以创建体积更小、运行更快、更加安全的跨平台桌面应用。

为什么选择 Rust?

Rust 是一门赋予每个人构建可靠且高效软件能力的语言。它在高性能、可靠性、生产力方面表现尤为出色。Rust 速度惊人且内存利用率极高,由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。Rust 也拥有出色的文档、友好的编译器和清晰的错误提示信息,还集成了一流的工具——包管理器和构建工具……

基于此,让 Rust 成为不二之选,开发人员可以很容易的使用 Rust 扩展 Tauri 默认的 Api 以实现定制化功能。

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

环境安装

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

创建 Tauri 项目

按一下回车键,继续……

Web 框架选择

可以看出,目前主流的 Web 框架 Tauri 都支持,
我们选择 create-vite……

Web 框架选择

此处选择 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 ApiRust 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");
}

首先我们引入 MenuSubmenuMenuItemCustomMenuItem

查看 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 关联函数用来创建 menuadd_item 方法用来添加自定义菜单项,add_native_item 方法用来添加 Tauri 原生实现的菜单项,add_submenu 用来添加菜单入口。

Submenu 这个结构体用来创建菜单项的入口。

如图:

菜单

箭头所指的 GearCustomer 就是 Submenu,红框里是 Submenu 下所包含的 MenuItem 项。

我们创建了一个命名为 GearSubmenu,并添加了一些 Tauri 原生支持的 MenuItem 项进去。

我们也创建了一个命名为 CustomerSubmenu,并添加了两个自定义的 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,
  ...
}

可以看出内置的这些菜单项在 WindowsAndroidiOS 平台都还不支持,但是随着稳定版的发布,相信这些兼容性问题应该能得到很好的解决。

调试

在开发模式下,调试相对容易。以下来看在开发模式下如何分别调试 RustJavaScript 代码。

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 的最佳时机,行动起来吧~

更多精彩请关注我们的公众号「百瓶技术」,有不定期福利呦!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

扔掉 Electron,拥抱基于 Rust 开发的 Tauri 的相关文章

随机推荐

  • unity c# 录音并保存为 mp3 或 wav 文件

    private int Frequency 61 16000 录音频率 private int BitRate 61 16 比特率 private int MicSecond 61 2 每隔2秒 xff0c 保存一下录音数据 public
  • Unity c# Application类 文件路径

    Application dataPath Assets资源文件夹的绝对路径 Application persistentDataPath 持久性的数据存储路径 xff0c 在不同平台路径不同 xff0c 但都存在 xff0c 绝对路径 Ap
  • c++ 图像RGB24旋转90度和YUV420旋转90度

    逆时针旋转90度 void RGBRotate90 anticlockwise BYTE des BYTE src int width int height if des src return int n 61 0 int linesize
  • ubuntu x86搭建 麒麟arm QT6交叉编译

    ubuntu搭建QT6交叉编译 使用QT6搭建arm交叉编译平台 编译环境准备 交叉编译器 qt源码准备 开始编译QT 使用QT6搭建arm交叉编译平台 近期项目需求开发平台是unbuntu x86 目标机器是UOS arm架构 由于需要在
  • java分解质因数

    一个数的因数就是能与别的数相乘得到这个数的数 比如30 xff0c 它的因数就是1 xff0c 2 xff0c 3 xff0c 5 xff0c 6 xff0c 10 xff0c 15 xff0c 30 质因数 xff0c 首先 xff0c
  • Mac上pip/pip3设置国内源

    pip3 config set global index url https pypi tuna tsinghua edu cn simple
  • WSL基本使用配置

    前提 相信很多同学已经体验了wsl的强大 能让win电脑上少装一个虚拟机软件 xff0c 但是原生的wsl操作窗口复制粘贴很不方便 xff0c 对于使用习惯ssh的人来说用原生的确实有些难受 xff0c 故需要配置下使用ssh进行连接操作
  • 创建ECS服务器

    阿里云服务器 作业一 xff1a ECS之初体验 xff08 Linux xff09 任务一 xff1a 创建弹性云服务器 任务二 xff1a 登录云服务器 分别使用vnc Workbench和xshell登录云服务器 任务三 xff1a
  • Python报错:ModuleNotFoundError: No module named ‘xxx‘可能的解决方案大全

    Python报错 xff1a 34 ModuleNotFoundError No module named 39 xxx 39 34 这个报错是个非常常见的报错 xff0c 几乎每个python程序员都遇到过 xff0c 导致这个报错的原因
  • 反证法证明:为什么KMP算法不会跳过(漏掉)正确的答案

    KMP算法用于在母串中查找子串的出现位置 KMP算法 xff1a 字符串匹配问题 有详细的引入过程 xff0c 很容易理解掌握 首先我们都知道 xff0c KMP算法的next数组可以指导匹配失败情况下 xff0c 子串 xff08 模式串
  • 详解介绍Selenium常用API的使用--Java语言(完整版)

    参考 xff1a http www testclass net selenium java 一共分为二十个部分 xff1a 环境安装之Java 环境安装之IntelliJ IDEA 环境安装之selenium selenium3浏览器驱动
  • 华为OD2023机试真题【字符串重新排序】

    华为OD2023机试真题 全题库点这里 题目名称 字符串重新排序 知识点 排序数组 时间限制 1s 空间限制 256M 题目描述 给定一个字符串s s包含以空格分隔的若干单词 请对s进行如下处理后输出 span class token nu
  • 解决WSL2中Vmmem内存占用过大问题

    一 问题描述 在 Windows 系统中 xff0c 感觉卡顿得厉害 查看任务管理器 xff0c 内存占用 98 而名为 Vmmem 的进程占用内存高达 2 1 GB 如图 xff1a 二 Vmmem介绍 Vmmem 进程是系统合成的一个虚
  • 5款最佳Linux桌面环境的优缺点比较

    如果你刚接触Linux xff0c 那么我确信你准花了大量的时间为你的Linux发行版选择桌面环境 你可能在想每一种桌面环境都试一下 xff0c 不过这很耗费时间 外头有好多优秀的桌面环境 这就是为什么我测评了5款最佳Linux桌面环境 x
  • vue3.2中setup语法糖<script lang=“ts“ setup>

    推荐阅读 xff1a 怎样使用 Vue 3 的 xff1c script setup xff1e 语法糖功能 南北极之间的博客 CSDN博客 在 Vue 3 中 xff0c 它引入了一个 功能 它是编译时语法糖 xff0c 用于在单个文件组
  • libc++abi.dylib`__cxa_throw:毫无预兆崩溃

    最近在接一款第三方直播api 一开始很正常 xff0c 从来调试的时候意外的出现了一下崩溃 经过一段时间的摸索 xff0c 各种找资料 xff0c 发觉是由于xcode中设置了当所有异常出现时的断点 解决办法是将all改为Objective
  • UOS安装最新 向日葵(ubuntu20.04也试用)

    下载依赖 libicu57 57 1 6 43 deb9u4 amd64 deb xff08 http mirrors aliyun com debian pool main i icu libicu57 57 1 6 43 deb9u4
  • UOS 安装 vscode

    商店安装版同官网冲突 官网下载缓慢 Selecting previously unselected package code An error occurred while applying changes An error occurre
  • org.freedesktop.timedate1: Launch helper exited

    WSL UBUNTU org freedesktop timedate1 Launch helper exited bin bash sudo hwclock w hwclock r date sudo service dbus resta
  • 扔掉 Electron,拥抱基于 Rust 开发的 Tauri

    Tauri 是什么 Tauri 是一个跨平台 GUI 框架 xff0c 与 Electron 的思想基本类似 Tauri 的前端实现也是基于 Web 系列语言 xff0c Tauri 的后端使用 Rust Tauri 可以创建体积更小 运行