managing-tauri-app-resources
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseManaging 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 colorSource 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
生成的格式
| Format | Platform |
|---|---|
| macOS |
| Windows |
| Linux, Android, iOS |
| 格式 | 平台 |
|---|---|
| macOS |
| Windows |
| 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 folders. Each needs , , .
src-tauri/gen/android/app/src/main/res/mipmap-*ic_launcher.pngic_launcher_round.pngic_launcher_foreground.pngiOS: No transparency. Place in . Required sizes: 20, 29, 40, 60, 76, 83.5 pixels with 1x/2x/3x scales, plus 512x512@2x.
src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Windows (.ico): 需包含16、24、32、48、64、256像素的图层。
Android: 不支持透明通道。将图标放置在文件夹中,每个文件夹需包含、、。
src-tauri/gen/android/app/src/main/res/mipmap-*ic_launcher.pngic_launcher_round.pngic_launcher_foreground.pngiOS: 不支持透明通道。将图标放置在目录下。所需尺寸:20、29、40、60、76、83.5像素,包含1x/2x/3x缩放版本,以及512x512@2x尺寸。
src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/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
路径匹配规则
| Pattern | Behavior |
|---|---|
| Single file |
| Directory recursive |
| Files non-recursive |
| All files recursive |
| 规则 | 行为 |
|---|---|
| 匹配单个文件 |
| 递归匹配目录下所有内容 |
| 非递归匹配目录下所有文件 |
| 递归匹配目录下所有文件 |
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 scope for recursive access.
$RESOURCE/**/*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/outputbash
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 destinationjson
{ "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 AppHandlerust
app.manage(Config { ... }); // 不可变状态
app.manage(Mutex::new(State { ... })); // 可变状态
fn cmd(state: State<'_, Mutex<T>>) // 在命令中访问状态
app_handle.state::<Mutex<T>>() // 通过AppHandle访问状态