Lesson 17-Tauri Advanced Features and Optimization

Overview of Tauri Advanced Features and Optimization

Tauri’s Advanced Features

Tauri offers a range of advanced capabilities:

  • Custom Events: Monitor system and user events.
  • Multithreading and Concurrency: Utilize Rust for complex tasks.
  • Security: Sandbox model and permission control.
  • Performance Optimization: Monitoring and resource management.
  • Hot Updates: Update mechanism without reinstallation.
  • Internationalization: Multilingual support.
  • Error Handling: Robust logging system.

Optimization Goals and Importance

  • Performance: Improve startup speed and runtime efficiency.
  • Security: Prevent unauthorized access.
  • User Experience: Ensure smooth interaction and rapid updates.

Development Environment Setup

Initialize Project

npx create-tauri-app my-tauri-app
cd my-tauri-app
npm install

Install Dependencies

cargo add tokio --features "full"
cargo add log env_logger serde_json

Custom System Event Listening

Listening to System Events

Example Code

src-tauri/src/main.rs
use tauri::{Manager, WindowEvent};

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let window = app.get_window("main").unwrap();
            window.on_window_event(|event| match event {
                WindowEvent::Resized(size) => println!("Window resized to {:?}", size),
                WindowEvent::CloseRequested { .. } => println!("Close requested"),
                _ => {}
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Analysis

  • on_window_event: Listens to window events.
  • Event Types: Includes resizing, close requests, etc.

Triggering Custom Events

Example Code

src-tauri/src/main.rs
#[tauri::command]
fn trigger_event(window: tauri::Window) {
    window.emit("custom-event", "Event triggered").unwrap();
}
src/main.js
const { event } = window.__TAURI__.tauri;

event.listen('custom-event', (e) => {
    document.getElementById('output').textContent = e.payload;
});

async function trigger() {
    await invoke('trigger_event');
}

Analysis

  • emit: Rust triggers the event.
  • event.listen: Frontend listens for the event.

Multithreading and Concurrency

Using Rust Multithreading

Example Code

src-tauri/src/main.rs
use std::thread;

#[tauri::command]
fn run_threaded_task() -> String {
    let handle = thread::spawn(|| {
        thread::sleep(std::time::Duration::from_secs(2));
        "Task completed".to_string()
    });
    handle.join().unwrap()
}

Tokio Concurrent Tasks

Example Code

src-tauri/src/main.rs
use tokio::time::{sleep, Duration};

#[tauri::command]
async fn run_async_task() -> String {
    sleep(Duration::from_secs(2)).await;
    "Async task completed".to_string()
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![run_async_task])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Analysis

  • thread::spawn: Multithreading.
  • tokio: Asynchronous concurrency.

Security and Sandbox Model

Tauri’s Sandbox Mechanism

Tauri enables a sandbox by default, restricting frontend access to the system.

Configuring the Sandbox

src-tauri/tauri.conf.json
{
  "tauri": {
    "allowlist": {
      "all": false,
      "fs": {
        "readFile": true,
        "scope": ["$APP/data/*"]
      }
    }
  }
}

Permission Control and Security Configuration

Example Code

src-tauri/src/main.rs
#[tauri::command]
fn read_secure_file(path: String) -> Result<String, String> {
    if !path.starts_with("data/") {
        return Err("Access denied".to_string());
    }
    tauri::api::file::read_text(path).map_err(|e| e.to_string())
}

Analysis

  • Permission Restrictions: Limits access to specific paths.
  • Error Handling: Enhances security.

Performance Monitoring and Optimization

Performance Monitoring Tools

Example Code

src-tauri/src/main.rs
use std::time::Instant;

#[tauri::command]
fn measure_performance() -> String {
    let start = Instant::now();
    for _ in 0..1000000 {}
    let duration = start.elapsed();
    format!("Task took {}ms", duration.as_millis())
}

Optimization Strategies

Example Code

src/main.js
const tasks = [];
function addTask(task) {
    tasks.push(task);
    if (tasks.length >= 10) {
        invoke('process_tasks', { tasks });
        tasks.length = 0; // Clear array
    }
}

Analysis

  • Batch Processing: Reduces IPC calls.
  • Rust Compilation: Use --release mode for optimization.

Hot Updates and Automatic Upgrades

Configuring Hot Updates

Configuring tauri.conf.json

{
  "tauri": {
    "updater": {
      "active": true,
      "endpoints": ["https://releases.myapp.com/updates.json"],
      "pubkey": "YOUR_PUBLIC_KEY"
    }
  }
}

Implementing Automatic Upgrades

Rust Backend

src-tauri/src/main.rs
use tauri::updater::UpdaterExt;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            app.updater().check().unwrap();
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Analysis

  • updater: Checks and installs updates.
  • Signature: Ensures update security.

Resource Management and Loading Optimization

Static Resource Management

Configuring Webpack

const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [{ from: 'src/assets', to: 'assets' }],
    }),
  ],
};

Dynamic Loading Optimization

Example Code

src/main.js
const loadImage = () => import('./assets/image.png').then(src => {
    document.getElementById('image').src = src.default;
});

Analysis

  • Lazy Loading: Reduces initial load time.

Internationalization and Localization

Configuring Multilingual Support

src/i18n.json

{
  "en": {
    "title": "Task Manager",
    "add": "Add"
  },
  "zh": {
    "title": "Task Manager",
    "add": "Add"
  }
}

Frontend and Backend Internationalization

