テーマや文字の大きさ、最近開いたファイルの一覧のような小さな設定は、Store プラグインで「キーと値」の組として保存するのが手軽です。中身は JSON ファイルで、読み書きはメモリ上で行い、変更は少し待ってからまとめてファイルに書かれます。同じファイルを開いたウィンドウ同士と Rust 側で同じ内容を共有でき、変更の通知も受け取れます。保存のタイミングと既定値の扱い、ファイルが壊れたときの挙動に注意が要ります。
前提条件
npm run tauri add store
src-tauri/capabilities/default.json に store:default を足します。読み書き・保存・再読み込みのすべての操作が含まれます。変更の通知(onKeyChange())はイベントの購読を使いますが、これは core:default に含まれます。Store の権限にはパスの範囲指定が無く、どのパスのファイルでも開けます。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"store:default"
]
}
保存先の選び方は次のとおりです。
| 保存したいもの | 使うもの |
|---|---|
| 小さな設定・状態(平文でよい) | Store プラグイン(このレシピ) |
| ファイルの形式や書き込み方を自分で決めたい設定 | 自前の JSON ファイル |
| パスワードや API トークン | Stronghold |
| 消えても作り直せる UI の状態 | LocalStorage / IndexedDB |
| 件数が多い・検索するデータ | SQLite |
| Excel などとやり取りする表 | CSV ファイル |
| ウィンドウの位置と大きさ | Window State プラグイン |
1. フロントエンドから実装する (TypeScript)
開き方と既定値を 1 か所で決める
LazyStore は最初に使われたときにファイルを読みます(すぐ読むなら await load())。相対パスはアプリのデータフォルダー(appDataDir())が基準です。defaults はファイルの中身と合成され、ファイルに無いキーだけ既定値になるので、新しい版で設定項目を増やしても古いファイルのまま動きます。
注意したいのは、同じパスのストアはアプリ全体で 1 つだけ作られ、後から渡したオプションは無視されることです。ウィンドウごとに違う defaults を書くと、先に開いた側の設定になります。オプションは 1 つのモジュールにまとめ、どのウィンドウからもそれを import します。
import { LazyStore } from '@tauri-apps/plugin-store';
export type Theme = 'system' | 'light' | 'dark';
export const settings = new LazyStore('settings.json', {
defaults: { theme: 'system', fontSize: 14, recentFiles: [] },
autoSave: 500, // 最後の変更から 500ms 後にまとめて保存(省略時は 100ms、false で自動保存しない)
});
export async function getTheme(): Promise<Theme> {
return (await settings.get<Theme>('theme')) ?? 'system'; // 無いキーは undefined
}
export async function setTheme(theme: Theme): Promise<void> {
await settings.set('theme', theme);
}
get<T>() の型は宣言だけで、中身は確かめません。利用者がファイルを手で書き換えることもあるので、大事な値は読んだ後に確かめます。値は JSON として送られるので、Date は文字列になって戻ります。
変更を受け取る・すぐに書き込む
onKeyChange() は、ほかのウィンドウや Rust で値が変わったときにも呼ばれます。テーマを全ウィンドウでそろえるのに使えます。自動保存は変更から少し遅れて書き込み、アプリが通常の手順で終了するときにも保存されますが、強制終了では直前の変更が失われることがあります。消えると困る変更は save() ですぐ書き込みます。
// (続き)
function applyTheme(theme: Theme) {
document.documentElement.dataset.theme = theme;
}
// 別のウィンドウや Rust で theme が変わったら反映する
const unlisten = await settings.onKeyChange<Theme>('theme', (value) => applyTheme(value ?? 'system'));
applyTheme(await getTheme());
export async function acceptLicense() {
await settings.set('licenseAccepted', true);
await settings.save(); // 自動保存を待たずに書き込む
}
主なメソッドは次のとおりです。
| メソッド | 動き |
|---|---|
has() / delete() | キーの有無の確認・削除 |
keys() / entries() / length() | 一覧と件数 |
reset() | defaults の状態に戻す |
clear() | すべて消す(既定値も消える) |
reload() | ファイルを読み直す(外で編集されたとき) |
2. バックエンドから実装する (Rust)
画面より先に使いたい設定は、setup で StoreExt の store_builder() から読みます。setup はページより先に動くので、ここで作ったストアに JS の defaults は効きません。Rust で先に開くなら、既定値も Rust 側で決めます。Rust の set() も JS の onKeyChange() に届きます。
use serde_json::json;
use std::time::Duration;
use tauri::Manager;
use tauri_plugin_store::StoreExt;
const SETTINGS: &str = "settings.json";
/// 最近開いたファイルの先頭に追加する(最大 10 件)
#[tauri::command]
fn add_recent_file(app: tauri::AppHandle, path: String) -> Result<Vec<String>, String> {
let store = app.store(SETTINGS).map_err(|e| e.to_string())?;
let mut recent: Vec<String> = store
.get("recentFiles")
.and_then(|v| serde_json::from_value(v).ok())
.unwrap_or_default();
recent.retain(|p| p != &path);
recent.insert(0, path);
recent.truncate(10);
store.set("recentFiles", json!(recent));
Ok(recent)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.setup(|app| {
let store = app
.store_builder(SETTINGS)
.default("theme", "system")
.default("fontSize", 14)
.default("recentFiles", json!([]))
.auto_save(Duration::from_millis(500))
.build()?;
// 保存したテーマをタイトルバーなどに反映する("system" は OS に合わせる)
let theme = match store.get("theme").as_ref().and_then(|v| v.as_str()) {
Some("dark") => Some(tauri::Theme::Dark),
Some("light") => Some(tauri::Theme::Light),
_ => None,
};
if let Some(win) = app.get_webview_window("main") {
win.set_theme(theme)?;
}
Ok(())
})
.invoke_handler(tauri::generate_handler![add_recent_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
const recent = await invoke<string[]>('add_recent_file', { path: 'C:\\work\\report.csv' });
console.log(recent); // ['C:\\work\\report.csv', ...]
動作確認
npm run tauri dev で起動し、ボタンなどから setTheme('dark') を呼びます。0.5 秒ほどで、Windows なら %APPDATA%\<identifier>\settings.json に次のように書かれます(キーの並びは保存のたびに変わることがあります)。
{
"fontSize": 14,
"recentFiles": [],
"theme": "dark"
}
同じページのウィンドウを 2 つ開いて片方でテーマを変えると、もう片方も onKeyChange() で切り替わります。再起動後も getTheme() は dark を返します。
よくあるエラーと対処法
- 「store.load not allowed. Permissions associated with this command: store:allow-load, store:default」: 権限の追加漏れです(リリースビルドでは「Command plugin:store|load not allowed by ACL」)。
store:defaultを足して再起動します。 - 「plugin store not found」:
lib.rsで.plugin(tauri_plugin_store::Builder::default().build())を登録していません。 - 設定がすべて既定値に戻った: ファイルの JSON が壊れていると、エラーにならず既定値で開き、次の保存で壊れたファイルは上書きされます。手で編集する運用なら、事前に写しを取るよう案内します。
defaultsやautoSaveが効かない: そのパスのストアを先にほかのウィンドウか Rust が開いています。オプションを 1 か所にまとめます。
OS ごとの違いと注意点
- 保存場所: 相対パスは
appDataDir()の下で、Windows はAppData\Roaming、macOS は~/Library/Application Support、Linux は~/.local/shareの<identifier>フォルダーです。Linux で~/.configに置きたいなら、appConfigDir()とjoin()で作った絶対パスを渡します(fs-018)。 - 平文: 誰でも読めるテキストなので、秘密の値は Stronghold に分けます。
- 大きなデータに向かない: 全体をメモリに持ち、保存のたびにファイル全体を書き直します。
- アプリを 2 つ起動したとき: それぞれが自分のメモリの内容で上書きし合い、後から保存した方が残ります。
set_theme(): Linux と macOS ではアプリ全体に効き、iOS / Android では使えません。
