フロントエンドの dist/ に入れたファイルは WebView からしか見えません。Rust 側で開きたい初期データベースや辞書ファイルなどは、bundle.resources で「リソース」として同梱します。同梱したファイルは OS ごとに決まった場所に展開され、Rust からは app.path().resource_dir()、フロントからは resolveResource() で実パスを解決できます。
前提条件
Rust から読むだけならプラグインは不要です。フロントエンドから直接読む場合は fs プラグインと権限が必要です。
npm run tauri add fs
src-tauri/capabilities/default.json にリソース配下の読み取りを許可します。
{
"permissions": [
"core:default",
"fs:allow-resource-read-recursive"
]
}
範囲を絞るなら fs:allow-read-text-file にスコープ { "path": "$RESOURCE/lang/*" } を付ける形でも構いません($RESOURCE がリソースディレクトリを指すパス変数です)。
1. tauri.conf.json でリソースを指定する
配列形式と、配置先を指定するオブジェクト形式があります。パスは src-tauri/ からの相対パスです。
{
"bundle": {
"resources": {
"resources/lang/*.json": "lang/",
"resources/seed.db": "db/seed.db",
"../shared/images/": "images/"
}
}
}
配列形式 ["resources/**/*"] にすると、src-tauri/ からの相対パス構造がそのまま保たれます。glob は dir/(再帰)、dir/*(直下のファイルのみ)、dir/**/*(サブディレクトリ内もすべて)の 3 種を使い分けます。配列形式で ../ を使うと配置先が _up_ というディレクトリ名に置き換わるので、src-tauri/ の外を参照するときはオブジェクト形式で配置先を明示するのが無難です。
2. フロントエンドから読む (TypeScript)
import { resolveResource } from '@tauri-apps/api/path';
import { readTextFile, readFile } from '@tauri-apps/plugin-fs';
// 同梱した JSON を読む
async function loadLang(code: string) {
const path = await resolveResource(`lang/${code}.json`);
return JSON.parse(await readTextFile(path));
}
// 同梱した画像を <img> に表示する
async function loadImage(name: string): Promise<string> {
const bytes = await readFile(await resolveResource(`images/${name}`));
return URL.createObjectURL(new Blob([bytes]));
}
resolveResource は core:default に含まれる path 権限で使え、絶対パスを返します。画像が多い場合は convertFileSrc で asset プロトコル経由にする方が軽量ですが、app.security.assetProtocol の設定が別途必要です。
3. バックエンドから読む (Rust)
use tauri::{path::BaseDirectory, Manager};
#[tauri::command]
fn load_lang(app: tauri::AppHandle, code: String) -> Result<String, String> {
let path = app
.path()
.resolve(format!("lang/{code}.json"), BaseDirectory::Resource)
.map_err(|e| e.to_string())?;
std::fs::read_to_string(&path).map_err(|e| e.to_string())
}
// 初期 DB はリソースから書き込み可能な場所へコピーしてから開く
#[tauri::command]
fn prepare_db(app: tauri::AppHandle) -> Result<String, String> {
let src = app.path().resolve("db/seed.db", BaseDirectory::Resource).map_err(|e| e.to_string())?;
let data_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
std::fs::create_dir_all(&data_dir).map_err(|e| e.to_string())?;
let dst = data_dir.join("app.db");
if !dst.exists() {
std::fs::copy(&src, &dst).map_err(|e| e.to_string())?;
}
Ok(dst.to_string_lossy().into_owned())
}
invoke_handler に tauri::generate_handler![load_lang, prepare_db] を登録し、フロントからは invoke('prepare_db') で呼びます。ディレクトリ自体は app.path().resource_dir() で得られます。
動作確認
npm run tauri dev では、リソースが src-tauri/target/debug/ 配下にコピーされ、そこが resource_dir() になります。tauri.conf.json を変更すると dev が再起動し、追加したファイルが読めるようになります。
$ npm run tauri dev
...
[load_lang] /home/you/app/src-tauri/target/debug/lang/ja.json
本番ビルド後は、Windows なら実行ファイルと同じフォルダ、macOS なら .app/Contents/Resources/、Linux の .deb なら /usr/lib/<name>/ に lang/ や db/ が展開されていることを確認します。
よくあるエラーと対処法
フロントから読むと not allowed になる
「fs.read_text_file not allowed. Permissions associated with this command: ...」という趣旨のエラーは、capability にリソースの読み取り権限がないためです。fs:allow-resource-read-recursive を追加してください。非再帰の fs:allow-resource-read でサブディレクトリを読もうとした場合も同じエラーになります。
ビルド時に glob に一致するファイルがない
resources/** のようにディレクトリだけに一致するパターンや、typo でファイルが見つからない場合、バンドル段階で「リソースが見つからない」という趣旨のエラーで止まります。ファイルを含めるには resources/**/* のように末尾に * を付けます。
インストール後に DB へ書き込めない
macOS の署名済み .app や Windows の Program Files、Linux の /usr/lib は一般ユーザーには書き込めません。リソースは読み取り専用と考え、上の prepare_db のように初回起動時に app_data_dir() へコピーしてから使ってください。
OS ごとの違いと注意点
- Windows: リソースは実行ファイルの隣(インストールフォルダ直下)に展開されます。
resource_dir()は.exeのあるディレクトリです - macOS:
<productName>.app/Contents/Resources/配下。署名後にファイルを差し替えると署名が壊れます - Linux:
.deb/.rpmは/usr/lib/<パッケージ名>/、AppImage は展開先のusr/lib/<パッケージ名>/です - 実行可能なバイナリを同梱する場合は
resourcesではなくexternalBinを使います(同梱したバイナリファイルを実行する)
