Go 1.11 and WebAssembly

VERF.IO
VERF.IO
Sep 4, 2018 · 5 min read

This is a guide for everyone who wants to unleash the power of Go not only for backend, but for frontend logic too. Starting from Go 1.11, we can compile our Go code to the WebAssembly file, which will be executed by browser. Go code will be able to interact with DOM elements, and, since this code is compiled, it pretends to be much faster than pure JavaScript.

In our example we’ll build the service to generate QR codes for our customers.

Step 1. Install Go 1.11

Go to https://golang.org/dl/, download the proper binary file and install it. This step is super easy, I’m using Windows machine and it works like a charm even on it. To check the Go version use this command:

PS C:\barcode> go version
go version go1.11 windows/amd64

Step 2. Prepare required files

For quick start we’ll use a kind of html boilerplate for our project. Open the root Go folder (in my case C:\Go) and the navigate to ./misc/wasm folder:

PS C:\barcode> ls C:\Go\misc\wasm\* .Directory: C:\Go\misc\wasmMode                LastWriteTime         Length Name
---- ------------- ------ ----
-a---- 8/24/2018 8:38 PM 441 go_js_wasm_exec
-a---- 8/24/2018 8:38 PM 1046 wasm_exec.html
-a---- 8/24/2018 8:38 PM 11905 wasm_exec.js

Copy wasm_exec.html and wasm_exec.js to your project’s folder:

PS C:\medium\verfio\webassembly> cp  C:\Go\misc\wasm\wasm* .
PS C:\medium\verfio\webassembly> ls
Directory: C:\medium\verfio\webassemblyMode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/24/2018 8:38 PM 1046 wasm_exec.html
-a---- 8/24/2018 8:38 PM 11905 wasm_exec.js

Step 3. Edit the default html page

Open the wasm_exec.html file and add four text fields to identify our users. Let’s add these strings before the highlighted <button>:

...
First Name: <input type="text" id="first" name="first">
Last Name: <input type="text" id="last" name="last">
E-mail: <input type="text" id="mail" name="mail">
Phone: <input type="text" id="phone" name="phone">
<button onClick="run();" id="runButton" >Run</button>
...

We’d need also an element where is our operations log will be posted and another div element to host the image of QR code:


...
<button onClick="run();" id="runButton" disabled>Run</button>

<button onClick="clean();" id="clearButton">Clean</button>
<div id="target"> </div><div id="code">
<img id="qrcode" src="" />
</div>

First div is intended to display text messages, second will be used to display the QR code in the form of png image. `Clean` button will make both divs empty, and in order to do that we need to create the specific Clean() function. Let’s put it after the already existing run() function and we’ll comment out the clear command inside the run():

