Open7

Nuxt3+Tauriでデスクトップアプリを作る

Crysta1221Crysta1221

はじめに

Electronでデスクトップアプリを作ろうとしたが、vue-routerによるルーターが機能しないうえに起動に時間がかかるためTauriへの移行した際に詰まったところ、やり方等を載せている。
また、Tauriのプラグインを用いてウィンドウ配置を固定するようにもした。

Tauriとは

TauriはRustベースのデスクトップアプリを作成できるオープンソースのフレームワーク。
軽量でElectronと比べインストーラのサイズ、使用メモリが少ないという開発者にとってはありがたい利点がある。また、Rust言語がベースになっているのでセキュリティ的にも堅牢なアプリを作ることができる。
Electronとの違いを軽くまとめてみた。なお、バンドルサイズなどはTauri公式が出しているものを引用している。

比較内容 Electron Tauri
言語 JavaScript(Node.js) Rust
レンダリング方法 Chromium WebView2
バンドルサイズ 52.1MB 3.1MB
メモリ使用量 462MB 180MB
起動時間 0.8秒 0.39秒
Crysta1221Crysta1221

Tauriを導入

タイトルにもあるように今回はTauriにNuxt3を導入してアプリケーションを作成していく。Tauriは現在v1とベータ版のv2があり、v2ではNuxt3に関するドキュメントがあるがv1には公式のものはないので追って説明する。
まずは、肝心のTauriのセットアップをしていく。セットアップはTauri ガイドに基づきながら進める。

VS C++ Build Toolのセットアップ

Tauriの動作に必要なRustをインストールしていくのだが、Visual Studio C++ ビルドツールが必須になる。そこで、Visual Studio 2022をダウンロードしてインストールする。
https://visualstudio.microsoft.com/ja/downloads/
Visual Studioには3つのエディションがあるが個人用途であればCommunityで問題ない。学生でAzure Portalにアクセスできるのであれば無料でEnterpriseライセンスがもらえるのでEnterpriseでもよい。
インストーラをダウンロードして実行すると、画像のようなものが表示されるはずだ。
「C++によるデスクトップ開発」にチェックを入れ、右のオプションに「MSVC ... - VS2022 C++ x64/86ビルドツール」と今使用しているWindowsのSDKにチェックが入っていればOK。(Windows 11ならWindows 11 SDKにチェックが入っている必要がある。Windows 10ならWindows 10 SDKの最新版が無難。)
インストール方法はダウンロードしながらでも一括ダウンロードしてからでもどちらでもよい。これでBuild Toolのセットアップは完了。

WebView2のセットアップ

次にTauriの動作に必要なWebView2をセットアップしていくが、Windows 10 1803以降であればアップデート時にインストールされているのでここは必要ない。
もし、1803より前の環境でセットアップを行っている場合はMicrosoftからwebview2をダウンロードできる。
https://developer.microsoft.com/en-us/microsoft-edge/webview2/?form=MA13LH#download-section

Rustのセットアップ

次にRustupというRustのインストーラをダウンロードしてインストールしていく。
https://www.rust-lang.org/tools/install
wingetが使える場合はwinget経由でもインストールすることができる。

winget install --id Rustlang.Rustup
Crysta1221Crysta1221

プロジェクトを作成する

次に、Tauri + Nuxt3のプロジェクトを作っていく。既にNode.jsは導入されているものとして進めていくが導入できていない場合はNode.jsのページからLTS版をダウンロードしてインストールするとよい。(バージョンはv18以上をおすすめする)

Tauri側のセットアップ

まずはTauriのプロジェクトを作っていく。

npm create tauri@latest
✔ Project name · tauri-app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun)
✔ Choose your package manager · npm
✔ Choose your UI template · Vanilla
✔ Choose your UI flavor · TypeScript

Template created! To get started run:
  cd tauri-app
  npm install
  npm run tauri dev
  • Project name
    プロジェクト名。同時にフォルダ名になる。
  • Frontend Language
    今回はNuxt.jsを用いるので「Typescript/Javascript」を選択。
  • package manager
    お好みで。ここでは一般的なnpmを用いる。
  • UI template
    Tauri v1ではNuxt用のテンプレートがないので、Vanillaを用いる。
  • UI flavor
    よく使う方を選ぶ。ここではTypescriptで進める。

プロジェクトが作成されたら、次のようなフォルダ構造になっているはず。

