embedding-tauri-sidecars について
Tauri 開発者向けに、外部バイナリをサイドカーとしてアプリに組み込み、実行する方法を解説するガイドです。設定、プラットフォーム別の命名、Rust または JavaScript からの実行、プロセス管理を扱います。
主な機能
- 01バンドルするサイドカーの設定
- 02対象プラットフォーム別のバイナリ命名
- 03Rust と JavaScript からの実行
- 04出力ストリーミングとプロセス管理
- 05実行引数の制限
活用シーン
- 01Python CLI のアプリへの同梱
- 02バックグラウンド API サーバーの実行
- 03外部バイナリによるファイル処理
- 04プラットフォーム別実行ファイルの同梱
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dchuk/ claude-code-tauri-skills --skill embedding-tauri-sidecarsインストール後、Claude Code で/embedding-tauri-sidecarsを入力して読み込まれたか確認します。
詳細
Tauri サイドカー:外部バイナリの埋め込み
このスキルでは、Tauri アプリケーションへの外部バイナリ(サイドカー)の埋め込みと実行について、設定、クロスプラットフォームでの考慮事項、Rust および JavaScript からの実行方法を説明します。
概要
サイドカーは、機能を拡張したり、ユーザーが依存関係をインストールする必要をなくしたりするために、Tauri アプリケーションに埋め込む外部バイナリです。任意のプログラミング言語で記述された実行ファイルを使用できます。
一般的なユースケース:
- PyInstaller でパッケージ化した Python CLI アプリケーション
- 特定のタスク向けにコンパイルした Go または Rust バイナリ
- 実行ファイルとしてバンドルした Node.js アプリケーション
- API サーバーまたはバックグラウンドサービス
プラグインの依存関係
サイドカーには shell プラグインが必要です。
Cargo.toml:
[dependencies]
tauri-plugin-shell = "2"
main.rs で登録:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
フロントエンドパッケージ:
npm install @tauri-apps/plugin-shell
設定
サイドカーの登録
tauri.conf.json の bundle.externalBin でサイドカーを設定します。パスは src-tauri からの相対パスです。
{
"bundle": {
"externalBin": [
"binaries/my-sidecar",
"../external/processor"
]
}
}
重要: パスにはベース名を指定します。ビルド時に Tauri がターゲットトリプルのサフィックスを追加します。
クロスプラットフォームでのバイナリ命名
各サイドカーには、ターゲットトリプルのサフィックスが付いた、プラットフォーム固有のバリアントが必要です。
| プラットフォーム | アーキテクチャ | 必須ファイル名 |
|---|---|---|
| Linux | x86_64 | my-sidecar-x86_64-unknown-linux-gnu |
| Linux | ARM64 | my-sidecar-aarch64-unknown-linux-gnu |
| macOS | Intel | my-sidecar-x86_64-apple-darwin |
| macOS | Apple Silicon | my-sidecar-aarch64-apple-darwin |
| Windows | x86_64 | my-sidecar-x86_64-pc-windows-msvc.exe |
ターゲットトリプルを確認する:
rustc --print host-tuple # Rust 1.84.0+
rustc -Vv | grep host # Older versions
ディレクトリ構成
src-tauri/
binaries/
my-sidecar-x86_64-unknown-linux-gnu
my-sidecar-aarch64-apple-darwin
my-sidecar-x86_64-apple-darwin
my-sidecar-x86_64-pc-windows-msvc.exe
tauri.conf.json
src/main.rs
Rust からサイドカーを実行する
基本的な実行
use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn run_sidecar(app: tauri::AppHandle) -> Result<String, String> {
let output = app
.shell()
.sidecar("my-sidecar")
.map_err(|e| e.to_string())?
.output()
.await
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
注: sidecar() には、設定内のフルパスではなくファイル名のみを渡します。
引数を指定して実行
#[tauri::command]
async fn process_file(app: tauri::AppHandle, file_path: String) -> Result<String, String> {
let output = app
.shell()
.sidecar("processor")
.map_err(|e| e.to_string())?
.args(["--input", &file_path, "--format", "json"])
.output()
.await
.map_err(|e| e.to_string())?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
長時間実行するプロセスの起動
継続的に実行するサイドカー(API サーバー、ウォッチャーなど)の場合:
use tauri_plugin_shell::{ShellExt, process::CommandEvent};
#[tauri::command]
async fn start_server(app: tauri::AppHandle) -> Result<u32, String> {
let (mut rx, child) = app
.shell()
.sidecar("api-server")
.map_err(|e| e.to_string())?
.args(["--port", "8080"])
.spawn()
.map_err(|e| e.to_string())?;
let pid = child.pid();
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => println!("{}", String::from_utf8_lossy(&line)),
CommandEvent::Stderr(line) => eprintln!("{}", String::from_utf8_lossy(&line)),
CommandEvent::Terminated(payload) => {
println!("Terminated: {:?}", payload.code);
break;
}
_ => {}
}
}
});
Ok(pid)
}
サイドカーのライフサイクル管理
use std::sync::Mutex;
use tauri::State;
use tauri_plugin_shell::{ShellExt, process::CommandChild};
struct SidecarState {
child: Mutex<Option<CommandChild>>,
}
#[tauri::command]
async fn start_sidecar(app: tauri::AppHandle, state: State<'_, SidecarState>) -> Result<(), String> {
let (_, child) = app.shell().sidecar("service").map_err(|e| e.to_string())?
.spawn().map_err(|e| e.to_string())?;
*state.child.lock().unwrap() = Some(child);
Ok(())
}
#[tauri::command]
async fn stop_sidecar(state: State<'_, SidecarState>) -> Result<(), String> {
if let Some(child) = state.child.lock().unwrap().take() {
child.kill().map_err(|e| e.to_string())?;
}
Ok(())
}
JavaScript からサイドカーを実行する
権限の設定
src-tauri/capabilities/default.json でシェルの実行権限を付与します。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [{ "name": "binaries/my-sidecar", "sidecar": true }]
}
]
}
基本的な実行
import { Command } from '@tauri-apps/plugin-shell';
async function runSidecar(): Promise<string> {
const command = Command.sidecar('binaries/my-sidecar');
const output = await command.execute();
if (output.code === 0) return output.stdout;
throw new Error(output.stderr);
}
引数を指定して実行
async function processFile(filePath: string): Promise<string> {
const command = Command.sidecar('binaries/processor', [
'--input', filePath, '--format', 'json'
]);
const output = await command.execute();
return output.stdout;
}
ストリーミング出力の処理
import { Command, Child } from '@tauri-apps/plugin-shell';
async function runWithStreaming(): Promise<Child> {
const command = Command.sidecar('binaries/long-task');
command.on('close', (data) => console.log(`Finished: ${data.code}`));
command.on('error', (error) => console.error(error));
command.stdout.on('data', (line) => console.log(line));
command.stderr.on('data', (line) => console.error(line));
return await command.spawn();
}
長時間実行するプロセスの管理
let serverProcess: Child | null = null;
async function startServer(): Promise<number> {
const command = Command.sidecar('binaries/api-server', ['--port', '8080']);
command.stdout.on('data', console.log);
serverProcess = await command.spawn();
return serverProcess.pid;
}
async function stopServer(): Promise<void> {
if (serverProcess) {
await serverProcess.kill();
serverProcess = null;
}
}
引数の検証
capabilities で引数の検証を設定します。
{
"identifier": "shell:allow-execute",
"allow": [{
"name": "binaries/my-sidecar",
"sidecar": true,
"args": [
"-o",
"--verbose",
{ "validator": "\\S+" }
]
}]
}
引数の種類:
- 静的文字列:完全一致が必要です(
-o、--verbose) - バリデーターオブジェクト:動的な値に対する正規表現パターンです
true:任意の引数を許可します(慎重に使用してください)
クロスプラットフォームでの考慮事項
プラットフォーム固有のバイナリのビルド
Rust のサイドカー:
cargo build --release --target x86_64-unknown-linux-gnu
cp target/x86_64-unknown-linux-gnu/release/my-tool \
src-tauri/binaries/my-tool-x86_64-unknown-linux-gnu
PyInstaller を使用した Python:
pyinstaller --onefile my_script.py
mv dist/my_script dist/my_script-x86_64-unknown-linux-gnu
プラットフォームに関する注意事項
Windows:
- 実行ファイルには
.exe拡張子が必要です - テキストファイルを処理する際は、改行コードを考慮してください
macOS:
- ユニバーサルバイナリ(Intel + Apple Silicon)には
lipoを使用します - 配布時にコード署名が必要になる場合があります
- Gatekeeper が署名されていないサイドカーをブロックする場合があります
Linux:
- バイナリに実行権限を付与します(
chmod +x) - glibc バージョンとの互換性を考慮してください
- 静的リンクにより依存関係の問題を軽減できます
完全な例
tauri.conf.json:
{
"productName": "My App",
"version": "1.0.0",
"identifier": "com.example.myapp",
"bundle": {
"externalBin": ["binaries/data-processor"]
}
}
capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [{
"name": "binaries/data-processor",
"sidecar": true,
"args": [
"--input", { "validator": "^[a-zA-Z0-9_\\-./]+$" },
"--output", { "validator": "^[a-zA-Z0-9_\\-./]+$" }
]
}]
}
]
}
src/main.rs:
use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn process_data(app: tauri::AppHandle, input: String, output: String) -> Result<String, String> {
let result = app.shell().sidecar("data-processor").map_err(|e| e.to_string())?
.args(["--input", &input, "--output", &output])
.output().await.map_err(|e| e.to_string())?;
if result.status.success() {
Ok(String::from_utf8_lossy(&result.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&result.stderr).to_string())
}
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![process_data])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
フロントエンド(App.tsx):
import { invoke } from '@tauri-apps/api/core';
function App() {
const handleProcess = async () => {
try {
const result = await invoke('process_data', {
input: '/path/to/input.txt',
output: '/path/to/output.txt'
});
console.log('Result:', result);
} catch (error) {
console.error('Error:', error);
}
};
return <button onClick={handleProcess}>Process Data</button>;
}
ベストプラクティス
- すべてのサイドカーパスを検証する:信頼できないパスをサイドカーに渡さないでください
- 引数バリデーターを使用する:capabilities で許可する引数を制限します
- エラーを適切に処理する:サイドカーが失敗したり、見つからなかったりする場合があります
- プロセスをクリーンアップする:アプリ終了時に起動したプロセスを停止します
- すべてのプラットフォームでテストする:バイナリの命名と実行方法はプラットフォームによって異なります
- バイナリサイズを考慮する:サイドカーによってバンドルサイズが大幅に増加します