Wails を始めてみた

# はじめに

Windows のデスクトップアプリを作るのに自分は、以前は Delphi を使っていて、仕事は WinForms＋VB.NET を使っていて、趣味は Electron も使っています。新たにアプリを作り始めるのに、別の開発手段も試してみようと思いました。

# Wails とは

Wails は、Go 言語とウェブ技術を使用して、デスクトップアプリの構築を可能にします。「Go の力によって、Electron が軽量かつ高速になったようなもの」と考えるとよいでしょう。 
（[イントロダクション | Wails](https://wails.io/ja/docs/introduction)）

アプリ全体のうち Go の部分は、アプリのコードとランタイムライブラリで構成されています。 フロントエンドは webkit ウィンドウであり、フロンドエンドアセットをウィンドウ上に表示します。Go のメソッドはフロントエンドにバインドされ、ローカルの JavaScript メソッドであるかのようにフロントエンドから呼び出すことができます。
（[どうやって動いているの? | Wails](https://wails.io/ja/docs/howdoesitwork)）

![説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/82fc60ca-2c4e-20db-2909-715d390f4d5b.png)

いわゆる「OS のネイティブアプリ」は、OS 上で実行できるバイナリファイルで、OS の機能を呼出して動作します。開発環境や開発言語が OS ごとに用意され、その OS 用の実行ファイルが作成されます。同じソースコードのプログラムを異なる OS で動作できません。
これに対して、いわゆる「ウェブアプリ」は、HTML と JavaScript で書かれたプログラムが、ウェブブラウザに読込され実行されます。同じ動作するブラウザがあれば同じウェブアプリのプログラムが異なる OS で動作します。ウェブブラウザが呼出できない OS の機能は利用できません。
「Electron」を使ったアプリは、ウェブアプリ＋ウェブブラウザが実行ファイルになってネイティブアプリのように OS 上で動作します。さらに Node.js アプリを内包できて、ウェブブラウザで呼出できない OS の機能も呼出できるようになっています。ウェブブラウザおよび Node.js エンジンを OS ごとに切替して実行ファイルを作成することで、同じ HTML＋JavaScript のプログラムで異なる OS で動作するアプリを作成できます。ウェブブラウザおよび Node.js エンジンを内包するため、OS ネイティブアプリに比べて実行ファイルが大きくなってしまいます。
「Tauri」アプリは、Electron の Node.js アプリの部分が、Rust アプリになっています。また Electron アプリはブラウザ機能を内容しているのに対して、OS が持つブラウザ機能を呼出します。このため、Electron アプリに比べて実行ファイルが小さくなります。
「Wails」アプリは、Tauri の Rust アプリの部分が、Go アプリになっています。

# Wails を始めてみた

* Wails 2.9

## 開発および実行できる環境

Wails は、以下の環境で開発できて、作成したアプリが実行できます。

* Windows 10 および 11 (x64 および ARM64)
* MacOS 10.13 以降 (x64) 、MacOS 11.0 以降 (ARM64)
* Linux (x64 および ARM64)

## 開発環境を準備する

以下の手順で開発環境を準備します。

[インストール | Wails](https://wails.io/ja/docs/gettingstarted/installation/)

### Go 環境をインストールする

まず Go 環境をインストールします。

[Download and install - The Go Programming Language](https://go.dev/doc/install)

### npm をインストールする

続いて npm をインストールします。

[Node.js — Download Node.js®](https://nodejs.org/en/download)

### WebView2 ランタイムをインストールする（Windows 環境の場合）

Windows 11 は WebView2 ランタイムを標準で持っています。そうでなければ Windows 環境に WebView2 ランタイムをインストールします。

[Microsoft Edge WebView2 | Microsoft Edge Developer](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/)

### xcode コマンドラインツールをインストールする（macOS 環境の場合）

macOS 環境に xcode コマンドラインツールをインストールします。

[XcodeのCommand Line Toolsのみインストールする #Mac - Qiita](https://qiita.com/SayCheeese/items/1b9db660917049bbad64) 

### Wails 開発環境をインストールする 

`go install` を使って Wails 開発環境をインストールします。

## プロジェクトを新規作成する

プロジェクトを作成したいフォルダに移動してコマンドを実行します。

```shell
wails init -n アプリ名 -t フレームワーク名
```

アプリ名で指定された名前のフォルダが作成されてソースコード一式がセットされます。

[プロジェクトの開始 | Wails](https://wails.io/ja/docs/gettingstarted/firstproject)

例えば

```shell
wails init -n HelloWails -t vanilla
```

## 新規プロジェクトの内容を確認する

アプリのベースになるプロジェクトは簡単に作成できましたが、ファイルが幾つもあります。どのファイルを変更すればいいのか。それを知るために、プロジェクトに含まれるファイルを調べてみました。

[どうやって動いているの? | Wails](https://wails.io/ja/docs/howdoesitwork)

### main.go

エントリポイントは、`main.go` にあります。

```go:main.go
package main

(中略)

func main() {
    // Create an instance of the app structure
    app := NewApp()

    // Create application with options
    err := wails.Run(&options.App{
        Title: "HelloWails",
(中略)
        OnStartup: app.startup,
        Bind: []interface{}{
            app,
        },
    })

    if err != nil {
        println("Error:", err.Error())
    }
```

アプリが起動するときの状態を指定できそうです。

### app.go

同じパッケージ `main` が `main.go` と別に `app.go` に分けて記述されています。

```go:app.go
package main

(中略)

// App struct
type App struct {
    ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
    return &App{}
}

// startup is called when the app starts. The context is saved // so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
    a.ctx = ctx
}
```

ここまでは手を入れる余地なさそうです。`main.go` に記述してあっていいように思いました。

```go:app.go
(前略)

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
    return fmt.Sprintf("Hello %s, It's show time!", name)
}
```

ここからアプリの独自のコードのようです。↑

### frontend/wailsjs/go/main/App.js

上記の `app.go` のコードがフロントエンドのプログラムで使用できるよう設定されます。

```js:App.js
export function Greet(arg1) {
  return window['go']['main']['App']['Greet'](arg1);
}
```
```ts:App.d.ts
export function Greet(arg1:string):Promise<string>;
```

この設定は `wails generate module` すると `app.go` の内容を読んで自動で作成されます。

### frontend/index.html

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
    <title>HelloWails</title>
</head>
<body>
<div id="app"></div>
<script src="./src/main.js" type="module"></script>
</body>
</html>
```

フレームワーク `Vanilla JS` を指定しましたが、HTML ファイルは ID `app` の `div` タグだけ用意するスタイルになっています。他のフレームワークを指定しても同じ内容でした。

### frontend/src/main.js

```js:main.js
import {Greet} from '../wailsjs/go/main/App';

(中略)

    // Call App.Greet(name)
    try {
        Greet(name)
            .then((result) => {
                // Update result with data back from App.Greet()
                resultElement.innerText = result;
            })
            .catch((err) => {
                console.error(err);
            });
    } catch (err) {
        console.error(err);
    }
};
```

上記の `app.go` のコードがフロントエンドのプログラムで使用できています。↑

## アプリのアイコンを変更する

アプリのアイコンを変更できます。

[Wailsでアプリをビルドするときにアイコンを変更する方法 - at backyard](https://shinshin86.hateblo.jp/entry/2022/12/04/000000)

## アプリを開発モードで起動する

プロジェクトのフォルダに移動してコマンドを実行します。

```shell
wails dev
```

開発モードの実行ファイルが作成されて起動します。

[アプリの開発 | Wails](https://wails.io/ja/docs/gettingstarted/development)

## アプリをビルド（コンパイル）する

プロジェクトのフォルダに移動してコマンドを実行します。

```shell
wails build
```

配布するための実行ファイルが、プロジェクトのフォルダ内の `build/bin` に作成されます。

[プロジェクトのコンパイル | Wails](https://wails.io/ja/docs/gettingstarted/building)

# VS Code で開発する

上記のコードをエディタで編集してコマンド実行すればアプリが作成できますが、コードの編集やデバッグは IDE（統合開発環境）を使いたいものです。

VS Code (Visual Studio Code) を使うことにします。

## VS Code をインストールする

VS Code をダウンロードしてインストールします。

[Download Visual Studio Code - Mac, Linux, Windows](https://code.visualstudio.com/download)

## 拡張機能をインストールする

Go アプリをデバッグするための拡張機能をインストールします。

[Visual Studio Code for Go 開発のインストールと構成 - Go on Azure | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/developer/go/configure-visual-studio-code)

## Delve をインストールする

Go のデバッガ Delve をインストールします。

[delve/Documentation/installation at master · go-delve/delve · GitHub](https://github.com/go-delve/delve/tree/master/Documentation/installation)

## プロジェクトのフォルダを開く

作成したプロジェクトのフォルダを VS Code で開きます。

[VSCodeの基本～プロジェクトフォルダの設定方法について解説します！ | プログラミング入門ナビ by Proglus（プログラス）](https://blog.proglus.jp/4672/)

## launch.json を用意する①

効率よくデバッグできるように VS Code のデバッグ構成 `launch.json` を用意します。

公式サイトで紹介されているものを使ってみます。[統合開発環境 | Wails](https://wails.io/ja/docs/guides/ides/)

```json:launch.json
{
    version: "0.2.0",
    configurations: [
        {
            "name": "Debug Program",
            "type": "go",
            "request": "launch",
            "mode": "exec",
            "program": "build/bin/●●.exe",
            "cwd": "${workspaceFolder}",
            "preLaunchTask": "build",
            "env": {}
        }
    ]
}
```

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "type": "shell",
            "command": "go",
            "args": [
                "build",
                "-tags", "dev",
                "-gcflags", "all=-N -l", "-o",
                "build/bin/●●.exe"
            ]
            "options": {
                "cwd": "${workspaceFolder}"
            },
        }
    ]
}
```

`●●.exe` は実際のプロジェクトに合わせて変更します。macOS の場合は `●●.app` になります。↑

この設定の準備して `デバッグ実行` します。

![説明①.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/1f63ef3d-9446-4c5f-9df3-de8c76b966b7.png)

バックエンドの Go のプログラムは、ステップ実行したり変数ウォッチできます。フロントエンドの JavaScript のプログラムは、だめでした。

## launch.json を用意する②

フロントエンドの JavaScript のプログラムにアタッチしてデバッグできないものでしょうか。

![説明②.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/c658ca4c-df03-4910-a50f-d3f238b0df33.png)

Wails アプリのフロントエンドは WebView を使っています。これにデバッガがアタッチできないか調べていくと、以下の記事を見つけました。

[Visual Studio Code を使用して WebView2 アプリをデバッグする - Microsoft Edge Developer documentation | Microsoft Learn](https://learn.microsoft.com/ja-jp/microsoft-edge/webview2/how-to/debug-visual-studio-code)

```json:launch.json
{
    version: "0.2.0",
    configurations: [
        {
            "name": "Debug frontend",
            "type": "msedge",
            "request": "launch",
            "runtimeExecutable": "build/bin/●●.exe",
            "env": {
                "Path": "%path%;build/bin/;"
            },
            "useWebView": true,
            "port": 9222, // The port value is optional, and the default value is 9222.
            "url": "file:///${workspaceFolder}/frontend/index.html",
            "webRoot": "${workspaceFolder}/frontend"
        },
(後略)
```

上記①と併せて、バックエンドとフロントエンドのどちらもデバッグ実行できるようになると思ったのですが、

・Windows 環境しか設定できない
・VS Code の設定だけでなく Windows でレジストリを変更しないといけない

なので、使うのは断念しました。

## launch.json を用意する③

`wails dev` して開発モードで起動すると、ブラウザからアプリを操作できるようにするウェブサーバを起動する。これにより任意のブラウザ拡張機能を利用できる、と書かれています。
これを使えば、フロントエンドのプログラムを VS Code でデバッグできるようです。

[How to debug frontend through vite on vscode · wailsapp/wails · Discussion #2877 · GitHub](https://github.com/wailsapp/wails/discussions/2877)

つまり、
①アプリを開発モードで起動する
②これにアタッチする。バックエンドのプログラムがデバッグできる
③開発モードのアプリが起動するウェブサーバにウェブブラウザを起動して開く
④これにアタッチする。フロントエンドのプログラムがデバッグできる

![説明③.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/f60b383b-a78a-4c19-a5a4-d3594ea06794.png)

まず、`wails dev` を VS Code から実行（①）できるように設定を用意します。

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "wails dev",
            "type": "shell",
            "command": "wails",
            "args": [ "dev" ]
            "options": {
                "cwd": "${workspaceFolder}"
            },
        }
    ]
}
```

続いて、`wails dev` で起動したアプリのバックエンドのプログラムにアタッチする（②）設定です。

```json:launch.json
{
    version: "0.2.0",
    configurations: [
        {
            "name": "attach to backend",
            "type": "go",
            "request": "attach",
            "mode": "local",
            "processId": "●●-dev.exe",
        },
(後略)
```

`proccessId` は、実行時点でプロセスを選択させるしかないと思っていましたが、プロセス名で OK でした。`wails dev` 実行して `build/bin` フォルダに作成されるアプリの実行ファイルの名前を指定します。↑

続いて、上記のアプリがウェブサーバになるので、ウェブブラウザを起動してフロントエンドのプログラムを開きます（③）。既にアプリが起動してフロントエンドのプログラムの画面が開いていて、これを別にブラウザでフロントエンドの画面を開くので、スマートでありませんが仕方ありません。

```json:launch.json
(前略)
        {
            "name": "launch chrome",
            "type": "chrome",
            "request": "launch",
            "url": "http://localhost:34115",
            "runtimeArgs": [
                --remote-debugging-port=9222,
            ],
        },
```

VS Code からアタッチできるよう `--remote-debugging-port` を指定します。↑

続いて、上記のウェブブラウザで実行しているフロントエンドのプログラムにアタッチする（④）設定です。

```json:launch.json
(前略)
        {
            "name": "attach to chrome",
            "type": "chrome",
            "request": "attach",
            "port": 9222,
            "webRoot": "${workspaceFolder}/frontend",
            "sourceMaps": true,
        },
```

これを順に実行すればいいのですが、面倒なので連続して実行できないか試してみました。

例えば、上記②の設定に `"preLaunchTask": "wails dev"` を加えてみたのですが、`wails dev` が終了して初めて②の設定の `attach` が実行されるので、これはだめでした。

試行錯誤した結果、以下の設定で使っています。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Start Program",
            "type": "node-terminal",
            "request": "launch",
            "command": "wails dev",
            "cwd": "${workspaceFolder}",
        },
        {
            "name": "attach to backend",
            "type": "go",
            "request": "attach",
            "mode": "local",
            "processId": "●●-dev.exe",  // 実行ファイルに合わせる
            "presentation": {
                "hidden": true,
            }
        },
        {
            "name": "launch chrome",
            "type": "chrome",
            "request": "launch",
            "url": "http://localhost:34115",
            "runtimeArgs": [
                "--remote-debugging-port=9222",
            ],
            "presentation": {
                "hidden": true,
            }
        },
        {
            "name": "attach to chrome",
            "type": "chrome",
            "request": "attach",
            "port": 9222,
            "webRoot": "${workspaceFolder}/frontend",
            "sourceMaps": true,
            "presentation": {
                "hidden": true,
            }
        },
    ],
    "compounds": [
        {
            "name": "Debug Program",
            "configurations": [
                "attach to backend",
                "launch chrome",
                "attach to chrome",
            ],
            "stopAll": true,
        },
    ],
}
```

`tasks.json` でなく `launch.json` に `wails dev` 実行の設定しています。↑

VS Code の「実行とデバッグ」のメニューに `Start Program` と `Debug Program` が表示されます。ビルドして実行してテストするなら `Start Program` を実行します。実行されたアプリにアタッチしてデバッグするとき `Debug Program` を実行します。