.
├── node_modules/
├── src/
│   ├── assets
│   │   ├── tauri.svg
│   │   ├── typescript.svg
│   │   └── vite.svg
│   ├── main.ts
│   └── styles.css
├── src-tauri/
│   ├── icons/
│   ├── src/
│   │   └── main.rs
│   ├── Cargo.toml
│   ├── build.rs
│   └── tauri.conf.json
├── README.md
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

Nuxt3のセットアップ

次にTauriプロジェクトにNuxt3を導入する。ここでは先ほど作成したプロジェクトtauri-appを現在のディレクトリ(ルートディレクトリ)として進める。
まずはsrcフォルダ、node_modulesフォルダ、tsconfig.jsonindex.htmlvite.config.tsは統合するのに不要なファイルとなるので削除する。

この段階でのフォルダ構造はこちら
.
├── src-tauri/
│   ├── icons/
│   ├── src/
│   │   └── main.rs
│   ├── Cargo.toml
│   ├── build.rs
│   └── tauri.conf.json
├── README.md
└── package.json

次に、Nuxt3のプロジェクトを作成する。

npx nuxi@latest init src

フォルダ名はsrcにする。プロジェクトを作成したらsrcフォルダに移動して、ファイルの整理を行う。まず、nuxt.config.tstsconfig.jsonはルートディレクトリに移動させる。
次に、ルートディレクトリのpackage.jsonを次のように書き加える。具体的に書き加える、削除するところはハイライトで表示している。

package.json
{
  "name": "tauri-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "tauri": "tauri",
-   "dev": "vite",
-   "build": "tsc && vite build",
-   "preview": "vite preview",
+  "tauri:dev": "tauri dev",
+  "build": "nuxt build",
+  "dev": "nuxt dev",
+  "generate": "nuxt generate",
+  "preview": "nuxt preview",
+  "postinstall": "nuxt prepare"
  },
  "dependencies": {
    "@tauri-apps/api": "^1",
+  "nuxt": "^3.11.2",
+  "vue": "^3.4.21",
+  "vue-router": "^4.3.0"
  },
  "devDependencies": {
    "@tauri-apps/cli": "^1",
    "typescript": "^5.0.2"
  }
}

次に.gitignoreも同様に書き換える。ルートディレクトリの.gitignoreを開き、次のように書き換える。

.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
.nuxt
.nitro
.cache
.output
.env

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

書き換えが終わったら、srcフォルダ内の次のファイル・フォルダを削除する。

  • package.json
  • package-lock.json(yarn.lock)
  • node_modules/
  • public/
  • .gitignore

(/がついているのはフォルダ)
これで、Nuxt3のプロジェクトのセットアップが完了した。

最終的なフォルダ構造
.
├── src/
│   ├── assets/
│   │   └── css/
│   │       └── tailwind.css
│   ├── components/
│   ├── pages/
│   │   └── index.vue
│   ├── plugins/
│   ├── public/
│   ├── server/
│   └── app.vue
├── src-tauri/
│   ├── capabilities/
│   │   └── main.json
│   ├── icons/
│   │   ├── icon-512.png
│   │   └── icon.ico
│   ├── src/
│   │   └── main.rs
│   ├── target/
│   ├── .gitignore
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.rs
│   └── tauri.conf.json
├── .gitignore
├── README.md
├── nuxt.config.ts
├── package-lock.json
├── package.json
├── tailwind.config.ts
└── tsconfig.json

Tauriの設定変更

このままでは、Tauriが正しくNuxt3プロジェクトを読み込んでくれないので設定を変える。
src-tauriフォルダに移動し、tauri.config.jsonを開く。

tauri.config.json
{
  "build": {
    "beforeDevCommand": "npm run dev",
-   "beforeBuildCommand": "npm run build",
+   "beforeBuildCommand": "npm run generate",
-   "devPath": "http://localhost:1420",
+   "devPath": "http://localhost:3000",
    "distDir": "../dist",
    "withGlobalTauri": true
  },
  "package": {
    "productName": "tauri-app",
    "version": "0.0.0"
  },
  "tauri": {
    "allowlist": {
      "all": false,
      "shell": {
        "all": false,
        "open": true
      }
    },
    "windows": [
      {
        "title": "tauri-app",
        "width": 800,
        "height": 600
      }
    ],
    "security": {
      "csp": null
    },
    "bundle": {
      "active": true,
      "targets": "all",
      // identifierを初期から変更しないとビルドに失敗する
-     "identifier": "dev.tauri",
+    "identifier: "your-domain",
      // ウィンドウのアイコンを変更する場合はiconsフォルダにpng、icoファイルを入れてここで設定
      "icon": ["icons/icon-512.png", "icons/icon.ico"]
    }
  }
}

