mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 07:21:32 +08:00
410 lines
13 KiB
Plaintext
410 lines
13 KiB
Plaintext
---
|
|
sidebar_position: 20
|
|
---
|
|
|
|
# How does it work?
|
|
|
|
A Wails application is a standard Go application, with a webkit frontend. The Go part of the application consists of the
|
|
application code and a runtime library that provides a number of useful operations, like controlling the application
|
|
window. The frontend is a webkit window that will display the frontend assets. Also available to the frontend is a Javascript
|
|
version of the runtime library. Finally, it is possible to bind Go methods to the frontend, and these will appear as
|
|
Javascript methods that can be called, just as if they were local Javascript methods.
|
|
|
|
<div className="text--center">
|
|
<img src="/img/architecture.svg" width="75%" />
|
|
</div>
|
|
|
|
## The Main Application
|
|
|
|
### Overview
|
|
|
|
The main application consists of a single call to `wails.Run()`. It accepts the
|
|
application configuration which describes the size of the application window, the window title,
|
|
what assets to use, etc. A basic application might look like this:
|
|
|
|
```go title="main.go"
|
|
package main
|
|
|
|
import (
|
|
"embed"
|
|
"log"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
//go:embed frontend/dist
|
|
var assets embed.FS
|
|
|
|
func main() {
|
|
|
|
app := &App{}
|
|
|
|
err := wails.Run(&options.App{
|
|
Title: "Basic Demo",
|
|
Width: 1024,
|
|
Height: 768,
|
|
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)
|
|
}
|
|
```
|
|
|
|
### Options rundown
|
|
|
|
This example has the following options set:
|
|
|
|
- `Title` - The text that should appear in the window's title bar
|
|
- `Width` & `Height` - The dimensions of the window
|
|
- `Assets` - The application's frontend assets
|
|
- `OnStartup` - A callback for when the window is created and is about to start loading the frontend assets
|
|
- `OnShutdown` - A callback for when the application is about to quit
|
|
- `Bind` - A slice of struct instances that we wish to expose to the frontend
|
|
|
|
A full list of application options can be found in the [Options Reference](/docs/reference/options).
|
|
|
|
#### Assets
|
|
|
|
The `Assets` option is mandatory as you can't have a Wails application without frontend assets. Those assets can be
|
|
any files you would expect to find in a web application - html, js, css, svg, png, etc. **There is no requirement to
|
|
generate asset bundles** - plain files will do. When the application starts, it will attempt to load `index.html`
|
|
from your assets and the frontend will essentially work as a browser from that point on. It is worth noting that
|
|
there is no requirement on where in the `embed.FS` the files live. It is likely that the embed path uses a nested
|
|
directory relative to your main application code, such as `frontend/dist`:
|
|
|
|
```go title="main.go"
|
|
//go:embed frontend/dist
|
|
var assets embed.FS
|
|
```
|
|
|
|
At startup, Wails will iterate the embedded files looking for the directory containing `index.html`. All other assets will be loaded relative
|
|
to this directory.
|
|
|
|
As production binaries use the files contained in `embed.FS`, there are no external files required to be shipped with
|
|
the application.
|
|
|
|
When running in development mode using the `wails dev` command, the assets are loaded off disk, and any changes result
|
|
in a "live reload". The location of the assets will be inferred from the `embed.FS`.
|
|
|
|
More details can be found in the [Application Development Guide](/docs/guides/application-development).
|
|
|
|
#### Application Lifecycle Callbacks
|
|
|
|
Just before the frontend is about to load `index.html`, a callback is made to the function provided in [OnStartup](/docs/reference/options#OnStartup).
|
|
A standard Go context is passed to this method. This context is required when calling the runtime so a standard pattern is to save
|
|
a reference to in this method. Just before the application shuts down, the [OnShutdown](/docs/reference/options#OnShutdown) callback is called in the same way,
|
|
again with the context. There is also an [OnDomReady](/docs/reference/options#OnDomReady) callback for when the frontend
|
|
has completed loading all assets in `index.html` and is equivalent of the [`body onload`](https://www.w3schools.com/jsref/event_onload.asp) event in Javascript.
|
|
It is also possible to hook into the window close (or application quit) event by setting the
|
|
option [OnBeforeClose](/docs/reference/options#OnBeforeClose).
|
|
|
|
#### Method Binding
|
|
|
|
The `Bind` option is one of the most important options in a Wails application. It specifies which struct methods
|
|
to expose to the frontend. When the application starts, it examines the struct instances listed in the `Bind` field in
|
|
the options, determines which methods are public (starts with an uppercase letter) and will generate Javascript versions
|
|
of those methods that can be called by the frontend code.
|
|
|
|
:::info Note
|
|
|
|
Wails requires that you pass in an *instance* of the struct for it to bind it correctly
|
|
|
|
:::
|
|
|
|
In this example, we create a new `App` instance and then add this instance to the `Bind` option in `wails.Run`:
|
|
|
|
```go {16,26} title="main.go"
|
|
package main
|
|
|
|
import (
|
|
"embed"
|
|
"log"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
//go:embed frontend/dist
|
|
var assets embed.FS
|
|
|
|
func main() {
|
|
|
|
app := &App{}
|
|
|
|
err := wails.Run(&options.App{
|
|
Title: "Basic Demo",
|
|
Width: 1024,
|
|
Height: 768,
|
|
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)
|
|
}
|
|
```
|
|
|
|
You may bind as many structs as you like. Just make sure you create an instance of it and pass it in bind:
|
|
|
|
```go {10-12}
|
|
...
|
|
err := wails.Run(&options.App{
|
|
Title: "Basic Demo",
|
|
Width: 1024,
|
|
Height: 768,
|
|
Assets: &assets,
|
|
OnStartup: app.startup,
|
|
OnShutdown: app.shutdown,
|
|
Bind: []interface{}{
|
|
app,
|
|
&mystruct1{},
|
|
&mystruct2{},
|
|
},
|
|
})
|
|
...
|
|
```
|
|
|
|
The bound methods are located in the frontend at `window.go.<packagename>.<struct>.<method>`.
|
|
In the example above, we bind `app`, which has one public method `Greet`.
|
|
This can be called in Javascript by calling `window.go.main.App.Greet`.
|
|
These methods return a promise. A successful call will result in the first return value from the Go call to be passed
|
|
to the resolve handler. An unsuccessful call is when a Go method that has an error type as it's second return value,
|
|
passes an error instance back to the caller. This is passed back via the reject handler.
|
|
In the example above, `Greet` only returns a `string` so the Javascript call will never reject - unless invalid data
|
|
is passed to it.
|
|
|
|
All data types are correctly translated between Go and Javascript. Even structs. If you return a struct from a Go call,
|
|
it will be returned to your frontend as a Javascript map. Note: If you wish to use structs, you **must** define `json` struct
|
|
tags for your fields! It is also possible to send structs back to Go. Any Javascript map passed as an argument that
|
|
is expecting a struct, will be converted to that struct type. To make this process a lot easier, in `dev` mode,
|
|
a TypeScript module is generated, defining all the struct types used in bound methods. Using this module, it's possible
|
|
to construct and send native Javascript objects to the Go code.
|
|
|
|
More information on Binding can be found in the [Binding Methods](/docs/guides/application-development#binding-methods)
|
|
section of the [Application Development Guide](/docs/guides/application-development).
|
|
|
|
## The Frontend
|
|
|
|
### Overview
|
|
|
|
The frontend is a collection of files rendered by webkit. It's like a browser and webserver in one.
|
|
There is virtually[^1] no limit to which frameworks or libraries you can use. The main points of interaction between
|
|
the frontend and your Go code are:
|
|
|
|
- Calling bound Go methods
|
|
- Calling runtime methods
|
|
|
|
[^1]:
|
|
There is a very small subset of libraries that use features unsupported in WebViews. There are often alternatives and
|
|
workarounds for such cases.
|
|
|
|
### Calling bound Go methods
|
|
|
|
All bound Go methods are available at `window.go.<package>.<struct>.<method>`. As stated in
|
|
the previous section, these return a Promise where a successful call returns a value to the
|
|
resolve handler and an error returns a value to the reject handler.
|
|
|
|
```go title="mycode.js"
|
|
window.go.main.App.Greet("Bill").then((result) => {
|
|
console.log("The greeting is: " + result);
|
|
})
|
|
```
|
|
|
|
When running the application in `dev` mode, a javascript module is generated that wraps these
|
|
methods with JSDoc annotations. This really help with development, especially as most
|
|
IDEs will process JSDoc to provide code completion and type hinting. This module is called `go`
|
|
and is generated in the directory specified by the `wailsjsdir` flag. In this module is a file
|
|
called `bindings.js` containing these wrappers. For the above example, the file contains the
|
|
following code:
|
|
|
|
```js title="bindings.js"
|
|
const go = {
|
|
main: {
|
|
App: {
|
|
/**
|
|
* Greet
|
|
* @param {Person} arg1 - Go Type: string
|
|
* @returns {Promise<string>} - Go Type: string
|
|
*/
|
|
Greet: (arg1) => {
|
|
return window.go.main.App.Greet(arg1);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
export default go;
|
|
```
|
|
|
|
#### Support for structs
|
|
|
|
There is also additional support for Go methods that use structs in their signature. All Go structs
|
|
specified by bound method (either as parameters or return types) will have Typescript versions auto
|
|
generated as part of the Go code wrapper module. Using these, it's possible to share the same data
|
|
model between Go and Javascript. These models align with the JSDoc annotations, empowering IDE code
|
|
completion.
|
|
|
|
Example: We update our `Greet` method to accept a `Person` instead of a string:
|
|
|
|
```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)
|
|
}
|
|
```
|
|
|
|
Our `bindings.js` file has now been updated to reflect the change:
|
|
|
|
```js title="bindings.js"
|
|
const go = {
|
|
main: {
|
|
App: {
|
|
/**
|
|
* Greet
|
|
* @param {Person} arg1 - Go Type: main.Person
|
|
* @returns {Promise<string>} - Go Type: string
|
|
*/
|
|
Greet: (arg1) => {
|
|
return window.go.main.App.Greet(arg1);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
export default go;
|
|
```
|
|
|
|
Alongside `bindings.js`, there is a file called `models.ts`. This contains our Go structs in TypeScript form:
|
|
|
|
```ts title="models.ts"
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
So long as you have TypeScript as part of your frontend build configuration, you can use these models in
|
|
the following way:
|
|
|
|
```js title="mycode.js"
|
|
import go from "./wailsjs/go/bindings";
|
|
import { Person } from "./wailsjs/go/models";
|
|
|
|
let name = "";
|
|
|
|
function greet(name) {
|
|
let p = new Person();
|
|
p.name = name;
|
|
p.age = 42;
|
|
go.main.App.Greet(p).then((result) => {
|
|
console.log(result);
|
|
});
|
|
}
|
|
```
|
|
|
|
The combination of JSDoc and TypeScript generated models makes for a powerful development environment.
|
|
|
|
### Calling runtime methods
|
|
|
|
The Javascript runtime is located at `window.runtime` and contains many methods to do various
|
|
tasks such as emit an event or perform logging operations:
|
|
|
|
```js title="mycode.js"
|
|
window.runtime.EventsEmit("my-event", 1);
|
|
```
|
|
|
|
More details about the JS runtime can be found in the [Runtime Reference](/docs/reference/runtime/intro).
|