@Pctg_x8です。
Deno 1.0の登場でRustとWeb関連技術の繋がりがより高まっていく中で、Electronライクな新しいアプリケーションフレームワークである「tauri」を見つけましたのでちょっと触ってみようと思います。
公式サイト: https://tauri.studio/
※この記事ではv0.9.2をベースに解説しています。 tauriはまだメジャーバージョンが1になっていないため、頻繁にAPIの変更が起こる可能性があります。
フロントはElectronと同じくWebViewですが、ベースの起動プログラム(Main Process)をRustで書くことができるものです。
ElectronではMain ProcessもJavaScript(Node.js)なので、例えば大量のデータを並行してバッと読むとか解析するとかの処理をさせようとすると マルチプロセス立てたりしないといけなくてちょっと面倒だな〜というところがありますが、tauriではMain ProcessはRustでネイティブアプリになるので この辺りの問題があらかた解決します。スレッドも任意のasync Executorも使いたい放題です。
類似プロダクトとしてGotronがありますが、あちらは裏でElectronを自動起動して、 nodejsとGoをWebsocketで繋ぐことでMain Processの一部の処理をGoで書けるような形になっています。 tauriはElectronを使用しておらず、Main Processは完全にRustプログラムで書かれるようになります。
tauri最大の特徴としては、プログラムが占めるバイトサイズがElectronと比較してパッケージ/メモリ共に圧倒的に小さいところでしょう。 公式の比較表は流石にちょっと極限すぎるかなーと思っていたんですが、実際に使ってみると確かにパッケージサイズ1/10、メモリ消費量1/2くらいになっています。
また、tauriもRust系プロダクトの例に漏れずCLIがしっかり作られています。 tauri自身に関係するようなところのみを最小限で自前で持って、それ以外はwebpackやVue CLIなどと連携することで、オプションとして覚えるべきことを最小限にし、 フロント部分は従来と変わらない構成でネイティブアプリのビルド/パッケージングを行うことができます。
tauriのアプリケーション構成にはいくつか種類があり(Patternsと呼ばれています)、 用途に応じて使い分けることができます。 いくつかあるんですが、Electronからの移植要件程度であればLockdownで事足りるかなと思います。
Lockdown Patternは公式サイトの図を見ていただければわかる通り、WebViewとRustプロセスがAPIを通してダイレクトに通信を行います。 雰囲気としてはElectronのIPCともっとも近く、使い方も似ているのでとっつきやすいと思います。
ElectronはどのプラットフォームでもChromiumですが、tauriはプラットフォームごとにWebViewのエンジンが異なります。
そのため、ブラウザ固有の罠を踏む可能性がElectronより高くなりますが、まあbabelとかでpolyfillすることも多いですし、tauri自身もいくつかpolyfillを注入してくれるのでそこまで困ることはないかなと思います。
Main ProcessとRenderer Process間の通信(というより、データパッシング)にはJSONを使用します。 より厳密には、
tauri.promisified
(非同期)や tauri.invoke
(同期)に渡されたオブジェクトを、Rust側 tauri::app::AppBuilder::invoke_handler
で指定されたハンドラではJSON形式の String
で受け取ることができる。tauri.promisified
を使用した場合
callback
と error
と名付けられたフィールドが追加されているため、これらの関数を必要に応じて呼び出す。Result
を返す処理があるなら、ヘルパー関数 tauri::execute_promise_sync
(スレッドプールを使用して並行処理するなら tauri::execute_promise
) を使用することで callback
と error
を組み合わせて良い感じに値を返すことができる。内部ではserde_jsonを使っているため、 serde::Serialize
を実装している型であればなんでも返すことができる。tauri::api::rpc::format_callback
を使用してコールバック処理を文字列にフォーマットし、 WebviewMut::dispatch
を使って適切にWebView内でevalしてあげるようにする。tauri.invoke
を使用した場合
tauri.promisified
があるのでそちらを使う。サンプルとしては月並みですが、触ってみたなのでとりあえずHello Worldでどれくらいtauri cliが扱いやすいかを紹介します。
まずは普通にWebフロントエンドのプロジェクトを作成します。addすべきパッケージは以下です。webpackを使うのでReactもとりあえず入れておきます。
$ yarn add react react-dom
$ yarn add --dev tauri webpack webpack-cli webpack-dev-server typescript ts-loader @types/react @types/react-dom
続いて、tauri関係の初期化を行います。
$ yarn tauri init
各設問には以下のように答えておきます。特に3番目と4番目が重要で、webpack-dev-serverの配信URLとwebpackのビルド出力先に合わせておく必要があります。 4番目は書いてある通り $(pwd)/src-tauriから見た相対ディレクトリ なので少し注意が必要です。
設問に答えた後はtauriの依存パッケージのインストールなどが始まり、しばらくすると完了します。 今回はとりあえずHello Worldなので、以下のようなファイルを用意しました。
// src/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
function App() {
return <h1>hello world</h1>;
}
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(<App />, document.body);
});
<!-- dist/index.html -->
<!DOCTYPE html>
<html>
<head>
<script src="./index.js"></script>
</head>
<body>
</body>
</html>
// webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
entry: path.resolve(__dirname, "src/index.tsx"),
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist")
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"]
},
module: {
rules: [
{ test: /.tsx?$/, use: "ts-loader" }
]
},
devServer: {
contentBase: "dist"
}
};
tsconfig.jsonは省略します。
上記準備ができたら、次は src-tauri/tauri.conf.json
を適切に書き換えます。initの時点である程度の雛形はできていますが、tauriの起動時に自動でwebpackによるビルドを走らせたいので、その設定を加えます。書き換えるのは2行だけです。
{
"build": {
"distDir": "../dist",
"devPath": "http://localhost:8080/",
"beforeDevCommand": "yarn webpack-dev-server",
"beforeBuildCommand": "yarn webpack"
},
build.beforeDevCommand
で tauri dev
時に起動するコマンドを、 build.beforeBuildCommand
で tauri build
時に起動するコマンドを指定します。特に凝ったことをしないのであれば、dev時はHMRを搭載したdevserverの起動コマンドを、build時には最終的なリリース用ビルドコマンドを指定します。
以下のコマンドで開発用にアプリを立ち上げることができます。
$ yarn tauri dev
今回はwebpack-dev-serverを使用しているため、src以下の更新に合わせてフロントが自動で更新されるようになっています。また、tauri自身もRustコードの変更を検知して自動でリビルドとアプリの再起動を行ってくれます。そのため、開発中は基本的にdevでアプリを立ち上げたままで行うことが多いです。
最後にビルドもしてみます。Electronのように特に追加のパッケージとかは必要なくて、以下のコマンドでパッケージを生成できます。
$ yarn tauri build
ビルド中に一時的にdmgファイルがマウントされて馴染みの画面が表示されますが、必要な処理が終わった後は自動でアンマウントされるので触らずに放置してください。
結果は src-tauri/target/release/bundle
以下に生成されます。 osx/*.app
がアプリ本体で dmg/*.dmg
がディスクイメージになっています。 今回は特にminifyなどはしていませんが、画像の通りappパッケージは4.7MBとかなり出力サイズが小さいことがわかります。
ちまちまと作っている社内用のゲームデータ閲覧アプリをElectronからtauriに移植してみました。
このアプリは、フロント部分がJavaScript(React)で、ElectronのMain Processを介してローカルに立てたデータ処理用のRustサーバと通信してデータを表示する形をとっていました。 今回tauriに移植することでRustサーバ部分をアプリ埋め込みにすることができ、TCPポート的な問題、サーバプロセス管理の問題を解決できた上、いざとなった場合に共有できる形にしやすくなりました。
もともと共有するようなものでもなかったのですが、今回試しにelectron-packagerでパッケージングしたものと比較してみたところ、以下のようになりました。
tauri自体はまだv0.9.2と発展途上のプラットフォームですが、CLIなど足回りがかなり揃っていてちょろっと使う分には面白かったです。
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。