The Talent500 Blog
Wails & Go

Building Cross-Platform Desktop Applications with Wails

Wails is an innovative framework designed for developing cross-platform desktop applications by leveraging the power of the Go programming language alongside modern web technologies. This combination allows developers to create applications that are not only efficient but also feature rich, providing a seamless user experience.
Unlike ElectronJS, which embeds Chrome for rendering web content, Wails operates without this overhead, making it a lightweight and faster alternative. The framework includes a command-line interface (CLI) tool that simplifies the processes of generating, building, and bundling applications. With just one command, developers can package their projects into a single binary executable tailored for the target platform.
A typical Wails application comprises two main components:

  • Application Logic: This is written in Go.
  • Frontend User Interface: This consists of HTML, CSS, and JavaScript.

Upon launching the application, Wails initializes a webview that renders all HTML, CSS, and JavaScript content. The Go methods are accessible via bindings, allowing them to be called directly from JavaScript as if they were native functions. Additionally, Wails features an event-based communication system that enables events to be produced or consumed in either Go or JavaScript while transmitting data seamlessly.

Project Setup

To get started with Wails, follow these steps:

  1. Install Wails CLI: Open your terminal and install the latest version of the Wails CLI tool using the following command:
    bash
    go install github.com/wailsapp/wails/v2/cmd/wails@latest
  2. Generate a New Project: Create a new Wails project by executing:
    bash
    wails init -n todos_app

This command generates a directory structure where the frontend directory contains templates for basic HTML/CSS/JS files.

  1. Set Up Frontend with OJET: Delete the existing frontend directory and scaffold a new project using OJET CLI:
    bash
    ojet create frontend --template=basic --typescript --webpack
  2. Configure wails.json: At the root of your project is the wails.json file where you can configure project-level settings such as output file names and build commands for the frontend. Update the build, watcher, and server URL properties accordingly.

Wails utilizes Go’s embed feature to integrate files and folders into the application binary. When building an OJET app, a web directory is generated that needs to be embedded in the main application. Update the embed path in your main.go file to include this directory.

  1. Run Your Application: After completing the setup, you can run your application using:
    bash
    wails dev

This command builds and serves the OJET app while launching the desktop application. During development, you can debug it just like any web application by right-clicking to inspect elements.

Backend Code (Golang)

In the main.go file, developers can configure application settings such as height and width while disabling resizing options. The complete list of configuration options is available in Wails documentation.

One key option is “Bind,” which accepts a slice of structs whose public methods will be exposed to the frontend. By default, an app struct is exposed from app.go, where developers can define methods to invoke from OJET UI.Here’s an example of how to implement basic functionality in app.go:

go
package main

import (
"context"
"encoding/json"
"os"
"path/filepath"

"github.com/wailsapp/wails/v2/pkg/runtime"
)

type App struct {
ctx context.Context
fileName string
}

func NewApp() *App {
return &App{}
}

func (a *App) startup(ctx context.Context) {
a.ctx = ctx
userDir, _ := os.UserHomeDir()
appDir := filepath.Join(userDir, "todos_app")
_ = os.Mkdir(appDir, os.ModePerm)
a.fileName = filepath.Join(appDir, "todos.json")

runtime.EventsOn(ctx, "saveAll", func(data ...interface{}) {
if data != nil && data[0] != nil {
todos := []byte(data[0].(string))
os.WriteFile(a.fileName, todos, 0644)
}
})
}

func (a *App) GetAllTodos() []string {
var todos []string
content, err := os.ReadFile(a.fileName)
if err != nil {
return nil
}
err = json.Unmarshal(content, &todos)
if err != nil {
return nil
}
return todos
}

In this code snippet:

  • The startup method initializes application settings and registers an event named “saveAll” that writes data to a JSON file.
  • The GetAllTodos method reads from this JSON file and returns all todo items as a slice of strings.

After implementing these methods in Go code, generate bindings for the frontend using:

bash
wails generate module

This command creates both JavaScript and TypeScript type definition files under the frontend/wailsjs directory.

Frontend Code (OJET)

For the user interface components in OJET:

  • An <oj-input-text> field allows users to add new todo items.
  • An <oj-list-view> component displays all todos.
  • Each <oj-list-item> shows todo text along with a delete button.
  • A “Save All” button persists data to the JSON file.

Here’s an example of how this can be structured in appController.ts:

typescript
import * as ko from 'knockout';
import Context = require('ojs/ojcontext');
import 'ojs/ojbutton';
import { ojButton } from 'ojs/ojbutton';
import 'ojs/ojinputtext';
import 'ojs/ojlistitemlayout';
import 'ojs/ojlistview';
import ArrayDataProvider = require('ojs/ojarraydataprovider');
import * as App from './../../wailsjs/go/main/App';
import * as Runtime from './../../wailsjs/runtime/runtime';

class RootViewModel {
newTodo = ko.observable('');
todos = ko.observableArray<string>([]);
readonly todosDP = new ArrayDataProvider(this.todos);

constructor() {
Context.getPageContext().getBusyContext().applicationBootstrapComplete();
this.fetchTodos();
}

private fetchTodos = async () => {
const data = await App.GetAllTodos();
if (data) this.todos(data);
};

handleAddTodo = (_: ojButton.ojAction) => {
if (this.newTodo() && this.newTodo().trim()) {
this.todos.push(this.newTodo());
this.newTodo('');
}
};

handleDeleteTodo = (todo: string) => {
const idx = this.todos().findIndex((item) => item === todo);
if (idx !== -1) this.todos.splice(idx, 1);
};

handleSaveAll = (_: ojButton.ojAction) => {
Runtime.EventsEmit('saveAll', JSON.stringify(this.todos()));
};
}
export default new RootViewModel();
In this code:

  • The RootViewModel class manages todo items with observable arrays.
  • Methods are defined for adding and deleting todos as well as saving all items back to storage.

The HTML body structure includes components for displaying and managing todo items effectively.

Building the Executable

To finalize your application development process, build it using:

This command compiles your project into an executable file located in the build/bin directory. You can run this executable file to interact with your Todo app.Users can add todo items through the interface provided by OJET. Upon clicking save, a todos.json file will be created in their user home directory under todos_app, storing all added items persistently.By following these steps and utilizing Wails effectively, developers can create robust cross-platform desktop applications that leverage both Go’s performance capabilities and modern web technologies for an enhanced user experience.
Read more such articles from our Newsletter here.
0
Avatar

prachi kothiyal

Add comment