--- sidebar_position: 20 --- # どうやって動いているの? Wailsは、webkitフロントエンドを備えた、何の変哲もないGoアプリです。 アプリ全体のうちGoの部分は、アプリのコードと、ウィンドウ制御などの便利な機能を提供するランタイムライブラリで構成されています。 フロントエンドはwebkitウィンドウであり、フロンドエンドアセットをウィンドウ上に表示します。 フロントエンドからも、JavaScriptでランタイムライブラリを呼び出すことができます。 そして最終的に、Goのメソッドはフロントエンドにバインドされ、ローカルのJavaScriptメソッドであるかのように、フロントエンドから呼び出すことができます。 ```mdx-code-block
``` ## アプリのメインコード ### 概要 アプリは、`wails.Run()`メソッドを1回呼び出すことで、構成することができます。 このメソッドで、アプリのウィンドウサイズやウィンドウタイトル、使用アセットなどを指定することができます。 基本的なアプリを作るコードは次のとおりです: ```go title="main.go" package main import ( "embed" "log" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist var assets embed.FS func main() { app := &App{} err := wails.Run(&options.App{ Title: "Basic Demo", Width: 1024, Height: 768, AssetServer: &assetserver.Options{ Assets: assets, }, OnStartup: app.startup, OnShutdown: app.shutdown, Bind: []interface{}{ app, }, }) if err != nil { log.Fatal(err) } } type App struct { ctx context.Context } func (b *App) startup(ctx context.Context) { b.ctx = ctx } func (b *App) shutdown(ctx context.Context) {} func (b *App) Greet(name string) string { return fmt.Sprintf("Hello %s!", name) } ``` ### オプション 上記のコードでは、次のオプションが指定されています: - `Title` - ウィンドウのタイトルバーに表示されるテキスト - `Width` & `Height` - ウィンドウの大きさ - `Assets` - アプリのフロントエンドアセット - `OnStartup` - ウィンドウが作成され、フロントエンドの読み込みを開始しようとする時のコールバック - `OnShutdown` - アプリを終了しようとするときのコールバック - `Bind` - フロントエンドにバインドさせたい構造体インスタンスのスライス 設定可能なすべてのオプションについては、[オプションのリファレンス](reference/options)をご覧ください。 #### アセット Wailsでフロントエンドアセット無しのアプリを作成することはできないため、`Assets`オプションは必須オプションです。 アセットには、一般的なWebアプリケーションでよく見かけるような、html、js、css、svg、pngなどのファイルを含めることができます。**アセットバンドルを生成する必要は一切なく**、そのままのファイルを使用できます。 アプリが起動すると、アセット内の`index.html`が読み込まれます。この時点で、フロントエンドはブラウザとして動作するようになります。 `embed.FS`を使ってアセットファイルが格納されたディレクトリを指定しますが、ディレクトリの場所はどこでも構いません。 embedで指定するパスは、`frontend/dist`のように、アプリのメインコードから相対的に見たディレクトリパスになります: ```go title="main.go" //go:embed all:frontend/dist var assets embed.FS ``` 起動時に、Wailsは`index.html`が含まれるディレクトリを再帰的に探します。 他のすべてのアセットは、当該ディレクトリから相対的に読み込まれます。 本番用のバイナリファイルには、`embed.FS`で指定されたアセットファイルが含まれるため、アプリ配布時に、バイナリファイルとは別にアセットファイルを付加させる必要はありません。 `wails dev`コマンドを使って開発モードでアプリを起動した場合、アセットはディスクから読み込まれ、アセットファイルが更新されると、自動的にアプリがライブリロードされます。 アセットの場所は、`embed.FS`での指定値から推定されます。 詳細は、[アプリ開発ガイド](guides/application-development.mdx)をご覧ください。 #### アプリのライフサイクル フロントエンドが`index.html`を読み込もうとする直前に、[OnStartup](reference/options.mdx#onstartup)で指定されたメソッドがコールバックされます。 Goの標準的なcontextがこのメソッドに渡されます。 このメソッドに引数で渡されるContextは、今後、Wailsのラインタイムを呼び出すときに必要になるため、通常は、このContextへの参照を保持しておいてください。 同様に、アプリがシャットダウンされる直前には、[OnShutdown](reference/options.mdx#onshutdown)で指定されたコールバックが呼び出され、Contextも渡されます。 `index.html`に含まれるすべてのアセットが読み込み終わったときに呼び出される[OnDomReady](reference/options.mdx#ondomready)コールバックもあります。これは、JavaScriptの[`body onload`](https://www.w3schools.com/jsref/event_onload.asp)イベントと同等のものです。 また、[OnBeforeClose](reference/options.mdx#onbeforeclose)を指定すると、ウィンドウを閉じる(またはアプリを終了する)イベントにフックさせることもできます。 #### メソッドのバインド `Bind`オプションは、Wailsアプリで最も重要なオプションの1つです。 このオプションでは、フロントエンドに公開する、構造体のメソッドを指定することができます。 構造体は、従来のWebアプリにおける"コントローラ"の立ち位置であるとお考えください。 アプリが起動すると、`Bind`オプションで指定されている構造体を対象に、その中にあるパブリックメソッド(大文字で始まるメソッド名)を探します。そして、フロントエンドのコードからそれらのメソッドを呼び出せるJavaScriptが生成されます。 :::info 備考 Wailsで構造体を正しくバインドするためには、構造体の*インスタンス*をオプションで指定してください。 ::: 下記のコードでは、新しく`App`インスタンスを作成し、`wails.Run`関数の`Bind`オプションの中で、そのインスタンスを追加しています: ```go {16,24} title="main.go" package main import ( "embed" "log" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist var assets embed.FS func main() { app := &App{} err := wails.Run(&options.App{ Title: "Basic Demo", Width: 1024, Height: 768, AssetServer: &assetserver.Options{ Assets: assets, }, Bind: []interface{}{ app, }, }) if err != nil { log.Fatal(err) } } type App struct { ctx context.Context } func (a *App) Greet(name string) string { return fmt.Sprintf("Hello %s!", name) } ``` 構造体は、好きな数だけバインドできます。 `Bind`には、構造体のインスタンスを渡すようにしてください: ```go {8-10} //... err := wails.Run(&options.App{ Title: "Basic Demo", Width: 1024, Height: 768, AssetServer: &assetserver.Options{ Assets: assets, }, Bind: []interface{}{ app, &mystruct1{}, &mystruct2{}, }, }) ``` `wails dev`コマンド(または、`wails generate module`コマンド)を実行すると、以下のものを含むフロントエンドモジュールが生成されます: - バインドされたすべてのメソッドのJavaScript - バインドされたすべてのメソッドのTypeScript宣言 - バインドされたメソッドの引数または返り値で使用されているGoの構造体のTypeScript宣言 これにより、強力な型付けがなされたデータ構造を使用して、フロントエンドから簡単にGoのコードを呼び出すことができます。 ## フロントエンド ### 概要 フロントエンドは、webkitによってレンダリングされるファイル群です。 ブラウザとWebサーバが一体となったような動きをします。 使用できるフレームワークやライブラリの制限はほぼありません[^1]。 フロントエンドとGoコードとの相互のやり取りについて、主なポイントは次のとおりです: - バインドされたGoメソッドの呼び出し - ランタイムメソッドの呼び出し ### バインドされたGoメソッドの呼び出し `wails dev`コマンドでアプリを起動すると、Go構造体のJavaScriptバインディングが`wailsjs/go`ディレクトリに自動的に生成されます(`wails generate module`コマンドでも生成可能)。 生成されたファイルには、アプリ内のGoパッケージ名が反映されています。 先の例では、`Greet`というパブリックメソッドを持つ`app`をバインドしていました。 この場合、次のようなファイルが生成されることになります: ```bash wailsjs └─go └─main ├─App.d.ts └─App.js ``` ご覧のとおり、`main`パッケージ内の`App`構造体JavaScriptバインディングが、それらのメソッドのTypeScript型定義と並んで生成されていることが分かります。 フロントエンドから`Greet`メソッドを呼び出すには、単純にメソッドをインポートし、通常のJavaScript関数と同じように呼び出してください: ```javascript // ... import {Greet} from '../wailsjs/go/main/App' function doGreeting(name) { Greet(name).then((result) => { // resultを使って何かする }) } ``` TypeScript型定義ファイルは、バインドされたメソッドの正しい型を提供します: ```ts export function Greet(arg1: string): Promise; ``` 生成されたメソッドはPromiseを返すようになっています。 呼び出しが成功すると、Goからの1番目の返り値が`resolve`ハンドラに渡されます。 呼び出しに失敗したとみなされるのは、Goメソッドの2番目の返り値がerror型で、それがerrorインスタンスを返したときです。 これは、`reject`ハンドラを介して返されます。 先の例では、`Greet`メソッドは`string`型の返り値のみであるため、無効なデータが渡されない限り、JavaScript側でrejectハンドラが呼ばれることはありません。 すべてのデータ型は、GoとJavaScriptの間で正しく解釈されます。 もちろん構造体も正しく解釈されます。 Goから構造体が返された場合、フロントエンドにはJavaScriptのクラスとして返されます。 :::info 備考 TypeScript型定義を正しく自動生成するために、構造体のフィールドには、有効な`json`タグを*必ず*付与するようにしてください。 ネストされた匿名構造体(無名構造体) は、現時点ではサポートされていません。 ::: Goに対して引数として構造体を渡すこともできます。 構造体として取り扱ってほしいJavaScriptのマップやクラスを渡すと、構造体に変換されます。 あなたが簡単にこれらのことを把握できるように、`dev`モードでは、バウンドされたGoメソッドで使用されている全構造体の型が定義された、Typescriptモジュールが生成されます。 このモジュールを使用すると、JavaScriptネイティブなオブジェクトを構築し、Goコードへ送信することができます。 また、シグネチャに構造体を使用するGoメソッドもサポートされています。 バインドされたメソッドで、引数または返り値として指定されているすべてのGo構造体は、Goのコードラッパーモジュールの一部として生成されたTypeScript定義を持っています。 れらを使用することで、GoとJavaScriptの間で、同じデータモデルを共有できます。 例: `Greet`メソッドを更新して、文字列型の代わりに`Person`型を引数で受け付けてみる: ```go title="main.go" type Person struct { Name string `json:"name"` Age uint8 `json:"age"` Address *Address `json:"address"` } type Address struct { Street string `json:"street"` Postcode string `json:"postcode"` } func (a *App) Greet(p Person) string { return fmt.Sprintf("Hello %s (Age: %d)!", p.Name, p.Age) } ``` `wailsjs/go/main/App.js`ファイルには、次のコードが出力されます: ```js title="App.js" export function Greet(arg1) { return window["go"]["main"]["App"]["Greet"](arg1); } ``` しかし、`wailsjs/go/main/App.d.ts`ファイルは次のコードが出力されます: ```ts title="App.d.ts" import { main } from "../models"; export function Greet(arg1: main.Person): Promise; ``` 見ると分かるように、"main"名前空間は、新しく生成された"models.ts"ファイルからインポートされています。 このファイルには、バインドされたメソッドで使用されるすべての構造体の型定義が含まれています。 この例では、`Person`構造体の型定義が含まれています。 `models.ts`を確認すれば、モデルがどのように定義されているかが分かります。 ```ts title="models.ts" export namespace main { export class Address { street: string; postcode: string; static createFrom(source: any = {}) { return new Address(source); } constructor(source: any = {}) { if ("string" === typeof source) source = JSON.parse(source); this.street = source["street"]; this.postcode = source["postcode"]; } } export class Person { name: string; age: number; address?: Address; static createFrom(source: any = {}) { return new Person(source); } constructor(source: any = {}) { if ("string" === typeof source) source = JSON.parse(source); this.name = source["name"]; this.age = source["age"]; this.address = this.convertValues(source["address"], Address); } convertValues(a: any, classs: any, asMap: boolean = false): any { if (!a) { return a; } if (a.slice) { return (a as any[]).map((elem) => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { for (const key of Object.keys(a)) { a[key] = new classs(a[key]); } return a; } return new classs(a); } return a; } } } ``` フロントエンドのビルド構成にTypescriptを使用している限り、これらのモデルを次のように使用できます: ```js title="mycode.js" import { Greet } from "../wailsjs/go/main/App"; import { main } from "../wailsjs/go/models"; function generate() { let person = new main.Person(); person.name = "Peter"; person.age = 27; Greet(person).then((result) => { console.log(result); }); } ``` 生成されたバインディングとTypescriptモデルの組み合わせによって、強力な開発環境を実現させています。 バインディングの詳細については、[アプリ開発ガイド](guides/application-development.mdx)の[バインディングメソッド](guides/application-development.mdx#binding-methods)をご覧ください。 ### ランタイムメソッドの呼び出し Javascriptランタイムは`window.runtime`に存在し、イベント発行やロギングなど、さまざまなタスクを実行するためのメソッドが含まれています: ```js title="mycode.js" window.runtime.EventsEmit("my-event", 1); ``` Javascriptランタイムの詳細については、[ランタイムリファレンス](reference/runtime/intro)をご覧ください。 [^1]: まれに、WebViewでサポートされていない機能を使用するライブラリがあります。 ほとんどの場合、それらは代替手段や回避方法がありますので、それらを検討してください。