Tauri framework: Building lightweight desktop applications with Rust
Tauri is an open source framework built with Rust for creating lightweight, secure, and efficient desktop applications. It combines the power of Rust with web technologies such as HTML, CSS, and JavaScript to provide a modern, cross-platform way to develop desktop applications. The core concept of Tauri is the “principle of least privilege”, calling the operating system API only when necessary to reduce the attack surface.
Tauri Architecture
The Tauri architecture mainly consists of the following parts:
1. Rust backend: written in Rust, responsible for interacting with the operating system, handling system events, security control, and API calls.
2. Web frontend: Use web technologies (HTML, CSS, and JavaScript) to create user interfaces, which can be based on any frontend framework (such as React, Vue, or Svelte).
3. Tauri API: A set of APIs provided by the Rust backend for communicating with the frontend to achieve data exchange and function calls between the frontend and backend.
4. Wrapper: A lightweight embedded Webview for displaying the frontend interface and interacting with the Rust backend.
Create a simple Tauri application
First, make sure you have Rust and Cargo installed. Then, use the tauri init command to create a new Tauri project:
cargo tauri init my-app
This generates a basic project structure, including src-tauri
(Rust backend) and src
(web frontend) directories.
Rust backend (src-tauri/main.rs
)
use tauri::{Manager, SubCommand};
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri app");
}
This is the main entry point for your Tauri application. The generate_context!
macro will automatically generate the required API and event handlers.
Web frontend (src/index.html
and src/index.ts
)
index.html
is your application interface, you can use any HTML structure you like. index.ts
is the TypeScript file that handles the Tauri API calls and event listeners.
<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Tauri App</title>
</head>
<body>
<h1>Hello, Tauri!</h1>
<button id="click-me">Click me!</button>
<script src="index.js"></script>
</body>
</html>
// src/index.ts
import { invoke } from '@tauri-apps/api';
document.getElementById('click-me')?.addEventListener('click', async () => {
const result = await invoke('sayHello');
alert(`Tauri says: ${result}`);
})
In this example, the invoke
function is used to call the sayHello
method of the Rust backend. Next, we need to implement this method in the Rust backend.
Rust backend implementation API (src-tauri/src/main.rs
)
// Import necessary libraries
use tauri::{Command, Manager, Window};
// Define say_hello command
#[tauri::command]
fn say_hello() -> String {
"Hello, frontend!".to_string()
}
fn main() {
// ...other code
// Register say_hello command
let manager = Manager::build_app(tauri::generate_context!())
.sub_command(Command::new("sayHello").exec(say_hello))
.run();
// ...other code
}
Now, when you click the button, the frontend calls the sayHello method of the Rust backend and displays the returned message.
Compile and run
Compile the project using cargo tauri build
, then run target/release/my-app
(or run the .exe file on Windows).
Advanced features and best practices of Tauri
1. Custom APIs and events
Tauri allows you to customize APIs and events for more complex communication between the Rust backend and the web frontend. For example, create an API for opening a file selection dialog:
// src-tauri/src/main.rs
use tauri::{Manager, Response, Window};
#[tauri::command]
async fn open_file() -> Result<String, String> {
let file_path = tauri::api::dialog::open_file().await?;
Ok(file_path.display().to_string())
}
Call on the Web front end:
// src/index.ts
import { invoke } from '@tauri-apps/api';
document.getElementById('open-file')?.addEventListener('click', async () => {
const filePath = await invoke('openFile');
console.log(`Selected file: ${filePath}`);
});
2. Use front-end frameworks
Tauri integrates seamlessly with front-end frameworks such as React, Vue, and Svelte. For example, create a component using React:
// src/App.js (React)
import React, { useState } from 'react';
import { invoke } from '@tauri-apps/api';
const App = () => {
const [filePath, setFilePath] = useState('');
const handleOpenFile = async () => {
const result = await invoke('openFile');
setFilePath(result);
};
return (
<div>
<button onClick={handleOpenFile}>Open File</button>
<p>{filePath}</p>
</div>
);
};
export default App;
3. Resource Management
Tauri provides built-in resource management functions, which can package static resources into applications. Configure in tauri.conf.json
:
{
"build": {
"resourcesPath": "./resources"
}
}
Then use tauri::api::fs::read
in the Rust backend to read the resource:
// src-tauri/src/main.rs
use tauri::{Manager, Response, Window};
#[tauri::command]
fn read_resource() -> Result<String, String> {
let content = tauri::api::fs::read("resources/myfile.txt")?;
Ok(String::from_utf8_lossy(content.as_ref()).to_string())
}
4. Application Update
Tauri supports automatic update, you can use the tauri-update
library to achieve it. Configure tauri.conf.json
:
{
"update": {
"enabled": true,
"interval": "1d",
"url": "https://myapp.com/releases"
}
}
Then handle the update event in the Rust backend:
// src-tauri/src/main.rs
use tauri::{Manager, Update};
fn main() {
let manager = Manager::build_app(tauri::generate_context!())
.update(Update::new())
.run();
// ...Other codes
}
5. System Integration
Tauri provides a rich system integration API, such as tray icons, menus, shortcut keys, etc. For example, to create a tray icon:
// src-tauri/src/main.rs
use tauri::{Manager, Window};
fn main() {
let mut manager = Manager::build_app(tauri::generate_context!());
manager.set_tray_icon("path/to/icon.png");
manager.run();
// ...Other codes
}
6. Security and Sandbox
Tauri follows the principle of least privilege and calls system APIs only when necessary. You can configure security policies to limit application permissions, such as disabling file system access:
// tauri.conf.json
{
"security": {
"allow": {
"fs": false
}
}
}
Tauri’s plugin system and extension capabilities
Plugin system
Tauri’s plugin system allows developers to extend its core functionality by writing Rust libraries to provide additional services or integrate external libraries. Plugins can interact with Tauri applications in a safe and efficient way, adding more possibilities to applications.
Creating custom plugins
- Define the plugin interface: First, define your plugin interface in Rust. This usually involves creating a trait that defines the functionality you want to expose to the front end.
// my_plugin.rs
use tauri::plugin::{Builder, TauriPlugin};
use tauri::Runtime;
#[tauri::command]
async fn custom_function<R: Runtime>(app: tauri::AppHandle<R>, arg: String) -> Result<String, String> {
// Implement your functional logic
Ok(format!("Custom function received: {}", arg))
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("myPlugin")
.invoke_handler(tauri::generate_handler![custom_function])
.build()
}
2. Use the plugin in the Tauri application: In src-tauri/src/main.rs, register your plugin through the Builder’s .plugin() method.
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.plugin(my_plugin::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
3. Call the plug-in method on the front end: In the front-end code, call your plug-in method through the invoke function provided by @tauri-apps/api.
// src/index.ts
import { invoke } from '@tauri-apps/api';
async function callCustomFunction() {
const result = await invoke('myPlugin:customFunction', { arg: 'Hello from frontend!' });
console.log(result);
}
Using Community Plugins
The Tauri community is active, and there are many ready-made plugins available, such as database integration, graphics rendering, network requests, etc. These plugins are usually hosted on GitHub or Crates.io, and you can learn how to integrate them into your project by reading the documentation.
Extensions and Integrations
- Database Integration: You can use Rust’s database drivers (such as Diesel, sqlx) to write plugins to provide database access capabilities for applications.
- Graphics and Multimedia: Use Rust’s multimedia libraries (such as image, rodio) to develop image processing, audio playback and other functions.
- Hardware Access: Access hardware resources through Rust’s low-level libraries, such as serial port communication, GPIO control, etc., which is suitable for Internet of Things (IoT) applications.
Security Considerations
Although Tauri’s design principles emphasize security, you still need to pay attention to security practices when developing plugins:
- The principle of least privilege: Ensure that the plugin only requests the minimum permissions required to complete the task.
- Code Audit: Conduct code reviews on third-party plugins to ensure that no security vulnerabilities are introduced.
- Sandbox environment: Use Tauri’s security policy to limit plugin access to system resources.
Tauri debugging and testing
Debugging
Tauri provides some tools and methods to help developers debug applications:
- Logs: Tauri supports detailed log output, which can be viewed in the terminal. Configure the log level in
tauri.conf.json
:
{
"logger": {
"level": "debug"
}
}
2. Developer Tools: When running the app, you can enable developer mode using the — dev flag, which will open the Webview’s developer tools (F12).
cargo tauri dev --dev
3. Rust debugging: Use Rust’s cargo command-line tool for source-level debugging, such as cargo rustc — — break and cargo run — — dev.
4. Front-end debugging: In developer mode, you can use Webview’s developer tools to debug front-end code, including JavaScript, CSS, and HTML.
Testing
Tauri provides support for unit testing and integration testing:
- Rust unit testing: For the Rust backend, you can write standard Rust unit tests. Create a tests subdirectory under the src-tauri directory and write test files there.
- Integration testing: Tauri provides a library called tauri-testing for writing integration tests. These tests can be run directly in the simulated Tauri environment without actually building and running the entire application.
// Add dependencies in Cargo.toml
[dependencies]
tauri-testing = { version = "latest", features = ["mock"] }
// In the test file
use tauri_testing::{mock, Command};
#[test]
fn test_say_hello() {
let app = mock::init().unwrap();
let result = app.invoke("sayHello").unwrap();
assert_eq!(result, "Hello, world!");
}
3. Front-end testing: For front-end code, you can use any JavaScript testing framework you like (such as Jest, Mocha) for testing.
Performance optimization
- Resource compression: Configure the build options in tauri.conf.json to enable resource compression and merging to reduce the size of the application.
{
"build": {
"distDir": "../dist",
"bundle": true,
"minify": true,
"target": "web",
"resolveSymlinks": false
}
}
2. Cache strategy: Use Tauri’s cache configuration to cache resources and reduce network requests.
3. Asynchronous loading: Use dynamic import and lazy loading strategies to load front-end code only when needed.
4. Performance monitoring: Use Chrome DevTools or other performance analysis tools to monitor application performance, identify bottlenecks and optimize.