wails/website/docs/howdoesitwork.mdx
2023-06-09 00:41:43 +00:00

458 lines
13 KiB
Text

---
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.
```mdx-code-block
<div className="text--center">
<img src={require("@site/static/img/architecture.webp").default} style={{"width":"75%", "max-width":"800px"}} />
</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"
"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)
}
```
### 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](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 all: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](guides/application-development.mdx).
#### Application Lifecycle Callbacks
Just before the frontend is about to load `index.html`, a callback is made to
the function provided in [OnStartup](reference/options.mdx#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](reference/options.mdx#onshutdown) callback is called in the same
way, again with the context. There is also an
[OnDomReady](reference/options.mdx#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](reference/options.mdx#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. Think of structs
like "controllers" in a traditional web application. 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 {17,27} 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)
}
```
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,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
&mystruct1{},
&mystruct2{},
},
})
```
When you run `wails dev` (or `wails generate module`), a frontend module will be
generated containing the following:
- JavaScript bindings for all bound methods
- TypeScript declarations for all bound methods
- TypeScript definitions for all Go structs used as inputs or outputs by the
bound methods
This makes it incredibly simple to call Go code from the frontend, using the
same strongly typed datastructures.
## 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
When you run your application with `wails dev`, it will automatically generate
JavaScript bindings for your structs in a directory called `wailsjs/go` (You can
also do this by running `wails generate module`). The generated files mirror the
package names in your application. In the example above, we bind `app`, which
has one public method `Greet`. This will lead to the generation of the following
files:
```bash
wailsjs
└─go
└─main
├─App.d.ts
└─App.js
```
Here we can see that there is a `main` package that contains the JavaScript
bindings for the bound `App` struct, as well as the TypeScript declaration file
for those methods. To call `Greet` from our frontend, we simply import the
method and call it like a regular JavaScript function:
```javascript
// ...
import { Greet } from "../wailsjs/go/main/App";
function doGreeting(name) {
Greet(name).then((result) => {
// Do something with result
});
}
```
The TypeScript declaration file gives you the correct types for the bound
methods:
```ts
export function Greet(arg1: string): Promise<string>;
```
The generated 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 class.
:::info Note
Struct fields _must_ have a valid `json` tag to be included in the generated
TypeScript.
Anonymous nested structs are not supported at this time.
:::
It is possible to send structs back to Go. Any JavaScript map/class 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.
There is also support for Go methods that use structs in their signature. All Go
structs specified by a 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.
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)
}
```
The `wailsjs/go/main/App.js` file will still have the following code:
```js title="App.js"
export function Greet(arg1) {
return window["go"]["main"]["App"]["Greet"](arg1);
}
```
But the `wailsjs/go/main/App.d.ts` file will be updated with the following code:
```ts title="App.d.ts"
import { main } from "../models";
export function Greet(arg1: main.Person): Promise<string>;
```
As we can see, the "main" namespace is imported from a new "models.ts" file.
This file contains all the struct definitions used by our bound methods. In this
example, this is a `Person` struct. If we look at `models.ts`, we can see how
the models are defined:
```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;
}
}
}
```
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 { 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);
});
}
```
The combination of generated bindings and TypeScript models makes for a powerful
development environment.
More information on Binding can be found in the
[Binding Methods](guides/application-development.mdx#binding-methods) section of
the [Application Development Guide](guides/application-development.mdx).
### 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](reference/runtime/intro).