wails/website/docs/guides/application-development.mdx
2023-06-09 00:41:43 +00:00

254 lines
7.5 KiB
Text

# Application Development
There are no hard and fast rules for developing applications with Wails, but
there are some basic guidelines.
## Application Setup
The pattern used by the default templates are that `main.go` is used for
configuring and running the application, whilst `app.go` is used for defining
the application logic.
The `app.go` file will define a struct that has 2 methods which act as hooks
into the main application:
```go title="app.go"
type App struct {
ctx context.Context
}
func NewApp() *App {
return &App{}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) shutdown(ctx context.Context) {
}
```
- The startup method is called as soon as Wails allocates the resources it needs
and is a good place for creating resources, setting up event listeners and
anything else the application needs at startup. It is given a
`context.Context` which is usually saved in a struct field. This context is
needed for calling the [runtime](../reference/runtime/intro.mdx). If this
method returns an error, the application will terminate. In dev mode, the
error will be output to the console.
- The shutdown method will be called by Wails right at the end of the shutdown
process. This is a good place to deallocate memory and perform any shutdown
tasks.
The `main.go` file generally consists of a single call to `wails.Run()`, which
accepts the application configuration. The pattern used by the templates is that
before the call to `wails.Run()`, an instance of the struct we defined in
`app.go` is created and saved in a variable called `app`. This configuration is
where we add our callbacks:
```go {3,9,10} title="main.go"
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "My App",
Width: 800,
Height: 600,
OnStartup: app.startup,
OnShutdown: app.shutdown,
})
if err != nil {
log.Fatal(err)
}
}
```
More information on application lifecycle hooks can be found
[here](../howdoesitwork.mdx#application-lifecycle-callbacks).
## Binding Methods
It is likely that you will want to call Go methods from the frontend. This is
normally done by adding public methods to the already defined struct in
`app.go`:
```go {16-18} title="app.go"
type App struct {
ctx context.Context
}
func NewApp() *App {
return &App{}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) shutdown(ctx context.Context) {
}
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
```
In the main application configuration, the `Bind` key is where we can tell Wails
what we want to bind:
```go {11-13} title="main.go"
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "My App",
Width: 800,
Height: 600,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
```
This will bind all public methods in our `App` struct (it will never bind the
startup and shutdown methods).
### Dealing with context when binding multiple structs
If you want to bind methods for multiple structs but want each struct to keep a
reference to the context so that you can use the runtime functions, a good
pattern is to pass the context from the `OnStartup` method to your struct
instances :
```go
func main() {
app := NewApp()
otherStruct := NewOtherStruct()
err := wails.Run(&options.App{
Title: "My App",
Width: 800,
Height: 600,
OnStartup: func(ctx context.Context){
app.SetContext(ctx)
otherStruct.SetContext(ctx)
},
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
otherStruct
},
})
if err != nil {
log.Fatal(err)
}
}
```
More information on Binding can be found
[here](../howdoesitwork.mdx#method-binding).
## Application Menu
Wails supports adding a menu to your application. This is done by passing a
[Menu](../reference/menus.mdx#menu) struct to application config. It's common to
use a method that returns a Menu, and even more common for that to be a method
on the `App` struct used for the lifecycle hooks.
```go {11} title="main.go"
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "My App",
Width: 800,
Height: 600,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Menu: app.menu(),
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
```
## Assets
The great thing about the way Wails v2 handles assets is that it doesn't! The
only thing you need to give Wails is an `embed.FS`. How you get to that is
entirely up to you. You can use vanilla html/css/js files like the vanilla
template. You could have some complicated build system, it doesn't matter.
When `wails build` is run, it will check the `wails.json` project file at the
project root. There are 2 keys in the project file that are read:
- "frontend:install"
- "frontend:build"
The first, if given, will be executed in the `frontend` directory to install the
node modules. The second, if given, will be executed in the `frontend` directory
to build the frontend project.
If these 2 keys aren't given, then Wails does absolutely nothing with the
frontend. It is only expecting that `embed.FS`.
### AssetsHandler
A Wails v2 app can optionally define a `http.Handler` in the `options.App`,
which allows hooking into the AssetServer to create files on the fly or process
POST/PUT requests. GET requests are always first handled by the `assets` FS. If
the FS doesn't find the requested file the request will be forwarded to the
`http.Handler` for serving. Any requests other than GET will be directly
processed by the `AssetsHandler` if specified. It's also possible to only use
the `AssetsHandler` by specifiy `nil` as the `Assets` option.
## Built in Dev Server
Running `wails dev` will start the built in dev server which will start a file
watcher in your project directory. By default, if any file changes, wails checks
if it was an application file (default: `.go`, configurable with `-e` flag). If
it was, then it will rebuild your application and relaunch it. If the changed
file was in the assets, it will issue a reload after a short amount of time.
The dev server uses a technique called "debouncing" which means it doesn't
reload straight away, as there may be multiple files changed in a short amount
of time. When a trigger occurs, it waits for a set amount of time before issuing
a reload. If another trigger happens, it resets to the wait time again. By
default this value is `100ms`. If this value doesn't work for your project, it
can be configured using the `-debounce` flag. If used, this value will be saved
to your project config and become the default.
## External Dev Server
Some frameworks come with their own live-reloading server, however they will not
be able to take advantage of the Wails Go bindings. In this scenario, it is best
to run a watcher script that rebuilds the project into the build directory,
which Wails will be watching. For an example, see the default svelte template
that uses [rollup](https://rollupjs.org/guide/en/). For
[create-react-app](https://create-react-app.dev/), it's possible to use
[this script](https://gist.github.com/int128/e0cdec598c5b3db728ff35758abdbafd)
to achieve a similar result.
## Go Module
The default Wails templates generate a `go.mod` file that contains the module
name "changeme". You should change this to something more appropriate after
project generation.