managing-tauri-app-resources

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Managing Tauri App Resources

Tauri应用资源管理

App Icons

应用图标

Icon Generation

图标生成

Generate all platform-specific icons from a single source file:
bash
cargo tauri icon              # Default: ./app-icon.png
cargo tauri icon ./custom.png -o ./icons  # Custom source/output
cargo tauri icon --ios-color "#000000"    # iOS background color
Source requirements: Squared PNG or SVG with transparency.
从单个源文件生成所有平台专属图标:
bash
cargo tauri icon              # 默认:./app-icon.png
cargo tauri icon ./custom.png -o ./icons  # 自定义源文件/输出路径
cargo tauri icon --ios-color "#000000"    # 设置iOS图标背景色
源文件要求: 带透明通道的正方形PNG或SVG文件。

Generated Formats

生成的格式

FormatPlatform
icon.icns
macOS
icon.ico
Windows
*.png
Linux, Android, iOS
格式平台
icon.icns
macOS
icon.ico
Windows
*.png
Linux, Android, iOS

Configuration

配置

json
{
  "bundle": {
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}
json
{
  "bundle": {
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}

Platform Requirements

平台要求

Windows (.ico): Layers for 16, 24, 32, 48, 64, 256 pixels.
Android: No transparency. Place in
src-tauri/gen/android/app/src/main/res/mipmap-*
folders. Each needs
ic_launcher.png
,
ic_launcher_round.png
,
ic_launcher_foreground.png
.
iOS: No transparency. Place in
src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/
. Required sizes: 20, 29, 40, 60, 76, 83.5 pixels with 1x/2x/3x scales, plus 512x512@2x.

Windows (.ico): 需包含16、24、32、48、64、256像素的图层。
Android: 不支持透明通道。将图标放置在
src-tauri/gen/android/app/src/main/res/mipmap-*
文件夹中,每个文件夹需包含
ic_launcher.png
ic_launcher_round.png
ic_launcher_foreground.png
iOS: 不支持透明通道。将图标放置在
src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/
目录下。所需尺寸:20、29、40、60、76、83.5像素,包含1x/2x/3x缩放版本,以及512x512@2x尺寸。

Embedding Static Resources

嵌入静态资源

Configuration

配置

Array syntax (preserves directory structure):
json
{
  "bundle": {
    "resources": ["./file.txt", "folder/", "docs/**/*.md"]
  }
}
Map syntax (custom destinations):
json
{
  "bundle": {
    "resources": {
      "path/to/source.json": "resources/dest.json",
      "docs/**/*.md": "website-docs/"
    }
  }
}
数组语法(保留目录结构):
json
{
  "bundle": {
    "resources": ["./file.txt", "folder/", "docs/**/*.md"]
  }
}
映射语法(自定义目标路径):
json
{
  "bundle": {
    "resources": {
      "path/to/source.json": "resources/dest.json",
      "docs/**/*.md": "website-docs/"
    }
  }
}

Path Patterns

路径匹配规则

PatternBehavior
"dir/file.txt"
Single file
"dir/"
Directory recursive
"dir/*"
Files non-recursive
"dir/**/*"
All files recursive
规则行为
"dir/file.txt"
匹配单个文件
"dir/"
递归匹配目录下所有内容
"dir/*"
非递归匹配目录下所有文件
"dir/**/*"
递归匹配目录下所有文件

Accessing Resources - Rust

访问资源 - Rust

rust
use tauri::Manager;
use tauri::path::BaseDirectory;

#[tauri::command]
fn load_resource(handle: tauri::AppHandle) -> Result<String, String> {
    let path = handle
        .path()
        .resolve("lang/de.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    std::fs::read_to_string(&path).map_err(|e| e.to_string())
}
rust
use tauri::Manager;
use tauri::path::BaseDirectory;

#[tauri::command]
fn load_resource(handle: tauri::AppHandle) -> Result<String, String> {
    let path = handle
        .path()
        .resolve("lang/de.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    std::fs::read_to_string(&path).map_err(|e| e.to_string())
}

Accessing Resources - JavaScript

访问资源 - JavaScript

javascript
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile } from '@tauri-apps/plugin-fs';

const resourcePath = await resolveResource('lang/de.json');
const content = await readTextFile(resourcePath);
const data = JSON.parse(content);
javascript
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile } from '@tauri-apps/plugin-fs';

const resourcePath = await resolveResource('lang/de.json');
const content = await readTextFile(resourcePath);
const data = JSON.parse(content);

Permissions

权限配置

json
{
  "permissions": [
    "fs:allow-read-text-file",
    "fs:allow-resource-read-recursive"
  ]
}
Use
$RESOURCE/**/*
scope for recursive access.

json
{
  "permissions": [
    "fs:allow-read-text-file",
    "fs:allow-resource-read-recursive"
  ]
}
使用
$RESOURCE/**/*
权限范围来实现递归访问。

State Management

状态管理

Basic Setup

基础设置

rust
use tauri::{Builder, Manager};

struct AppData {
    welcome_message: &'static str,
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(AppData {
                welcome_message: "Welcome!",
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}
rust
use tauri::{Builder, Manager};

struct AppData {
    welcome_message: &'static str,
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(AppData {
                welcome_message: "Welcome!",
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}

Thread-Safe Mutable State

线程安全的可变状态

rust
use std::sync::Mutex;
use tauri::{Builder, Manager};

#[derive(Default)]
struct AppState {
    counter: u32,
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(AppState::default()));
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}
rust
use std::sync::Mutex;
use tauri::{Builder, Manager};

#[derive(Default)]
struct AppState {
    counter: u32,
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(AppState::default()));
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}

Accessing State in Commands

在命令中访问状态

rust
use std::sync::Mutex;
use tauri::State;

#[tauri::command]
fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 {
    let mut state = state.lock().unwrap();
    state.counter += 1;
    state.counter
}

#[tauri::command]
fn get_counter(state: State<'_, Mutex<AppState>>) -> u32 {
    state.lock().unwrap().counter
}
rust
use std::sync::Mutex;
use tauri::State;

#[tauri::command]
fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 {
    let mut state = state.lock().unwrap();
    state.counter += 1;
    state.counter
}

#[tauri::command]
fn get_counter(state: State<'_, Mutex<AppState>>) -> u32 {
    state.lock().unwrap().counter
}

Async Commands with Tokio Mutex

结合Tokio Mutex的异步命令

rust
use tokio::sync::Mutex;
use tauri::State;

#[tauri::command]
async fn increase_counter_async(
    state: State<'_, Mutex<AppState>>
) -> Result<u32, ()> {
    let mut state = state.lock().await;
    state.counter += 1;
    Ok(state.counter)
}
rust
use tokio::sync::Mutex;
use tauri::State;

#[tauri::command]
async fn increase_counter_async(
    state: State<'_, Mutex<AppState>>
) -> Result<u32, ()> {
    let mut state = state.lock().await;
    state.counter += 1;
    Ok(state.counter)
}

Accessing State Outside Commands

在命令外部访问状态

rust
use std::sync::Mutex;
use tauri::{Manager, Window, WindowEvent};

fn on_window_event(window: &Window, event: &WindowEvent) {
    let app_handle = window.app_handle();
    let state = app_handle.state::<Mutex<AppState>>();
    let mut state = state.lock().unwrap();
    state.counter += 1;
}
rust
use std::sync::Mutex;
use tauri::{Manager, Window, WindowEvent};

fn on_window_event(window: &Window, event: &WindowEvent) {
    let app_handle = window.app_handle();
    let state = app_handle.state::<Mutex<AppState>>();
    let mut state = state.lock().unwrap();
    state.counter += 1;
}

Type Alias Pattern

类型别名模式

Prevent runtime panics from type mismatches:
rust
use std::sync::Mutex;

struct AppStateInner {
    counter: u32,
}

type AppState = Mutex<AppStateInner>;

#[tauri::command]
fn get_counter(state: State<'_, AppState>) -> u32 {
    state.lock().unwrap().counter
}
避免因类型不匹配导致的运行时恐慌:
rust
use std::sync::Mutex;

struct AppStateInner {
    counter: u32,
}

type AppState = Mutex<AppStateInner>;

#[tauri::command]
fn get_counter(state: State<'_, AppState>) -> u32 {
    state.lock().unwrap().counter
}

Multiple State Types

多状态类型

rust
use std::sync::Mutex;
use tauri::{Builder, Manager, State};

struct UserState { username: Option<String> }
struct AppSettings { theme: String }

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(UserState { username: None }));
            app.manage(Mutex::new(AppSettings { theme: "dark".into() }));
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}

#[tauri::command]
fn login(user_state: State<'_, Mutex<UserState>>, username: String) {
    user_state.lock().unwrap().username = Some(username);
}

#[tauri::command]
fn set_theme(settings: State<'_, Mutex<AppSettings>>, theme: String) {
    settings.lock().unwrap().theme = theme;
}
rust
use std::sync::Mutex;
use tauri::{Builder, Manager, State};

struct UserState { username: Option<String> }
struct AppSettings { theme: String }

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(UserState { username: None }));
            app.manage(Mutex::new(AppSettings { theme: "dark".into() }));
            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap()
}

