managing-tauri-app-resources について
Tauri アプリのリソースと状態を管理する開発者向けリファレンススキルです。プラットフォーム別アイコンの生成、静的ファイルのバンドルとアクセス、コマンドやハンドラー間での状態共有を扱います。
主な機能
- 01プラットフォーム別アイコンの生成
- 02静的リソースのバンドル設定
- 03Rust と JavaScript からのリソースアクセス
- 04リソースアクセス権限の設定
- 05共有・可変状態のパターン
活用シーン
- 01Tauri 向けプラットフォームアイコンの生成
- 02ファイルの Tauri アプリへの同梱
- 03Rust または JavaScript で同梱リソースを読み込む
- 04Tauri コマンド間のアプリ状態管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dchuk/ claude-code-tauri-skills --skill managing-tauri-app-resourcesインストール後、Claude Code で/managing-tauri-app-resourcesを入力して読み込まれたか確認します。
詳細
Tauri アプリのリソース管理
アプリアイコン
アイコンの生成
1 つのソースファイルから、すべてのプラットフォーム固有のアイコンを生成します。
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
ソースの要件: 透過に対応した正方形の PNG または SVG。
生成される形式
| 形式 | プラットフォーム |
|---|---|
icon.icns | macOS |
icon.ico | Windows |
*.png | Linux、Android、iOS |
設定
{
"bundle": {
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/[email protected]",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
プラットフォームの要件
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/ に配置します。必要なサイズは、1x/2x/3x スケールの 20、29、40、60、76、83.5 ピクセルに加え、512x512@2x です。
静的リソースの埋め込み
設定
配列構文(ディレクトリー構造を保持します):
{
"bundle": {
"resources": ["./file.txt", "folder/", "docs/**/*.md"]
}
}
マップ構文(カスタムの配置先を指定します):
{
"bundle": {
"resources": {
"path/to/source.json": "resources/dest.json",
"docs/**/*.md": "website-docs/"
}
}
}
パスパターン
| パターン | 動作 |
|---|---|
"dir/file.txt" | 単一ファイル |
"dir/" | ディレクトリーを再帰的に処理 |
"dir/*" | ファイルを再帰的に処理しない |
"dir/**/*" | すべてのファイルを再帰的に処理 |
リソースへのアクセス - 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())
}
リソースへのアクセス - 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": [
"fs:allow-read-text-file",
"fs:allow-resource-read-recursive"
]
}
再帰的なアクセスには $RESOURCE/**/* スコープを使用します。
状態管理
基本設定
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()
}
スレッドセーフな可変状態
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()
}
コマンド内での状態へのアクセス
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
}
Tokio Mutex を使用した非同期コマンド
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)
}
コマンド外での状態へのアクセス
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;
}
型エイリアスパターン
型の不一致による実行時のパニックを防ぎます。
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
}
複数の状態型
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;
}
要点
- Arc は不要です - Tauri が内部で参照カウントを処理します
- ほとんどの場合は std::sync::Mutex を使用します。Tokio の Mutex は、await ポイントをまたいでロックを保持する場合にのみ使用します
- 型安全性 - 誤った状態型を指定すると、コンパイルエラーではなく実行時にパニックが発生します。型エイリアスを使用してください
完全な例
tauri.conf.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:
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()
}
フロントエンド:
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));
クイックリファレンス
アイコンのコマンド
cargo tauri icon # Generate from ./app-icon.png
cargo tauri icon ./icon.png -o out # Custom source/output
リソースパターン
{ "resources": ["data.json"] } // Single file
{ "resources": ["assets/"] } // Directory recursive
{ "resources": { "src/x.json": "x.json" }} // Custom destination
状態パターン
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