Closed15

tauri 2.0素振り

tmtk75tmtk75

Tauri 2.0が出そうなのとrustをちょっとかじったのでなんとかなるんじゃないかと思って再度試してみる。

https://v2.tauri.app

環境はmacOS。

pnpmが好きなのでこれで作成。

~ % pnpm create tauri-app --rc
...
Your system is missing dependencies (or they do not exist in $PATH):
╭──────┬───────────────────────────────────────────────────────────────────╮
│ Rust │ Visit https://www.rust-lang.org/learn/get-started#installing-rust │
╰──────┴───────────────────────────────────────────────────────────────────╯
...

あれ、cargoがPATHにない。
以前試したときに入れたままのはずなんだが...いきなり出鼻をくじかれたなw

rust入れ直して再挑戦。
frontendをrustで書くってのはどういうことなんだと思って調べたら、ロジックをweb assemblyで書くってことなのね。

いや違うな。
LeptosってのはJSXみたいにHTMLライクにrustコード内に書けるのか。Rustのマクロすごいな。
https://nulab.com/ja/blog/nulab/rust-leptos-webassembly-kanban-app/

ただUIまで全部Rustでやるのはまだ違う気がするな。
ここはおとなしくTS選んでおこう。

pnpm, react, typescriptの構成。l

~ % pnpm create tauri-app --rc
✔ Project name · tauri-v2-app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun)
✔ Choose your package manager · pnpm
✔ Choose your UI template · React - (https://react.dev/)
✔ Choose your UI flavor · TypeScript

Template created! To get started run:
  cd tauri-v2-app
  pnpm install
  pnpm tauri android init
  pnpm tauri ios init

For Desktop development, run:
  pnpm tauri dev

For Android development, run:
  pnpm tauri android dev

For iOS development, run:
  pnpm tauri ios dev

できたディレクトリに入っておもむろにgit initして全部突っ込む。

pnpm tauri dev

するとcargoでmoduleをもりもりダウンロードし始めた。正味一分くらいでデスクトップアプリが起動した。command + Rでreloadができないのに気づく。

さてコードの中身を見てみる。

その前にbuildしてみるか。package.jsonにあまりtaskが書かれていないが、たぶんこれか。おもむろに実行してみる。identifierをcom.tauri.devから変えておかないと怒られるので適当に変えておく。

npx tauri build

ターミナルがFinderの許可くれ、とか出てきたからNoにしたらビルド失敗した。
デフォルトの設定だと.dmgを作って開こうとするのか。そこでFinderへのアクセス許可がいるんだな。

Error failed to bundle project: error running bundle_dmg.sh: `failed to run /Users/tomotaka/tauri-v2-app/src-tauri/target/release/bundle/dmg/bundle_dmg.sh`

とりあえずしばらく.dmgは必要ないのでskipしたい。
tauri.config.jsonのbundle.targetsをappにしたらskipした。
https://tauri.app/v1/api/config/#bundleconfig.targets

diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index cc579f4..ae5b43b 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -22,7 +22,7 @@
   },
   "bundle": {
     "active": true,
-    "targets": "all",
+    "targets": "app",
     "icon": [
       "icons/32x32.png",
       "icons/128x128.png",
...
    Finished 1 bundle at:
        .../tauri-v2-app/src-tauri/target/release/bundle/macos/tauri-v2-app.app

バイナリはここにできるみたい。

~/tauri-v2-app on main% file src-tauri/target/release/bundle/macos/tauri-v2-app.app/Contents/MacOS/tauri-v2-app
src-tauri/target/release/bundle/macos/tauri-v2-app.app/Contents/MacOS/tauri-v2-app: Mach-O 64-bit executable arm64

これでreleaseビルド作成までOK

さて改めて構成とコードを眺めてみる。これは今の構成で、将来的には変わる余地が多分にあるはず。
とはいえ、変わったとしても構成要素自体は基本的に同じである期待。

~/tauri-v2-app on main% tree
.
|-- README.md
|-- index.html
|-- package.json
|-- public
|   |-- tauri.svg
|   `-- vite.svg
|-- src
|   |-- App.css
|   |-- App.tsx
|   |-- assets
|   |   `-- react.svg
|   |-- main.tsx
|   `-- vite-env.d.ts
|-- src-tauri
|   |-- Cargo.toml
|   |-- build.rs
|   |-- capabilities
|   |   `-- default.json
|   |-- icons
|   |   |-- 128x128.png
|   |   |-- ...
|   |   `-- icon.png
|   |-- src
|   |   |-- lib.rs
|   |   `-- main.rs
|   `-- tauri.conf.json
|-- tsconfig.json
|-- tsconfig.node.json
`-- vite.config.ts

10 directories, 41 files
tmtk75tmtk75

srcがTypeScript用で、src-tauriがrustのmain processなのかな。
この辺を中心に見てみる。

  • tsconfig.json
  • tsconfig.node.json
  • vite.config.ts
  • src-tauri/tauri.conf.json
  • src-tauri/Cargo.tomol

tsconfig.*

tsconfig.*に特に目立ったところはなし。

vite.config.ts

pluginはreactのみ。clearScreenは初めてみた。serverは開発用サーバーの設定の模様。
TAURI_DEV_HOSTが設定されていると有効になるのか。1420と1421をそれぞれ固定で使う模様。

tauri devで起動して得られるTAURI prefixを環境変数は以下。TAURI_DEV_HOSTはないので、カスタマイズ用なのか。

TAURI_ENV_ARCH: 'aarch64',
TAURI_ENV_DEBUG: 'true',
TAURI_ENV_FAMILY: 'unix',
TAURI_ENV_PLATFORM: 'darwin',
TAURI_ENV_PLATFORM_VERSION: '14.3.1',
TAURI_ENV_TARGET_TRIPLE: 'aarch64-apple-darwin',
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST;

// https://vitejs.dev/config/
export default defineConfig(async () => ({
  plugins: [react()],

  // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
  //
  // 1. prevent vite from obscuring rust errors
  clearScreen: false,
  // 2. tauri expects a fixed port, fail if that port is not available
  server: {
    port: 1420,
    strictPort: true,
    host: host || false,
    hmr: host
      ? {
          protocol: "ws",
          host,
          port: 1421,
        }
      : undefined,
    watch: {
      // 3. tell vite to ignore watching `src-tauri`
      ignored: ["**/src-tauri/**"],
    },
  },
}));
tmtk75tmtk75

tauri devで起動してlocalhost:1420にブラウザでアクセスするとUIが見れる。
pnpm devviteだけ起動しているが、UIだけの開発は:1420でやれると。
tauriのサブコマンドがないのでdevとbuildくらい足しておく。

~/tauri-v2-app on main% cat package.json
{
  "name": "tauri-v2-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "tauri": "tauri"
  },
  "dependencies": {
    "@tauri-apps/api": "2.0.0-rc.0",
    "@tauri-apps/plugin-shell": "2.0.0-rc.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@tauri-apps/cli": "2.0.0-rc.2",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "typescript": "^5.5.4",
    "vite": "^5.4.0"
  }
}
diff --git a/package.json b/package.json
index 03c15d7..7347748 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,9 @@
     "dev": "vite",
     "build": "tsc && vite build",
     "preview": "vite preview",
-    "tauri": "tauri"
+    "tauri": "tauri",
+    "tauri:dev": "tauri dev",
+    "tauri:build": "tauri buildw"
   },
   "dependencies": {
     "@tauri-apps/api": "2.0.0-rc.0",
tmtk75tmtk75

src-tauri/tauri.conf.json

build.beforeDevCommandbeforeBuildCommandというのが目に入る。なんとなく用途は想像つく。devのほうは開発用のdev serverを起動するのか。つまりviteが叩かれると。

それ以外は特に目立ったところなしか。Electronでいうelectron-builderの設定ファイルみたいなファイルっぽい。公式に組み込まれているのは助かる。publishまで

{
  "productName": "tauri-v2-app",
  "version": "0.0.0",
  "identifier": "net.tmtk.dev",
  "build": {
    "beforeDevCommand": "pnpm dev",
    "devUrl": "http://localhost:1420",
    "beforeBuildCommand": "pnpm build",
    "frontendDist": "../dist"
  },
  "app": {
    "windows": [
      {
        "title": "tauri-v2-app",
        "width": 800,
        "height": 600
      }
    ],
    "security": {
      "csp": null
    }
  },
  "bundle": {
    "active": true,
    "targets": "app",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}

tauriのサブコマンドを見るとsignerとかまであるから、publishまでをサポートしていると思っていいだろう。

~/tauri-v2-app on main% npx tauri -h
Command line interface for building Tauri apps

Usage: npm run npx [OPTIONS] <COMMAND>

Commands:
  init         Initialize a Tauri project in an existing directory
  dev          Run your app in development mode
  build        Build your app in release mode and generate bundles and installers
  bundle       Generate bundles and installers for your app (already built by `tauri build`)
  android      Android commands
  ios          iOS commands
  migrate      Migrate from v1 to v2
  info         Show a concise list of information about the environment, Rust, Node.js and their versions as well as a few relevant project configurations
  add          Add a tauri plugin to the project
  plugin       Manage or create Tauri plugins
  icon         Generate various icons for all major platforms
  signer       Generate signing keys for Tauri updater or sign files
  completions  Generate Tauri CLI shell completions for Bash, Zsh, PowerShell or Fish
  permission   Manage or create permissions for your app or plugin
  capability   Manage or create capabilities for your app
  help         Print this message or the help of the given subcommand(s)

Options:
...
tmtk75tmtk75

src-tauri/Cargo.toml

設定ファイルの確認最後はCargo.toml。TOMLってまだ慣れてなくてぱっと見構造が理解できないときがあるんだけど、これは[[...]]とかは使われてなくてまあ分かる。

Rustにはまだあまり慣れてないのでこの辺をきっかけに調べていく。

[package]
name = "tauri-v2-app"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "tauri_v2_app_lib"
crate-type = ["lib", "cdylib", "staticlib"]

[build-dependencies]
tauri-build = { version = "2.0.0-rc", features = [] }

[dependencies]
tauri = { version = "2.0.0-rc", features = [] }
tauri-plugin-shell = "2.0.0-rc"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tmtk75tmtk75

entry pointはsrc-tauri/src/lib.rsrunの模様。
他にはGreetボタンが押されたときに呼ばれるハンドラが定義されている。

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    println!("Hello from Tauri!");
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

TypeScriptからはinvokeを介して呼んでる。

# App.tsx
import { invoke } from "@tauri-apps/api/core";
...
  async function greet() {
    // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
    setGreetMsg(await invoke("greet", { name }));
  }

invoke_handlerで別のハンドラが足せるのかと思って足してみたが、400エラーがdevtoolsのコンソールに表示される。helloをgreetの前に登録したらgreetは動いたので後勝ちで上書きしている模様。複数のハンドラ登録するにはどうするか後で調べる。

あと当たり前だがTauriなので開くコンソールはSafariのdevtoolsが開いて少し新鮮だった。

diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 291bed7..9ef1f17 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -4,11 +4,18 @@ fn greet(name: &str) -> String {
     format!("Hello, {}! You've been greeted from Rust!", name)
 }

+#[tauri::command]
+fn hello(_name: &str) {
+    println!("Hello from Rust!");
+}
+
 #[cfg_attr(mobile, tauri::mobile_entry_point)]
 pub fn run() {
+    // println!("Hello from Tauri!");
     tauri::Builder::default()
         .plugin(tauri_plugin_shell::init())
         .invoke_handler(tauri::generate_handler![greet])
+        .invoke_handler(tauri::generate_handler![hello])
         .run(tauri::generate_context!())
         .expect("error while running tauri application");
 }
tmtk75tmtk75

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command

とあるので読んでみる。v1だけど。

こんだけだったw そうか、配列だもんな。

diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 9ef1f17..0287005 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -14,8 +14,7 @@ pub fn run() {
     // println!("Hello from Tauri!");
     tauri::Builder::default()
         .plugin(tauri_plugin_shell::init())
-        .invoke_handler(tauri::generate_handler![greet])
-        .invoke_handler(tauri::generate_handler![hello])
+        .invoke_handler(tauri::generate_handler![greet, hello])
         .run(tauri::generate_context!())
         .expect("error while running tauri application");
 }
tmtk75tmtk75

ハンドラの引数と戻り値を複雑にしてみる。オブジェクトや配列を渡す。
handler関数の引数の名前とinvokeに与えるobjectのfield名が対応するみたいで、どうやってんだこれってなってる。tauri::commandがよしなにやってるんだろうな。
戻り値はserializableな値なら返せそう。

diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 9ef1f17..62907e5 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,12 +1,26 @@
+use serde::Serialize;
...
+#[derive(Serialize)]
+struct User {
+    name: String,
+    age: u32,
+}
+
 #[tauri::command]
-fn hello(_name: &str) {
-    println!("Hello from Rust!");
+fn hello(name: &str, age: u32, enabled: bool, arr: Vec<i32>, obj: serde_json::Value) -> User {
+    println!(
+        "Hello from Rust!, {}! {}! {}! {:#?}! {}!",
+        name, age, enabled, arr, obj
+    );
+    User {
+        name: "Alice".to_string(),
+        age: 30,
+    }
 }
diff --git a/src/App.tsx b/src/App.tsx
index 86aa4b1..c1d2029 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -45,6 +45,23 @@ function App() {
         <button type="submit">Greet</button>
       </form>

+      <form
+        className="row"
+        onSubmit={async (e) => {
+          e.preventDefault();
+          const v = await invoke("hello", {
+            age: 29,
+            name: "John",
+            enabled: true,
+            arr: [1, 2, 3],
+            obj: { a: 1, b: 2 },
+          });
+          console.log(v);
+        }}
+      >
+        <button type="submit">Hello</button>
+      </form>
+
       <p>{greetMsg}</p>
     </div>
   );
tmtk75tmtk75

remix

remixをSPAモードで入れてみる。とりあえずrouting用。
appDirectoryはtauriが生成したsrcをそのまま使うようにした。
src/routes以下は割愛。

~/tauri-v2-app on main% pnpm i -D @remix-run/react @remix-run/dev vite-tsconfig-paths
...
~/tauri-v2-app on remix-config-js% git diff main -- package.json vite.config.ts
diff --git a/package.json b/package.json
index 36ccc6d..797e268 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,8 @@
     "react-dom": "^18.3.1"
   },
   "devDependencies": {
+    "@remix-run/dev": "^2.11.1",
+    "@remix-run/react": "^2.11.1",
     "@tauri-apps/cli": "2.0.0-rc.2",
     "@types/react": "^18.3.3",
     "@types/react-dom": "^18.3.0",
@@ -26,6 +28,7 @@
     "postcss": "^8.4.41",
     "tailwindcss": "^3.4.9",
     "typescript": "^5.5.4",
-    "vite": "^5.4.0"
+    "vite": "^5.4.0",
+    "vite-tsconfig-paths": "^5.0.1"
   }
 }
diff --git a/vite.config.ts b/vite.config.ts
index f74d1b2..a8388ab 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,12 +1,18 @@
 import { defineConfig } from "vite";
 import react from "@vitejs/plugin-react";
+import { vitePlugin as remix } from "@remix-run/dev";
+import tsconfigPaths from "vite-tsconfig-paths";

 // @ts-expect-error process is a nodejs global
 const host = process.env.TAURI_DEV_HOST;

 // https://vitejs.dev/config/
 export default defineConfig(async () => ({
-  plugins: [react()],
+  plugins: [
+    // react(),
+    remix({ ssr: false, appDirectory: "./src" }),
+    tsconfigPaths(),
+  ],

   // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
   //
tmtk75tmtk75

UI周り〜Rustとの通信周りは大体素振り完了。
あとはRustに慣れるだけだな。

Rust側のTauriのAPIを調べてみるかな。その前にsecurity modelとか把握する必要がありそう。
この辺を読むか。
https://v2.tauri.app/concept/

tmtk75tmtk75

この時点でリリースビルドを試そうとしてtauri buildしたらindex.htmlが読めないという表示に。
remixにしたので、buildもremixを使う必要があった。生成されたファイルの参照先 frontendDist も変更。
スタイルシートの効きがなんかdevとrelease buildで違う気がするけど、まあいいか。

diff --git a/package.json b/package.json
index 198eaaf..85803d7 100644
--- a/package.json
+++ b/package.json
@@ -4,8 +4,8 @@
   "version": "0.0.0",
   "type": "module",
   "scripts": {
-    "dev": "vite",
-    "build": "tsc && vite build",
+    "dev": "remix vite:dev",
+    "build": "remix vite:build",
     "preview": "vite preview",
     "tauri": "tauri",
     "tauri:dev": "tauri dev",
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 9a4354d..d93cd5a 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -6,7 +6,7 @@
     "beforeDevCommand": "pnpm dev",
     "devUrl": "http://localhost:1420",
     "beforeBuildCommand": "pnpm build",
-    "frontendDist": "../dist"
+    "frontendDist": "../build/client"
   },
   "app": {
     "windows": [
tmtk75tmtk75

スタイルシートの効きがなんかdevとrelease buildで違う気がするけど、まあいいか。

tailwindは効いてるみたい。
tauriが生成したApp.cssが効いてないみたいだな、というかむしろdevで効いてるほうがおかしいように見えてきた。

そうね、効いてないページで明示的にimport "../App.css"とか読んでやるとスタイルが変わるから、devで効いてるほうがこれはおかしいな。

まあデフォルトのApp.cssとか実際は消すだろうからいいか。

このスクラップは2024/08/11にクローズされました