5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 07:21:32 +08:00
wails/website/docs/howdoesitwork.mdx

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).