Frontend

src/main.js
async function loadLanguage(lang) {
    const response = await fetch(`./i18n.json`);
    const i18n = await response.json();
    const texts = i18n[lang];
    document.getElementById('title').textContent = texts.title;
    document.getElementById('addButton').textContent = texts.add;
}

Rust Backend

src-tauri/src/main.rs
#[tauri::command]
fn get_language(lang: String) -> String {
    match lang.as_str() {
        "en" => "Welcome".to_string(),
        "zh" => "Welcome".to_string(),
        _ => "Hello".to_string(),
    }
}

Analysis

  • Multilingual: Frontend dynamically loads, backend provides support.

Error Handling and Logging

Error Handling Mechanism

Example Code

src-tauri/src/main.rs
#[tauri::command]
fn might_fail() -> Result<String, String> {
    Err("Something went wrong".to_string())
}

Frontend

async function handleError() {
    try {
        const result = await invoke('might_fail');
    } catch (error) {
        document.getElementById('output').textContent = error;
    }
}

Logging Implementation

Configuring Dependencies

[dependencies]
log = "0.4"
env_logger = "0.10"

Rust Backend

src-tauri/src/main.rs
use log::{info, error};

fn main() {
    env_logger::init();
    tauri::Builder::default()
        .setup(|_| {
            info!("Application started");
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![might_fail])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command]
fn might_fail() -> Result<String, String> {
    error!("Task failed");
    Err("Something went wrong".to_string())
}

Comprehensive Case Study: Optimized Task Manager

Requirements Analysis

Build a task manager that:

  • Notifies task completion via custom events.
  • Executes tasks with multithreading.
  • Restricts file access with sandboxing.
  • Monitors performance and supports hot updates.
  • Implements internationalization and logging.

Implementation Code Breakdown

src-tauri/tauri.conf.json

{
  "tauri": {
    "allowlist": {
      "fs": { "all": true, "scope": ["$APP/data/*"] },
      "notification": { "all": true },
      "updater": { "active": true, "endpoints": ["https://releases.myapp.com/updates.json"] }
    }
  }
}

src-tauri/Cargo.toml

[dependencies]
tauri = { version = "1.5", features = ["api-all"] }
tokio = { version = "1.37", features = ["full"] }
log = "0.4"
env_logger = "0.10"
serde_json = "1.0"

src-tauri/src/main.rs

use tauri::{Manager, Window};
use tokio::time::{sleep, Duration};
use log::{info, error};
use std::sync::Mutex;

struct AppState {
    tasks: Mutex<Vec<String>>,
}

#[tauri::command]
async fn add_task(task: String, window: Window, state: tauri::State<AppState>) -> Result<(), String> {
    info!("Adding task: {}", task);
    let handle = tokio::spawn(async move {
        sleep(Duration::from_secs(2)).await;
        window.emit("task-completed", &task).unwrap();
    });
    let mut tasks = state.tasks.lock().unwrap();
    tasks.push(task);
    handle.await.map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_tasks(state: tauri::State<AppState>) -> Vec<String> {
    let tasks = state.tasks.lock().unwrap();
    tasks.clone()
}

fn main() {
    env_logger::init();
    let state = AppState {
        tasks: Mutex::new(vec![]),
    };

    tauri::Builder::default()
        .manage(state)
        .invoke_handler(tauri::generate_handler![add_task, get_tasks])
        .setup(|app| {
            app.updater().check().unwrap();
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

src/i18n.json

{
  "en": { "title": "Task Manager", "add": "Add" },
  "zh": { "title": "Task Manager", "add": "Add" }
}

src/main.js

const { invoke, event } = window.__TAURI__.tauri;

let tasks = [];
let lang = 'en';

async function loadLanguage() {
    const response = await fetch(`./i18n.json`);
    const i18n = await response.json();
    const texts = i18n[lang];
    document.getElementById('title').textContent = texts.title;
    document.getElementById('addButton').textContent = texts.add;
}

async function addTask() {
    const task = document.getElementById('taskInput').value.trim();
    if (task) {
        try {
            await invoke('add_task', { task });
            updateTasks();
        } catch (error) {
            console.error('Error:', error);
        }
    }
}

async function updateTasks() {
    tasks = await invoke('get_tasks');
    document.getElementById('taskList').innerHTML = tasks.map(t => `<li>${t}</li>`).join('');
}

event.listen('task-completed', (e) => {
    document.getElementById('output').textContent = `Completed: ${e.payload}`;
    updateTasks();
});

document.getElementById('addButton').addEventListener('click', addTask);
window.addEventListener('DOMContentLoaded', () => {
    loadLanguage();
    updateTasks();
});

src/index.html

<!DOCTYPE html>
<html>
<head>
  <title>Task Manager</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; text-align: center; }
    ul { list-style: none; padding: 0; }
    li { padding: 10px; background-color: #f0f0f0; margin-bottom: 5px; }
  </style>
</head>
<body>
  <h1 id="title"></h1>
  <input id="taskInput" placeholder="Enter task">
  <button id="addButton"></button>
  <ul id="taskList"></ul>
  <div id="output"></div>
  <script src="main.js"></script>
</body>
</html>

Running the Application

npm run tauri dev

Analysis

  • Event Listening: Notifies task completion.
  • Multithreading: Asynchronous task execution.
  • Security: Sandbox restrictions.
  • Internationalization: Multilingual support.
  • Logging: Records operations.
Share your love