- Published:
- By:
- Colin Tester
Build backwards compatible apps with Go and Wails.
Wails was created to make it easier for developers to create desktop apps using Go and web technologies.
A main feature of Wails is that it uses the OS native rendering engine, e.g. WebKit WebView on Mac OS, in order to present HTML views and run client-side JavaScript. An approach different from, say, Electron, which embeds Chromium and Node.js directly into the final application binary. Wails' approach, therefore, results in a much smaller file size for the app binary.
Wails uses Vite – a fast build tool for web technologies – to bundle and watch client-side JavaScript as it is developed. Vite's default operating mode is to parse client code to run on browsers that support ECMAScript 2015+.
However, I needed to be sure that the apps I intend to share would run on both current and older desktop platforms. So, my approach in this article, was to configure Vite to produce client code that will be backwards compatible.
Ultimately, I wished to explore how the experience of developing with Wails could be tailored, restructuring the folder and files Wails creates automatically; in order to accommodate my operational preferences.
I primarily use an Apple Mac and the following article outlines the steps I took to provide a coherent working environment for developing desktop apps with Go and Wails.
An example project to support this article is available on Github
Wails Installation
Wails requires two dependencies, Go 1.18+ and NPM (Node15+), so make sure they are installed before continuing.
One should also view the Wails installation document for platform specific guidance.
To install Wails, type the following into the CLI:
Latest version at the time of writing is: v2.3.1
Once installation has completed, it is recommended to run the Wails system check, making sure the correct Wails dependencies are also installed. At the CLI, type the following:
Wails app project initialisation
This section is about allowing Wails to initialise a project workspace.
Create the project folder in the workspace – substitute your own project name for ‘project-name’.
Initialise the new project workspace using Wails.
Flags used with the init command:
-n
Set the name of the project – this is mandatory.-d
followed by the project directory; I specified ‘.’ meaning the current directory.
Wails will create the following folder and files structure.
Set up JavaScript dev environment
For this project, I wish to develop views using sass files, bundled into a single css asset. I also wish to structure the project's JavaScript, CSS and image files under a sub-directory; maintaining an organisation and working convention.
- Within the
frontend
folder, remove thesrc
folder and its files. - Rename the
frontend
folder tosrc
. - Within the renamed ‘src’ folder create folders for:
js
,sass
andimage
, these will hold the JavaScript, CSS and view images respectively. - Within
src/js
create a new file labelled:index.js
. Within
src/sass
create a new file labelled:index.sass
.Our
src
folder should be similar to the following:
The src/index.html
created by Wails is used to present the view and content within the desktop app window.
Edit the
src/index.html
file, adding to the HTML<head>
element a link to our newsrc/sass/index.sass
file.Lower down in the same
src/index.html
file, modify the script tag to source thejs/index.js
file.Now back in the CLI, change working directory to
src
.
Vite uses RollUp to bundle JavaScript code. In order to get the desired backwards compatibility, we need to utilise a Babel plugin.
At the CLI, install the following npm modules as dev dependencies:
- vite@latest
- sass
- @rollup/plugin-babel
- @babel/plugin-proposal-class-properties
- @babel/plugin-proposal-nullish-coalescing-operator
- @babel/plugin-proposal-optional-chaining
- @babel/plugin-syntax-dynamic-import
e.g.
$ npm i -D vite@latest
Repeat for each listed npm module.Open the
src/package.json
file, which should indicate the installed devDependencies:Note: that installed version numbers indicated in your package.json file may differ from those above.
We now need to let Vite know that the RollUp and Babel dependencies are to be used when bundling JavaScript files.
Create a Babel configuration file:
src/babel.config.json
and place within it the following JSON:Now create a Vite configuration file:
src/vite.config.js
and place within it the following JavaScript:
We have now configured Vite to utilise a plugin for the Babel compiler to convert modern ECMAScript code into a backwards compatible version of JavaScript, specifically using the @babel plugins defined in the src/babel.config.json
file.
Adding a custom icon for our Desktop app
Our app will need an icon, giving it a unique visual identity wherever it is viewable on a target platform, e.g. the Mac OS Dock and Windows Taskbar.
Create a PNG image file (1024 × 1024 pixels) containing your custom icon artwork and place it within the
build
folder, replacing the existing file:appicon.png
.Wails will use
appicon.png
as the application icon when building desktop app binaries.Whilst in the
build
folder, remove both thedarwin
andwindows
folders. These will be rebuilt by Wails later, at which point our custom icon file will be utilised.
Set up Go dev environment
Wails generates bindings for Inter-process Communication (IPC) between Go application code and JavaScript code.
- Within the root of the project folder, edit the
go.mod
file changing themodule
name, ‘changeme’ declared at the top, to something relevant to you and the project, e.g.project-domain/project-name
– substituting your own project name. - Remove the
app.go
file. - Create a folder labelled: ‘go’. This folder will hold all the app Go packages and files.
- Within the new
go
folder, create a new subfolder labelled:IPC
(Inter-process Communication), and within that a file labelled:service.go
. Open the
go/IPC/service.go
file and add to the top:package IPC
.The Service will act as the main pipe through which all communications will be handled between the Go and JavaScript code.
Within
service.go
append the following:From the project's root folder, open to edit the
main.go
file.To keeps things simple at this level, the
main.go
file will contain only the definition of options for Wails and its instantiation, it will also import our IPC service API.We are going to modify the code generated by the Wails initialisation process.
Edit the line embedding the assets for the client view to make sure it now points to our renamed
src
folder: substitutesrc
forfrontend
making the embed command read as//go:embed all:src/dist
.The
dist
folder is created by Vite and used by Wails to pull our client-side assets into the desktop app.We wish also to embed our custom icon into our app binary, so that it can be used within desktop views, e.g. within an ‘About’ view. Add to the
main.go
file the lines:Within the
func main()
remove all the contained code and then insert the following line:We also need to make sure we add to the upper part of
main.go
an import statement for:Further within the
func main()
we will configure the Wails app. Add the following tofunc main()
:We also need to make sure we import the required packages:
Connecting up the IPC pipe
Wails, when generating bindings for IPC, builds JavaScript files within which the dependencies are defined. These dependencies are determined from exported (public) properties and methods defined within the Go app. They can be imported to the client code as needed.
However, on the client-side, the IPC is also represented by a global object assigned to the window
object; this is how dependencies are referenced directly by the Wails generated JavaScript file.
We have, therefore, two ways to reference our IPC service, either by importing it from the Wails generated JavaScript file or directly off the window
object. I favour using the latter.
We can now, within our src/js/index.js
file, establish a connection to the Go app IPC service.
Within the
src/js
folder open to editindex.js
and add the following:In the example code above, the
service
reference is a global but could be assigned any way deemed appropriate.Any methods exported via the Go app service can be accessed via the
service
reference, e.g. to send a request to the Go runtime – using the Service API Request method – we would use:service.Request()
.
Let's add some more to our src/index.js
file to make a simple request to the Service IPC to get content for our app main view.
Append to
src/index.js
the following:When the Service request is made, we would expect the Promise to resolve with a response object returned from the Service IPC written in Go. The response object carries data with a
content
property value, which is set as the text inside our referencedapp
HTML element.
Let's also add some simple styling for our app view.
Open to edit
src/sass/index.sass
and add the following:
Configure Wails for building our app
From the project root folder, open to edit the Wails configuration file:
wails.json
.Change the fields:
name
andoutputfilename
to a value you wish to use.Add fields for:
frontend:dir
andreloaddirs
setting each with the value of"src"
. These tell Wails the name of our client code folder, in which it observes the Vitedist
directory and that files within thesrc
folder should be watched for changes.The
wails.json
file should now include the following properties and values:
Let's now take the opportunity to test that all is working Ok.
On the CLI in the root of the project folder, i.e. change directory up one level from
src
, type:Wails will go through steps to prepare the client-side code, run Vite, then build and run a desktop app.
As a result you should see a desktop window open for your app, similar to figure 1 below.
Wails will now operate in watching mode; watching for changes to the app files and assets. You can now make changes to the JavaScript and Go code and Wails will either push those changes to the app or rebuild the app, as needed.
You can exit out of watching mode by either quitting the open app, or pressing Ctrl+C
in the CLI.
Build a final binary
With the development phase working, let's now build a final binary file, one each to run on Mac and Windows.
On the CLI in the root of the project folder, type the build command:
Flags used with the build command:
-clean
removes previous builds before compiling.-platform
followed by the platform types, separated by a comma, informs Wails which OS platforms to build binaries for. See more information on the supported platforms.-o
followed by a filename, specifies the output filename and extension to use, although this appears to apply only to Windows binaries.
The folder build/bin
should contain two versions of your app: one for Mac and another .exe
file for Windows.
Summary
The article outlined:
- Installing and configuring Wails.
- Configuring Vite and its JavaScript bundling options.
- Establishing an IPC (Inter-process Communcation) service between Go and JavaScript.
- Adding an application custom icon.
- Building final binaries for Mac and Windows.
It would be worth exploring further the Wails configuration options, particularly how to configure application menus, and the Wails runtime library.
I hope this article has been helpful for you to begin developing desktop apps with Go and Wails.
Article relevant links:
- Example project files on Github to support this article.
- Go All releases downloads page: https://go.dev/dl/
- Node Downloads page: https://nodejs.org/en/download/
- Wails website: https://wails.io/
- Wails Introduction: https://wails.io/docs/introduction
- Wails Installation: https://wails.io/docs/gettingstarted/installation
- Configuring Vite: https://vitejs.dev/config/
- Rollup Plugins: https://vite-rollup-plugins.patak.dev/
- What is Babel? https://babeljs.io/docs/