次に、ルートディレクトリのnuxt.config.tsを開き、次のコードを追加する。

nuxt.config.ts
export default defineNuxtConfig({
+ srcDir: "src/",
  app: {
    head: {
      charset: "utf-16",
      viewport: "width=device-width",
      link: [{ rel: "icon", type: "image/x-icon", href: "/icon.png" }],
      htmlAttrs: {
        lang: "ja",
      },
      meta: [
        { property: "og:image", content: "/ogimage.png" },
        { property: "theme-color", content: "#1d94bb" },
      ],
    },
  },
})

これで、Tauri上でNuxt3が動作するようになった。

Crysta1221Crysta1221

Tauriを起動する

ここまではTauri上にNuxt3を構築していった。最終的に起動するかどうかを確認する。
ルートディレクトリに戻って、

npm run tauri:dev

を実行する。Tauriのウィンドウが開いたら成功。

Crysta1221Crysta1221

Tauriのウィンドウ配置を変更する

デフォルトではウィンドウ配置がランダムに行われ、ウィンドウ内コンテンツが見づらくなる恐れがあるので起動時にウィンドウを中央に配置するようにする。
tauri-plugin-positionerというプラグインを用いてこれを行う。
https://github.com/tauri-apps/tauri-plugin-positioner
注意として、TauriのページにあるTauri Plugin PositionerはTauri v2(ベータ)にしか対応していないため情報を調べる際は除外することをお勧めする。
Githubにある通りに進めると、positionerが動作しないのでここに修正版を載せておく。
まずは、src-tauriフォルダ内のCargo.tomlを開き、次のテキストを追加する。

cargo.toml
[package]
name = "tauri-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

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

[dependencies]
tauri = { version = "1", features = ["shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 次の一行を追加
tauri-plugin-positioner = { version = "1.0", features = ["system-tray"] }

[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]

次に、src-tauri/src内にあるmain.rsを開き、次のように書き換える。このコードはGithub Copilot(Chat GPT)に一部書き換えをお願いしたが問題なく実行できた。

main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::Manager;
use tauri_plugin_positioner::{WindowExt, Position};

#[tauri::command]
fn main() {
  let app = tauri::Builder::default()
    .plugin(tauri_plugin_positioner::init())
    // This is required to get tray-relative positions to work
    .on_system_tray_event(|app, event| {
        tauri_plugin_positioner::on_tray_event(app, &event);
    })
    .build(tauri::generate_context!())
    .expect("error while building tauri application");

  let win = app.get_window("main").unwrap();
  let _ = win.move_window(Position::Center);

  app.run(|_app_handle, _event| {});
}

この場合は、アプリ起動時・システムトレイ復帰時に中央にウィンドウを移動して表示させることができる。別の場所(左上など)に表示させたい場合は、let _ = win.move_window(Position::Center);のPosition::の後を次のサイトのVariantsから選んで書き直す。
https://docs.rs/tauri-plugin-positioner/latest/tauri_plugin_positioner/enum.Position.html
今回は、Rust側で管理する方法にしたが、Nuxt3側からの変更もできるはず。

Crysta1221Crysta1221

デスクトップ開発

あとは、tauri:devを起動してNuxt3のvueファイルを編集するだけである。ホットリロード機能がついているので保存するとすぐにウィンドウに反映される。
また、ファイルを開くなどの動作を行う場合は、src-tauri/srcにrsファイルを作り、IPC Tunnelを作ってフロントエンド側と同期する。その際は、tauri.config.jsonのallowlistに必要な権限を入れておく必要がある。

Crysta1221Crysta1221

参考文献

  • TauriにVueフレームワークのNuxt3を導入するまで(ついでにTailwindcssとRome) on Qiita

https://qiita.com/GreenTea25/items/3a6311977d0e4d4d046d

  • Tauri window position 'Tray position not set' on StackOverflow

https://stackoverflow.com/questions/77188060/tauri-window-position-tray-position-not-set

  • Tauri Plugin Positioner(ここに書かれていることをそのまま使えないので注意)

https://github.com/tauri-apps/tauri-plugin-positioner