The Road to Wails v3

Wails is a project that simplifies the ability to write cross-platform desktop applications using Go. It uses native webview components for the frontend (not embedded browsers), bringing the power of the world's most popular UI system to Go, whilst remaining lightweight.
Version 2 was released on the 22nd of September 2022 and brought with it a lot of enhancements including:
- Live development, leveraging the popular Vite project
- Rich functionality for managing windows and creating menus
- Microsoft's WebView2 component
- Generation of Typescript models that mirror your Go structs
- Creating of NSIS Installer
- Obfuscated builds
Right now, Wails v2 provides powerful tooling for creating rich, cross-platform desktop applications.
This blog post aims to look at where the project is at right now and what we can improve on moving forward.
Where are we now?
It's been incredible to see the popularity of Wails rising since the v2 release. I'm constantly amazed by the creativity of the community and the wonderful things that are being built with it. With more popularity, comes more eyes on the project. And with that, more feature requests and bug reports.
Over time, I've been able to identify some of the most pressing issues facing the project. I've also been able to identify some of the things that are holding the project back.
Current issues
I've identified the following areas that I feel are holding the project back:
- The API
- Bindings generation
- The Build System
The API
The API to build a Wails application currently consists of 2 parts:
- The Application API
- The Runtime API
The Application API famously has only 1 function: Run() which takes a heap of options which govern how the application will work. Whilst this is very simple to use, it is also very limiting. It is a "declarative" approach which hides a lot of the underlying complexity. For instance, there is no handle to the main window, so you can't interact with it directly. For that, you need to use the Runtime API. This is a problem when you start to want to do more complex things like create multiple windows.
The Runtime API provides a lot of utility functions for the developer. This includes:
- Window management
- Dialogs
- Menus
- Events
- Logs
There are a number of things I am not happy with the Runtime API. The first is that it requires a "context" to be passed around. This is both frustrating and confusing for new developers who pass in a context and then get a runtime error.
The biggest issue with the Runtime API is that it was designed for applications that only use a single window. Over time, the demand for multiple windows has grown and the API is not well suited to this.
Thoughts on the v3 API
Wouldn't it be great if we could do something like this?
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}
This programmatic approach is far more intuitive and allows the developer to interact with the application elements directly. All current runtime methods for windows would simply be methods on the window object. For the other runtime methods, we could move them to the application object like so:
app := wails.NewApplication(options.App{})
app.NewInfoDialog(options.InfoDialog{})
app.Log.Info("Hello World")
This is a much more powerful API which will allow for more complex applications to be built. It also allows for the creation of multiple windows, the most up-voted feature on GitHub:
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
myWindow2 := app.NewWindow(options.Window{})
myWindow2.SetTitle("My Window 2")
myWindow2.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}
Bindings generation
One of the key features of Wails is generating bindings for your Go methods so they may be called from Javascript. The current method for doing this is a bit of a hack. It involves building the application with a special flag and then running the resultant binary which uses reflection to determine what has been bound. This leads to a bit of a chicken and egg situation: You can't build the application without the bindings and you can't generate the bindings without building the application. There are many ways around this but the best one would be not to use this approach at all.
There was a number of attempts at writing a static analyser for Wails projects but they didn't get very far. In more recent times, it has become slightly easier to do this with more material available on the subject.
Compared to reflection, the AST approach is much faster however it is significantly more complicated. To start with, we may need to impose certain constraints on how to specify bindings in the code. The goal is to support the most common use cases and then expand it later on.
