テーマや文字の大きさ、最後に開いたフォルダーといった設定は、appConfigDir()(Rust は app_config_dir())が返すフォルダーに保存します。フォルダー名は tauri.conf.json の identifier で、場所は OS の慣習に従います。注意したいのは、Linux ではデータ用とは別に ~/.config の下に作られる一方、Windows と macOS ではデータ用の appDataDir() と同じフォルダーになることです。ここでは、初回起動や壊れたファイルに強い設定ファイルの読み書きと、画面を出す前に Rust で設定を読む方法を示します。
前提条件
パスの取得は core:default に含まれる core:path:default で行えます。ファイルの読み書きには File System プラグインを追加します。
npm run tauri add fs
fs:default だけで、設定フォルダーの作成と読み取り(mkdir()・exists()・readTextFile())ができます。保存に使う fs:allow-write-text-file を足します。範囲は fs:default がアプリ用フォルダーを覆っているので書かなくて構いません(範囲と $APPCONFIG などの変数は ファイルやディレクトリを削除する)。2 章の Rust だけで読み書きする方式なら、fs プラグインもこの権限も要りません。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
"fs:allow-write-text-file"
]
}
設定フォルダーを使うときの注意
各 OS の実際の場所と、ほかのフォルダーとの使い分けは アプリ専用のデータ保存フォルダパスを取得する の表にまとめています。設定ファイルを置くうえでは、次の点を押さえます。
- データ用と同じフォルダーになる OS がある: Windows と macOS では
appConfigDir()とappDataDir()が同じ場所です。設定とデータに同じファイル名を使うと上書きし合います。 - フォルダーは自動では作られない: 初回起動では、設定ファイルもフォルダーも無い前提で書きます。
- 初期化はファイル単位で: 「設定をリセット」でフォルダーごと消すと、Windows と macOS ではデータも消えます。
- Store プラグインの既定は別の場所: Store プラグイン は相対パスを
appDataDir()基準で保存します。設定フォルダーに置くなら、appConfigDir()とjoin()で作った絶対パスを渡します。
1. フロントエンドから実装する (TypeScript)
読み込みでは、初回起動でファイルが無い、手で編集されたり保存中に強制終了したりして JSON が壊れている、古い版で保存されて新しい項目が無い、の 3 つに備えます。既定値に保存した値を重ねれば、足りない項目は既定値で補われます。壊れていたときは既定値で起動しますが、次の保存で上書きされる前に中身を別名で残しておくと、利用者が手で直せます。一方、ファイルを読めないこと自体(権限など)は既定値で隠さず、例外のまま返します。
import { appConfigDir } from '@tauri-apps/api/path';
import { BaseDirectory, exists, mkdir, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
export type Settings = { theme: 'system' | 'light' | 'dark'; fontSize: number; lastFolder: string | null };
export const DEFAULTS: Settings = { theme: 'system', fontSize: 14, lastFolder: null };
const FILE = 'settings.json';
const opts = { baseDir: BaseDirectory.AppConfig };
export async function loadSettings(): Promise<Settings> {
if (!(await exists(FILE, opts))) return { ...DEFAULTS }; // 初回起動
const text = await readTextFile(FILE, opts); // 読めないときは例外のまま
try {
return { ...DEFAULTS, ...(JSON.parse(text) as Partial<Settings>) }; // 無い項目は既定値
} catch {
await writeTextFile('settings.broken.json', text, opts); // 壊れた中身を残しておく
return { ...DEFAULTS };
}
}
export async function saveSettings(s: Settings): Promise<void> {
await mkdir(await appConfigDir(), { recursive: true }); // 初回はフォルダー自体が無い
await writeTextFile(FILE, JSON.stringify(s, null, 2), opts);
}
JSON.stringify(s, null, 2) で整形しておくと、利用者やサポートが中身を読みやすくなります。保存中の強制終了でも壊れないようにするなら、一時ファイルに書いてから名前を変える手順(テキストファイルに書き込む)にします。
2. バックエンドから実装する (Rust)
ウィンドウの大きさやテーマのように画面を出す前に反映したい設定は、Rust の setup で読みます。読んだ値を State に置き、JS からはコマンドで取得・保存する形にすると、ファイルを触るのが Rust だけになり、JS と Rust がそれぞれ保存して食い違うこともありません。#[serde(default)] を付けると、ファイルに無い項目は Default の値になります。
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use tauri::{Manager, State};
#[derive(Serialize, Deserialize, Clone)]
#[serde(default, rename_all = "camelCase")]
struct Settings {
theme: String,
font_size: u32,
last_folder: Option<String>,
}
impl Default for Settings {
fn default() -> Self {
Self { theme: "system".into(), font_size: 14, last_folder: None }
}
}
struct SettingsState {
path: PathBuf,
current: Mutex<Settings>,
}
/// 無ければ既定値。壊れていれば settings.broken.json に退避して既定値
fn load_settings(path: &Path) -> std::io::Result<Settings> {
let text = match std::fs::read_to_string(path) {
Ok(t) => t,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Settings::default()),
Err(e) => return Err(e),
};
Ok(serde_json::from_str(&text).unwrap_or_else(|_| {
let _ = std::fs::rename(path, path.with_extension("broken.json"));
Settings::default()
}))
}
#[tauri::command]
fn get_settings(state: State<'_, SettingsState>) -> Settings {
state.current.lock().unwrap().clone()
}
#[tauri::command]
fn save_settings(state: State<'_, SettingsState>, settings: Settings) -> Result<(), String> {
let text = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
let tmp = state.path.with_extension("json.tmp");
std::fs::write(&tmp, text).map_err(|e| e.to_string())?;
std::fs::rename(&tmp, &state.path).map_err(|e| e.to_string())?; // 書き終えてから置き換える
*state.current.lock().unwrap() = settings;
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let dir = app.path().app_config_dir()?;
std::fs::create_dir_all(&dir)?;
let path = dir.join("settings.json");
let current = Mutex::new(load_settings(&path)?);
// ここで読んだテーマなどをウィンドウに反映してから表示できる
app.manage(SettingsState { path, current });
Ok(())
})
.invoke_handler(tauri::generate_handler![get_settings, save_settings])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
type Settings = { theme: string; fontSize: number; lastFolder: string | null };
const current = await invoke<Settings>('get_settings');
await invoke('save_settings', { settings: { ...current, fontSize: 16 } });
動作確認
npm run tauri dev で起動して saveSettings({ ...DEFAULTS, theme: 'dark' }) を呼ぶと、Windows では C:\Users\me\AppData\Roaming\com.example.myapp\settings.json、Linux では ~/.config/com.example.myapp/settings.json に次の内容が保存されます。
{
"theme": "dark",
"fontSize": 14,
"lastFolder": null
}
このファイルの最後の } を消して再起動すると、loadSettings() は既定値を返し、同じフォルダーに settings.broken.json ができます。
よくあるエラーと対処法
- 「failed to open file at path: <パス> with error: …」: 設定フォルダーがまだありません。保存の前に
recursive: trueのmkdir()でフォルダーを作ります。 - 「fs.write_text_file not allowed. Permissions associated with this command: …」: 保存の権限の追加漏れです(リリースビルドでは「Command plugin:fs|write_text_file not allowed by ACL」)。
- 更新後に設定が初期化される:
#[serde(default)]が無いと、古い設定ファイルに無い項目があるだけで読み込みが「missing field」を含むエラーになり、上のload_settings()では壊れたファイルとして退避されます。構造体に#[serde(default)]を付けます。 - 保存したはずの設定が戻る: JS と Rust の両方が同じファイルを書いていないか確かめます。State に読み込んだ後で JS がファイルを書き換えても、State は古いままです。
OS ごとの違いと注意点
- Windows:
Roamingの下です。移動プロファイルの環境ではサインインのたびにほかの PC と同期されるので、大きなファイルは置かずappLocalDataDir()に回します。 - macOS: データ用・ローカルデータ用と同じ
~/Library/Application Support/<id>です。 - Linux:
XDG_CONFIG_HOMEがあればその下、無ければ~/.config/<id>です。データ用(~/.local/share/<id>)とは別の場所なので、設定とデータが同じフォルダーにある前提で相対パスを組むと、Linux でだけ失敗します。 - 共通: 利用者がファイルを直接編集することもあります。型の違う値(
"fontSize": "14"など)まで防ぐなら、読み込んだ後に値を確かめます。
