Introduction to Wails - Desktop Apps in Go - Project Structure

Author

Gurleen Sethi on 05 February 2023

Introduction to Wails - Desktop Apps in Go - Project Structure
Code Repository
Code realted to this article is stored in this repository.

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.

Go Init Wails Application

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.

Go Wails Architecture Diagram

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.

Table of Contents
Code Repository
Code realted to this article is stored in this repository.
Subscribe via email

Get notified once/twice per month when new articles are published.

Byte Byte Go Affiliate
TheDeveloperCafe
Copyright © 2022 - 2024 TheDeveloperCafe.
The Go gopher was designed by Renee French.