#[tauri::command]
fn login(user_state: State<'_, Mutex<UserState>>, username: String) {
    user_state.lock().unwrap().username = Some(username);
}

#[tauri::command]
fn set_theme(settings: State<'_, Mutex<AppSettings>>, theme: String) {
    settings.lock().unwrap().theme = theme;
}

Key Points

关键点

  • Arc not required - Tauri handles reference counting internally
  • Use std::sync::Mutex for most cases; Tokio's mutex only for holding locks across await points
  • Type safety - Wrong state types cause runtime panics, not compile errors; use type aliases

  • 无需手动使用Arc - Tauri内部已处理引用计数
  • 多数场景使用std::sync::Mutex;仅当需要在await点之间持有锁时才使用Tokio的mutex
  • 类型安全 - 错误的状态类型会导致运行时恐慌而非编译错误;建议使用类型别名

Complete Example

完整示例

tauri.conf.json:
json
{
  "bundle": {
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ],
    "resources": {
      "assets/config.json": "config.json",
      "assets/translations/": "lang/"
    }
  }
}
src-tauri/src/main.rs:
rust
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
use tauri::{Builder, Manager, State};
use tauri::path::BaseDirectory;

#[derive(Default)]
struct AppState { counter: u32, locale: String }
type ManagedState = Mutex<AppState>;

