I first wrote about using Go for WebAssembly (WASM) 6 years ago right before the release of Go 1.11 which was the first time Go supported compiling to WASM. Go's initial support for WASM had many limitations (some I listed in my initial article) which have since been addressed so I decided to revisit the topic with some updated example of using Go for WASM.
Being able to compile code to WASM now allow:
- Go programs to run in the browser.
- Go functions to be called by JavaScript in the browser.
- Go code to call JavaScript functions through syscall/js.
- Go code access to the DOM.
Setup
Go's official Wiki now has an article on the basics of using Go for WASM including how to set the compile target and setup.
A quick summary of the steps to the process:
Compile to WASM with the output file ending as wasm since it's likely that the mime type set in /etc/mime probably use the wasm extension.
> GOOS=js GOARCH=wasm go build -o <filename>.wasm
Copy the JavaScript support file to your working directory (wherever your http server will serve it from). It's necessary to use the matching wasm_exec.js for the version of the Go being used so maybe put this as part of the build script.
> cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .
Then add the following to the html file to load the WASM binary:
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
Keeping It Running
It is a good starting point but the Go code example is too simplistic. It only demonstrates that the WASM binary that is created by Go can be loaded by having write a line to the browser's console. The Go program basically gets loaded by the browser, prints a line and exits. Most of the time, it's probably desirable to have the WASM binary get loaded and stay running. This can be achieved by either having having a channel that keeps waiting:
c := make(chan struct{}, 0)
<- c
or even easier:
select {}
Have either of these at the end of main() will keep the program alive after it gets loaded into the browser.
In Place of JavaScript
Being able to access the DOM excites me the most because it allows me to avoid writing JavaScript followed by being able to run Go programs in the browsers. While I think the inter-op between Go and JavaScript is probably the most practical application, it's not something I've had to do much since I'm not a front-end developing doing optimizations or trying to reuse Go code between the front-end and back-end.
I don't mind using HTML for UI development or even CSS, I'm just personally not a fan of JavaScript. This isn't to say that it is bad, just I prefer other languages just like some people prefer C++, Java, Python, etc. I don't have fun writing JavaScript like I do with with Go if though I know JavaScript.
Take a basic example of a web app (index.html) with a button to illustrate:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
</head>
<body>
<button id="myButton">Click Me</button>
</body>
</html>
JavaScript is used to attach an event to it so that when the button is clicked, an alert message pops up:
// Select the button element
const button = document.getElementById('myButton');
// Attach an event listener to the button
button.addEventListener('click', function() {
alert('Button clicked!');
});
With WASM, the JavaScript can be replaced with Go code:
package main
import (
"honnef.co/go/js/dom/v2"
)
func main() {
document := dom.GetWindow().Document()
// Select the button element
button := document.GetElementByID("myButton")
// Attach an event listener to the button
button.AddEventListener("click", false, func(dom.Event) {
dom.GetWindow().Alert("Button clicked!")
})
select {}
}
In this case, the Go code looks extremely similar to the JavaScript code because I'm using the honnef.co/go/js/dom/v2 package. It is a Go binding to the JavaScript DOM APIs and I find that it makes it more convenient than using syscall/js directly.
Why do I prefer this over writing JavaScript especially when they look so similar? The main reason is that most code is not just calling an API. There's other logic that are implemented and for those, I can use Go and Go's libraries along with the benefits of a compiled and type safe language.
There are still things that needs to be considered before just using Go and WASM for general consumer production web app. The binary size can be large so it needs to be considered for your audience, but if I'm doing my own hobby project for myself or corporate apps where I know the user have fast connections, or if app performance and functionality outweighs the initial download and memory usage, I'd try to use it.
No comments:
Post a Comment