Wails is a framework that enables development of desktop applications using the Go programming language.
In this article, I will provide an in-depth look at the initial project structure when a new Wails application is created. By examining this structure, you will gain a better understanding of how Wails operates and what the development cycle entails.
Installing Wails #
Install Wails using go install
.
$ go install github.com/wailsapp/wails/v2/cmd/wails@latest
Initialize new project #
Now that wails cli installed, create a new project using wails init
.
$ wails init -t vanilla -n wails-hello-world
This will create a new folder wails-hello-world
with the following project strcuture.
āāā wails-hello-world/
āāā build/
āāā frontend/
āāā .gitignore
āāā app.go
āāā go.mod
āāā main.go
āāā README.md
āāā wails.json
Run the project #
From withing the project folder, run wails dev
and you should see the following application pop up.
Overview of Wails Architecture #
To fully grasp the inner workings of Wails, I strongly suggest reading the "How does it work?" section of the official Wails documentation.
Wails is primarily composed of two parts: a Go runtime and a Webkit engine for displaying the frontend. Yes, the frontend of the application is written in JavaScript.
Wails generates bindings between Go and JavaScript, making it easy for you to invoke Go code from the JavaScript side.
Here is a more detailed diagram at Wails's official website.
Project Strcuture #
Let's take a closer look at some of the files and directories within the project and gain an understanding of their purpose.
main.go #
āāā wails-hello-world/
āāā build/
āāā frontend/
āāā .gitignore
āāā app.go
āāā go.mod
āāā main.go š
āāā README.md
āāā wails.json
This is the entrypoint of the application. wails.Run
is the function to launch the application.
package main import ( "embed" "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() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ Title: "wails-hello-world", Width: 1024, Height: 768, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ app, }, }) if err != nil { println("Error:", err.Error()) } }
main.go
Frontend assets in Go binary #
I want to draw attention to a specific line.
//go:embed all:frontend/dist
var assets embed.FS
The above line loads the frontend files into the Go binary by utilizing file embedding. This forms the user interface for the application. Learn more about file embedding in Go here.
Go function bindings #
Another code snippet that is worth taking a closer look at.
err := wails.Run(&options.App{
...
Bind: []interface{}{
app,
},
})
This is how frontend bindings for the Go functions are generated. In this case, a function binding is generated for every public method on the app
struct. You can add as many structs to Bind
array as you want.
app.go #
āāā wails-hello-world/
āāā build/
āāā frontend/
āāā .gitignore
āāā app.go š
āāā go.mod
āāā main.go
āāā README.md
āāā wails.json
There is nothing special about this file, it contains a sample application struct that has one function (Greet
) that is accessible from the frontend (as discussed in the previous section on function bindings).
package main import ( "context" "fmt" ) type App struct { ctx context.Context } func NewApp() *App { return &App{} } func (a *App) startup(ctx context.Context) { a.ctx = ctx } func (a *App) Greet(name string) string { return fmt.Sprintf("Hello %s, It's show time!", name) }
app.go
frontend folder #
āāā wails-hello-world/
āāā build/
āāā frontend/ š
āāā .gitignore
āāā app.go
āāā go.mod
āāā main.go
āāā README.md
āāā wails.json
Now we come to the frontend
folder. In Wails, the UI is built using standard HTML, CSS, and JavaScript. This choice of technology can be seen as either positive or negative, depending on one's personal opinion. Personally, I would have preferred a solution built entirely in Go.
When creating a new Wails application, you can choose from a list of frontend templates such as React, Typescript, and Vue. For instance, to initialize a React template, run the following command.
wails init -n myproject -t react
Visit Create a Project section here on Wails website to see all the available templates.
In this article, we initialized the project with Vanilla JavaScript.
Below is the content of the frontend folder. If you have any prior experience in frontend development, most of these should look familiar to you.
.
āāā frontend/
āāā dist
āāā node_modules
āāā src/
ā āāā assets/
ā ā āāā fonts
ā ā āāā images
ā āāā app.css
ā āāā main.js
ā āāā style.css
āāā wailsjs/
ā āāā go
ā āāā runtime
āāā index.html
āāā package-lock.json
āāā package.json
index.html #
Pretty standard html, css and javascript code.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/> <title>wails-hello-world</title> </head> <body> <div id="app"></div> <script src="./src/main.js" type="module"></script> </body> </html>
frontend/index.html
import "./style.css"; import "./app.css"; import logo from "./assets/images/logo-universal.png"; import { Greet } from "../wailsjs/go/main/App"; document.querySelector("#app").innerHTML = ` <img id="logo" class="logo"> <div class="result" id="result">Please enter your name below š</div> <div class="input-box" id="input"> <input class="input" id="name" type="text" autocomplete="off" /> <button class="btn" onclick="greet()">Greet</button> </div> </div> `; document.getElementById("logo").src = logo; let nameElement = document.getElementById("name"); nameElement.focus(); let resultElement = document.getElementById("result"); // Setup the greet function window.greet = function () { // Get name let name = nameElement.value; // Check if the input is empty if (name === "") return; // Call App.Greet(name) try { Greet(name) .then((result) => { // Update result with data back from App.Greet() resultElement.innerText = result; }) .catch((err) => { console.error(err); }); } catch (err) { console.error(err); } };
frontend/src/main.js
wails folder #
.
āāā frontend/
āāā dist
āāā node_modules
āāā src/
ā āāā assets/
ā ā āāā fonts
ā ā āāā images
ā āāā app.css
ā āāā main.js
ā āāā style.css
āāā wailsjs/ š
ā āāā go
ā āāā runtime
āāā index.html
āāā package-lock.json
āāā package.json
An important thing to note is the frontend/wailsjs
folder. This is the folder that contains the Go-Javascript generated bindings and some runtime functionality.
The frontend/wailsjs/go/main/App.js
files contains the functions that are generated from the Bindings
section in the Go code.
// @ts-check // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ć MODIWL // This file is automatically generated. DO NOT EDIT export function Greet(arg1) { return window['go']['main']['App']['Greet'](arg1); }
frontend/wailsjs/go/main/App.js
The frontend/wailsjs/runtime/runtime.js
file holds the Wails runtime functionality, including logging and window management. You can examine it in more detail by visiting this link.
Ending #
These are the primary files that you will interact with when developing desktop applications using Wails. I hope this information was helpful and you learned something new.