TauriでRailsをラップする
4 min read

最近はTauriを使ってデスクトップアプリケーションを作るようになった。
プログラミングは楽しい。
直近のプロジェクトではRails + Go + Rustを組み合わせて、できるだけRailsから離れるような構成を試していた。いわゆる「脱Rails」だ。
けれど、結局またRailsに戻ってきている。
1ヶ月近くBunとTypeScriptばかり書いていたので、少し不安もあったが、それは杞憂だった。むしろRailsは相変わらず快適だった。
この時代、AIにプロンプトを投げれば目的のコードの6割くらいはすぐに出てくる。効率だけを考えれば、それを前提に量産していくのが正しいのだと思う。
ただ、自分はどうしてもそれで終われない。
AIに対して「こうじゃない」「ここは違う」と言い続けて、最終的に自分の思う100%に近づけたくなる。ブレインストーミングの相手としては本当に優秀で、設計の相談にもいくらでも付き合ってくれる。
その代わり、時間はかかる。
先月、モデルを軽くしてコードを書いていたときは、正直かなりつらかった。理解が噛み合わず、何度もやり直すことになり、頭が痛くなることもあった。
今は再び上位モデルに戻して、ようやく落ち着いている。
ただ、Railsで書いている限り、基本的には出力先はブラウザになる。
React Nativeを試したこともあったが、この前提はあまり変わらなかった。iOSやAndroidのことを考え始めると、結局どこかで止まってしまう。
Webサーバーはいくらでも作れるのに、「デスクトップを拡張する」という方向にはあまり進めていない。
ゲームを作っていた時期もあったが、それはそれで閉じた世界の話だった。もう少し実用的なものを作りたいという気持ちがある。
そんな中で、最近触っていたTauriからひとつ発想が出てきた。
普通、Tauriを使うときはViteやReact、Svelteなどを組み合わせる。フロントエンドをしっかり作り込んで、それをネイティブアプリとして配布する。
でも、別にそこまでしなくてもいいのではないかと思った。
Tauriを「UIを描画するためのフレームワーク」としてではなく、「WebViewとOS機能の橋渡し」として使う。
つまり、RailsはそのままWebアプリとして動かしつつ、Tauriはそれを表示しながら通知やOS連携だけを担当させる。
ブラウザのWeb Notificationではなく、OSの通知を使う。そのためのラッパーとしてTauriを使う、という発想だ。1
しかもTauriは src-tauri というディレクトリで管理できるので、既存のRailsアプリケーションにもそのまま載せやすい。
WebViewであればTurboもそのまま使えるし、CORSの設定も不要になる。
ほとんど摩擦なくTauriを取り込めたのが、今回の一番の発見だった。
試しにこんなコードを書いてみた。
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
send() {
if (!window.__TAURI__) {
alert("Tauri環境ではありません")
return
}
const {
isPermissionGranted,
requestPermission,
sendNotification
} = window.__TAURI__.notification
isPermissionGranted().then((granted) => {
if (granted) {
sendNotification({ title: "My App", body: "Hello World!" })
} else {
requestPermission().then((permission) => {
if (permission === "granted") {
sendNotification({ title: "My App", body: "Hello World!" })
}
})
}
})
}
}
これで普通に通知が出るはずだった。
しかし、実際に動かしてみると、音だけが鳴って通知は表示されなかった。
調べてみると、これは既知の問題だった。
tauri-apps/plugins-workspace[bug] no notifications in gnome 46+ (eg fedora 41 and ubuntu 24.04)GNOME 46以降では通知の扱いが変わっており、Tauriの通知が表示されないケースがあるらしい。
.desktop ファイルを用意したり、identifierを揃えたりといったことも試したが、状況は変わらなかった。
DBusには通知自体は届いているようなので、TauriというよりはGNOME側の仕様に引っかかっているように見える。
#[tauri::command]
fn send_notification(title: String, body: String) {
let identifier = "com.tauri.dev";
let _ = std::process::Command::new("notify-send")
.arg(format!("--app-name={}", identifier))
.arg(format!("--hint=string:desktop-entry:{}", identifier))
.arg(&title)
.arg(&body)
.spawn();
}
最終的には、Rust側から notify-send を呼ぶ形に落ち着いた。
少し拍子抜けではあるが、少なくとも目的は達成できた。
出だしからつまずいた形にはなったが、この方向性自体は悪くないと感じている。
以前書いた「デスクトップは重要」という話でやりたかったことに、少し近づいた気がする。
Webアプリケーションはもう十分に書ける。だからこそ、その外側にどう広げるかをもう少し考えてみたい。