async function run() {
//console.clear();
await go.run(inst); inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
async function clean() {
document.getElementById("target").innerHTML = "";
document.getElementById("code").innerHTML = "";
}

Step 4. Create main.go to implement the logic

Inside this part we want to generate requests and send them to server side and interact with DOM elements to update their values.

package mainimport (
"log"
"net/http"
"net/url"
"syscall/js"
"github.com/dennwc/dom"
)
type writer dom.Element// Write implements io.Writer.
func (d writer) Write(p []byte) (n int, err error) {
node := dom.GetDocument().CreateElement("div")
node.SetTextContent(string(p))
(*dom.Element)(&d).AppendChild(node)
return len(p), nil
}
func main() {//get elements to interact with
t := dom.GetDocument().GetElementById("target")
i := dom.GetDocument().GetElementById("qrcode")
//read the First Name value
f := js.Global().Get("document").Call("getElementById", "first").Get("value").String()
//read the Last Name value
l := js.Global().Get("document").Call("getElementById", "last").Get("value").String()
//read the Mail value
m := js.Global().Get("document").Call("getElementById", "mail").Get("value").String()
//read the Phone value
p := js.Global().Get("document").Call("getElementById", "phone").Get("value").String()
//send the POST request to server and pass the variables
_, err := http.PostForm("./wasm_exec.html",
url.Values{"first": {f}, "last": {l}, "mail": {m}, "phone": {p}})
if err != nil {
log.Fatal(err)
}
// Generate the name of the QR code file
filename := f + l + ".png"
// Update the link to the QR code with a new filename
i.SetAttribute("src", filename)
// Log messages to the specific div
logger := log.New((*writer)(t), "", log.LstdFlags)
logger.Print("QR code is ready" + "./" + filename)
}

Step 5. Compile main.go to WebAssembly file

We need to change Go variables to instruct the compiler to do the thing:

PS C:\medium\verfio\webassembly> $env:GOARCH="wasm"; $env:GOOS="js"
PS C:\medium\verfio\webassembly> go build -o test.wasm main.go
PS C:\medium\verfio\webassembly> ls
Directory: C:\medium\verfio\webassemblyMode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 9/4/2018 11:29 AM 1501 main.go
-a---- 9/4/2018 11:30 AM 7513300 test.wasm
-a---- 9/4/2018 11:13 AM 1471 wasm_exec.html
-a---- 8/24/2018 8:38 PM 11905 wasm_exec.js
PS C:\medium\verfio\webassembly>

As you can see, test.wasm file is our compiled Go code which we want to run in user’s browser. The link to the file located inside the wasm_exec.html:

WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(async (result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
});

You don’t need to change anything else in this file.

Step 6. Create server.go file

In server.go we want to serve our files and also implement the logic to generate the QR code when POST method has been received.

package mainimport (
"fmt"
"image/png"
"log"
"net/http"
"os"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
)
func wasmHandler(w http.ResponseWriter, r *http.Request) {switch r.Method {
// serve the file during the GET
case "GET":
http.ServeFile(w, r, "wasm_exec.html")

//handle the POST to generate the QR code
case "POST":
// Call ParseForm() to parse the raw query
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
//initialize variables to produce the QR code
f := r.FormValue("first")
l := r.FormValue("last")
m := r.FormValue("mail")
p := r.FormValue("phone")
content := f + l + m + p
filename := f + l + ".png"
// barcod lib creates the QR code
qrCode, _ := qr.Encode(content, qr.M, qr.Auto)
// Scale the barcode to 200x200 pixels
qrCode, _ = barcode.Scale(qrCode, 200, 200)
// Create the file in root folder
file, _ := os.Create(filename)
defer file.Close()
png.Encode(file, qrCode)
// Default case
default:
fmt.Fprintf(w, "only GET and POST methods are supported.")
}
}
func main() { // Serve the entire directory
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.Dir(".")))
// Specific handler to process the POST request
mux.HandleFunc("/wasm_exec.html", wasmHandler)
log.Fatal(http.ListenAndServe(":3000", mux))
}

Step 7. Test it

To run the server use another terminal window (not the same where is GOOS and GOARCH variables where changed)

PS C:\medium\verfio\webassembly> go run .\server.go

Open the localhost:3000 in your browser and navigate to wasm_exec.html page, enter the required values to the form, push the Run button:

Try to generate couple more codes:

All the images are in the root folder of your server:

PS C:\medium\verfio\webassembly> lsDirectory: C:\medium\verfio\webassemblyMode                LastWriteTime         Length Name
---- ------------- ------ ----
-a---- 9/4/2018 11:54 AM 1049 AliceCooper.png
-a---- 9/4/2018 11:52 AM 1045 JohnDow.png
-a---- 9/4/2018 11:29 AM 1501 main.go
-a---- 9/4/2018 11:54 AM 1027 MattDamon.png
-a---- 9/4/2018 11:50 AM 1282 server.go
-a---- 9/4/2018 11:30 AM 7513300 test.wasm
-a---- 9/4/2018 11:52 AM 1417 wasm_exec.html
-a---- 8/24/2018 8:38 PM 11905 wasm_exec.js
PS C:\medium\verfio\webassembly>

That’s it. Stay tuned!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade