mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 04:40:41 +08:00

* Fix typos * Update howdoesitwork.mdx * Added entry to changelog --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
313 lines
10 KiB
Plaintext
313 lines
10 KiB
Plaintext
# 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)
|
|
}
|
|
}
|
|
```
|
|
|
|
Also you might want to use Enums in your structs and have models for them on frontend.
|
|
In that case you should create array that will contain all possible enum values, instrument enum type and bind it to the app:
|
|
|
|
```go {16-18} title="app.go"
|
|
type Weekday string
|
|
|
|
const (
|
|
Sunday Weekday = "Sunday"
|
|
Monday Weekday = "Monday"
|
|
Tuesday Weekday = "Tuesday"
|
|
Wednesday Weekday = "Wednesday"
|
|
Thursday Weekday = "Thursday"
|
|
Friday Weekday = "Friday"
|
|
Saturday Weekday = "Saturday"
|
|
)
|
|
|
|
var AllWeekdays = []struct {
|
|
Value Weekday
|
|
TSName string
|
|
}{
|
|
{Sunday, "SUNDAY"},
|
|
{Monday, "MONDAY"},
|
|
{Tuesday, "TUESDAY"},
|
|
{Wednesday, "WEDNESDAY"},
|
|
{Thursday, "THURSDAY"},
|
|
{Friday, "FRIDAY"},
|
|
{Saturday, "SATURDAY"},
|
|
}
|
|
```
|
|
|
|
In the main application configuration, the `EnumBind` key is where we can tell Wails what we want to bind enums as well:
|
|
|
|
```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,
|
|
},
|
|
EnumBind: []interface{}{
|
|
AllWeekdays,
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
This will add missing enums to your `model.ts` file.
|
|
|
|
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 specifying `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/).
|
|
|
|
### Create React App
|
|
|
|
The process for a Create-React-App project is slightly more complicated. In order to support live frontend reloading the following configuration
|
|
needs to be added to your `wails.json`:
|
|
|
|
```json
|
|
"frontend:dev:watcher": "yarn start",
|
|
"frontend:dev:serverUrl": "http://localhost:3000",
|
|
```
|
|
|
|
The `frontend:dev:watcher` command will start the Create-React-App development server (hosted on port `3000` typically). The `frontend:dev:serverUrl` command then
|
|
instructs Wails to serve assets from the development server when loading the frontend rather than from the build folder. In addition to the above, the
|
|
`index.html` needs to be updated with the following:
|
|
|
|
```html
|
|
<head>
|
|
<meta name="wails-options" content="noautoinject" />
|
|
<script src="/wails/ipc.js"></script>
|
|
<script src="/wails/runtime.js"></script>
|
|
</head>
|
|
```
|
|
|
|
This is required as the watcher command that rebuilds the frontend prevents Wails from injecting the required scripts. This circumvents that issue by ensuring
|
|
the scripts are always injected. With this configuration, `wails dev` can be run which will appropriately build the frontend and backend with hot-reloading enabled.
|
|
Additionally, when accessing the application from a browser the React developer tools can now be used on a non-minified version of the application for straightforward
|
|
debugging. Finally, for faster builds, `wails dev -s` can be run to skip the default building of the frontend by Wails as this is an unnecessary step.
|
|
|
|
## 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.
|