#[derive(Serialize, Deserialize)]
struct Config { app_name: String, version: String }

#[tauri::command]
fn increment(state: State<'_, ManagedState>) -> u32 {
    let mut s = state.lock().unwrap();
    s.counter += 1;
    s.counter
}

#[tauri::command]
fn load_config(handle: tauri::AppHandle) -> Result<Config, String> {
    let path = handle.path()
        .resolve("config.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
    serde_json::from_str(&content).map_err(|e| e.to_string())
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(AppState::default()));
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![increment, load_config])
        .run(tauri::generate_context!())
        .unwrap()
}
Frontend:
javascript
import { invoke } from '@tauri-apps/api/core';
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile } from '@tauri-apps/plugin-fs';

const newValue = await invoke('increment');
const config = await invoke('load_config');
const langPath = await resolveResource('lang/en.json');
const translations = JSON.parse(await readTextFile(langPath));

tauri.conf.json:
json
{
  "bundle": {
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ],
    "resources": {
      "assets/config.json": "config.json",
      "assets/translations/": "lang/"
    }
  }
}
src-tauri/src/main.rs:
rust
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
use tauri::{Builder, Manager, State};
use tauri::path::BaseDirectory;

#[derive(Default)]
struct AppState { counter: u32, locale: String }
type ManagedState = Mutex<AppState>;

#[derive(Serialize, Deserialize)]
struct Config { app_name: String, version: String }

#[tauri::command]
fn increment(state: State<'_, ManagedState>) -> u32 {
    let mut s = state.lock().unwrap();
    s.counter += 1;
    s.counter
}

#[tauri::command]
fn load_config(handle: tauri::AppHandle) -> Result<Config, String> {
    let path = handle.path()
        .resolve("config.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
    serde_json::from_str(&content).map_err(|e| e.to_string())?;
}

fn main() {
    Builder::default()
        .setup(|app| {
            app.manage(Mutex::new(AppState::default()));
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![increment, load_config])
        .run(tauri::generate_context!())
        .unwrap()
}
前端代码:
javascript
import { invoke } from '@tauri-apps/api/core';
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile } from '@tauri-apps/plugin-fs';

const newValue = await invoke('increment');
const config = await invoke('load_config');
const langPath = await resolveResource('lang/en.json');
const translations = JSON.parse(await readTextFile(langPath));

Quick Reference

快速参考

Icon Commands

图标命令

bash
cargo tauri icon                    # Generate from ./app-icon.png
cargo tauri icon ./icon.png -o out  # Custom source/output
bash
cargo tauri icon                    # 从./app-icon.png生成图标
cargo tauri icon ./icon.png -o out  # 自定义源文件/输出路径

Resource Patterns

资源匹配规则

json
{ "resources": ["data.json"] }              // Single file
{ "resources": ["assets/"] }                // Directory recursive
{ "resources": { "src/x.json": "x.json" }}  // Custom destination
json
{ "resources": ["data.json"] }              // 单个文件
{ "resources": ["assets/"] }                // 递归匹配目录
{ "resources": { "src/x.json": "x.json" }}  // 自定义目标路径

State Patterns

状态管理模式

rust
app.manage(Config { ... });              // Immutable
app.manage(Mutex::new(State { ... }));   // Mutable
fn cmd(state: State<'_, Mutex<T>>)       // In command
app_handle.state::<Mutex<T>>()           // Via AppHandle
rust
app.manage(Config { ... });              // 不可变状态
app.manage(Mutex::new(State { ... }));   // 可变状态
fn cmd(state: State<'_, Mutex<T>>)       // 在命令中访问状态
app_handle.state::<Mutex<T>>()           // 通过AppHandle访问状态