Tauri Main Process and Renderer Process
Tauri Architecture Overview
Tauri builds cross-platform applications using Rust and system-native Webview:
- Main Process:
- Runs Rust, handling core logic and native functionality.
- Manages windows, system interactions, and IPC.
- Renderer Process:
- Runs Webview, executing frontend code (HTML/CSS/JS).
- Communicates with the main process via Webview.
- Communication Bridge:
- Uses Rust’s command system and JavaScript’s IPC.
Differences and Collaboration Between Main and Renderer Processes
- Main Process:
- Controls the application lifecycle.
- Handles native operations like file system and network.
- Renderer Process:
- Renders the UI and handles user interactions.
- Collaboration:
- The main process responds to frontend requests via Rust commands.
- The renderer process invokes main process functions through
window.__TAURI__.
Development Environment Setup
Initialize Project
npx create-tauri-app my-tauri-app
cd my-tauri-app
npm installProject Structure
my-tauri-app/
├── src/ # Frontend code
├── src-tauri/ # Rust main process code
│ ├── Cargo.toml
│ ├── tauri.conf.json
│ └── src/
│ └── main.rs
├── package.jsonTauri Command System
Command System Basics
Tauri’s command system enables the frontend to call Rust-defined functions via JavaScript:
- Rust Side: Uses
#[tauri::command]to define commands. - Frontend Side: Uses
tauri.invoketo call commands.
Defining and Invoking Commands
Example Code
src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}src/main.js
const { invoke } = window.__TAURI__.tauri;
async function sayHello() {
const name = document.getElementById('nameInput').value;
const result = await invoke('greet', { name });
document.getElementById('output').textContent = result;
}
document.getElementById('greetButton').addEventListener('click', sayHello);src/index.html
<!DOCTYPE html>
<html>
<head>
<title>Greet App</title>
</head>
<body>
<input id="nameInput" placeholder="Enter your name">
<button id="greetButton">Greet</button>
<div id="output"></div>
<script src="main.js"></script>
</body>
</html>Running the Application
npm run tauri devAnalysis
#[tauri::command]: Marks a Rust function as a command.invoke: Frontend calls the Rust function.- Parameter Passing: Serialized in JSON format.
Understanding the tauri::api Module
Functionality of tauri::api Module
The tauri::api module provides built-in tools for system interactions:
tauri::api::dialog: Dialogs.tauri::api::file: File operations.tauri::api::http: Network requests.
Common API Examples
File Operations
src-tauri/src/main.rs
use tauri::api::file::write_text;
#[tauri::command]
fn save_file(content: String) -> Result<(), String> {
write_text("output.txt", &content).map_err(|e| e.to_string())?;
Ok(())
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![save_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}src-tauri/tauri.conf.json
{
"tauri": {
"allowlist": {
"fs": {
"writeFile": true
}
}
}
}src/main.js
async function saveContent() {
const content = document.getElementById('contentInput').value;
await invoke('save_file', { content });
alert('Content saved!');
}Dialogs
src-tauri/src/main.rs
use tauri::api::dialog::MessageDialogBuilder;
#[tauri::command]
fn show_dialog(message: String) {
MessageDialogBuilder::new("Alert", message)
.kind(tauri::api::dialog::MessageDialogKind::Info)
.show();
}src/main.js
async function showDialog() {
await invoke('show_dialog', { message: 'Hello from Tauri!' });
}Analysis
- Permissions: Must be enabled in
tauri.conf.json. - Invocation: Commands bridge Rust and frontend.
State Management and Message Passing
State Management in Tauri
Tauri provides State for managing shared state in the Rust main process.
Example Code
src-tauri/src/main.rs
use tauri::State;
use std::sync::Mutex;
struct AppState {
tasks: Mutex<Vec<String>>,
}
#[tauri::command]
fn add_task(state: State<AppState>, task: String) {
let mut tasks = state.tasks.lock().unwrap();
tasks.push(task);
}
#[tauri::command]
fn get_tasks(state: State<AppState>) -> Vec<String> {
let tasks = state.tasks.lock().unwrap();
tasks.clone()
}
fn main() {
let state = AppState {
tasks: Mutex::new(vec![]),
};
tauri::Builder::default()
.manage(state)
.invoke_handler(tauri::generate_handler![add_task, get_tasks])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Analysis
State: Shared state in Rust.Mutex: Thread-safe lock.
Message Passing Between Frontend and Backend
Example Code
src/main.js
const { invoke, event } = window.__TAURI__.tauri;
async function addTask() {
const task = document.getElementById('taskInput').value;
await invoke('add_task', { task });
updateTasks();
}
async function updateTasks() {
const tasks = await invoke('get_tasks');
document.getElementById('taskList').innerHTML = tasks.map(t => `<li>${t}</li>`).join('');
}
event.listen('task-added', (event) => {
updateTasks();
});
document.getElementById('addButton').addEventListener('click', addTask);
window.addEventListener('DOMContentLoaded', updateTasks);src-tauri/src/main.rs
#[tauri::command]
fn add_task(state: State<AppState>, task: String, window: tauri::Window) {
let mut tasks = state.tasks.lock().unwrap();
tasks.push(task.clone());
window.emit("task-added", &task).unwrap();
}Analysis
invoke: Calls Rust commands.event.listen: Listens for Rust events.
Plugins and Extended Functionality
Introduction to Tauri’s Plugin System
Tauri supports functionality extensions via plugins:
- Built-in Plugins: e.g.,
tauri-plugin-sql. - Custom Plugins: Written in Rust.
Creating and Using Custom Plugins
Creating a Plugin
cargo new --lib my-plugin
cd my-pluginmy-plugin/Cargo.toml
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
tauri = "1.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"my-plugin/src/lib.rs
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Runtime, Manager};
#[tauri::command]
fn custom_command() -> String {
"Hello from plugin!".to_string()
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("my-plugin")
.invoke_handler(tauri::generate_handler![custom_command])
.build()
}Integrating the Plugin
src-tauri/Cargo.toml
[dependencies]
my-plugin = { path = "../my-plugin" }src-tauri/src/main.rs
use my_plugin::init;
fn main() {
tauri::Builder::default()
.plugin(init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}src/main.js
async function callPlugin() {
const result = await invoke('custom_command', {}, { plugin: 'my-plugin' });
console.log(result);
}Analysis
- Plugin: Extends Tauri functionality.
- Invocation: Specified via
invokewith plugin namespace.
Comprehensive Case Study: Task Manager with Plugin Integration
Requirements Analysis
Build a task manager that:
- Manages a task list in the main window.
- Runs custom commands in a tools window.
- Uses a plugin to log actions.
Implementation Code Breakdown
src-tauri/tauri.conf.json
{
"tauri": {
"allowlist": {
"all": false
}
}
}my-plugin/src/lib.rs
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Runtime, Manager};
#[tauri::command]
fn log_message(message: String, app: tauri::AppHandle) {
println!("Plugin Log: {}", message);
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("logger")
.invoke_handler(tauri::generate_handler![log_message])
.build()
}src-tauri/Cargo.toml
[dependencies]
tauri = { version = "1.5", features = ["api-all"] }
my-plugin = { path = "../my-plugin" }src-tauri/src/main.rs
use tauri::{State, Manager};
use std::sync::Mutex;
struct AppState {
tasks: Mutex<Vec<String>>,
}
#[tauri::command]
fn add_task(state: State<AppState>, task: String, app: tauri::AppHandle) {
let mut tasks = state.tasks.lock().unwrap();
tasks.push(task.clone());
app.emit_all("task-added", &task).unwrap();
}
#[tauri::command]
fn get_tasks(state: State<AppState>) -> Vec<String> {
let tasks = state.tasks.lock().unwrap();
tasks.clone()
}
fn main() {
let state = AppState {
tasks: Mutex::new(vec![]),
};
tauri::Builder::default()
.manage(state)
.plugin(my_plugin::init())
.invoke_handler(tauri::generate_handler![add_task, get_tasks])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}src/main.js
const { invoke, event } = window.__TAURI__.tauri;
async function addTask() {
const task = document.getElementById('taskInput').value;
await invoke('add_task', { task });
await invoke('log_message', { message: `Task added: ${task}` }, { plugin: 'logger' });
updateTasks();
}
async function updateTasks() {
const tasks = await invoke('get_tasks');
document.getElementById('taskList').innerHTML = tasks.map(t => `<li>${t}</li>`).join('');
}
event.listen('task-added', updateTasks);
document.getElementById('addButton').addEventListener('click', addTask);
window.addEventListener('DOMContentLoaded', updateTasks);src/index.html
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
</head>
<body>
<h1>Task Manager</h1>
<input id="taskInput" placeholder="Enter task">
<button id="addButton">Add</button>
<ul id="taskList"></ul>
<script src="main.js"></script>
</body>
</html>Running the Application
npm run tauri devAnalysis
- Main Process: Manages task state.
- Renderer Process: Displays tasks.
- Plugin: Logs actions.
Advanced Techniques and Considerations
Performance Optimization Tips
- Reduce IPC: Batch command calls.
- State Caching: Avoid frequent queries.
Debugging Techniques
- Rust Logging:
println!("Debug: {:?}", state);- Frontend Debugging: Use Webview developer tools.
Summary and Learning Resources
This tutorial covers Tauri’s main and renderer processes, command system, tauri::api, state management, and plugin development in detail. The comprehensive case study demonstrates their practical application. Mastering these skills enables you to develop efficient Tauri applications.



