Thanks to WebAssembly, you can write Frontend on Go
- Tutorial
Original article.

In February 2017, a member of the go team Brad Fitzpatrick offered to make support for WebAssembly in the language. Four months later, in November 2017, the author GopherJS Richard Muziol began to implement the idea. And, finally, the full implementation was in the master. Developers will receive wasm around August 2018, with version 1.11 go . As a result, the standard library takes on almost all the technical difficulties with importing and exporting functions familiar to you, if you have already tried compiling C in wasm. It sounds promising. Let's see what can be done with the first version.

All examples in this article can be run from docker containers that are in the author's repository :
Then go to localhost : 32XXX /, and go from one link to another.
Creating a basic “hello world” and concepts is already fairly well documented (even in Russian ), so let's just move on to more subtle things more quickly.
The most necessary is a freshly compiled version of Go that supports wasm. I will not describe the installation step by step , just know what is needed already in the master.
If you don’t want to worry about it, Dockerfile c go is available in the golub-wasm repository on github , or even faster you can take an image from nlepage / golang_wasm .
Now you can write the traditional one
The environment variables GOOS and GOARCH are already set in the nlepage / golang_wasm image, so you can use a file
The final step is to use the files
You just need to give 3 static files using nginx, for example, then wasm_exec.html will display the “run” button (it will turn on only if it is
It is noteworthy that
You can use the nginx image from nlepage / golang_wasmwhich already includes the corrected MIME type,
Now click the “run” button, then open the console of your browser, and you will see the console.log greeting (“Hello Wasm!”).

A full example is available here .
Now that we have successfully launched the first WebAssembly binary file compiled from Go, let's take a closer look at the possibilities provided.
The new syscall / js package is included in the standard library, consider the main file -
A new type
It offers a simple API for managing JavaScript variables:
And additional interesting methods:
Instead of displaying a message in os.StdOut, let's display it in the alert window using
Since we are in the browser, the global scope is a window, so you first need to get alert () from the global scope:
Now we have a variable
As you can see, there is no need to call js.ValueOf () before passing Invoke arguments, it takes an arbitrary number
Now our new program should look like this:
As in the first example, you just need to create a file with the name
Now, when we press the “Run” button, an alert window appears with our message.
A working example is in the folder
Calling JS from Go is quite simple, let's take a closer look at the package
Let's try to do something simple: run Go
We'll make some changes to
This launches the wasm binary and waits for it to complete, then reinitializes it for the next run.
Let's add a new function that will receive and save the Go callback and change the state
Now let's adapt the function
And this is on the JS side!
Now in the Go part you need to create a callback, send it to the JS side and wait for the function to be needed.
Then must write a real function
The arguments are passed through the slice
Now we can wrap this function in a callback:
Then call the JS function
The last thing to do is wait for the callback call in main:
This last part is important because the callbacks are executed in a dedicated goroutine, and the main goroutine must wait for a callback, otherwise the wasm binary will be stopped prematurely.
The resulting Go program should look like this:
As in the previous examples, create a file with the name
Now, when you press the “run” button, as in our first example, the message is printed in the browser console, but this time it is much better! (And more difficult.) The
working example in the baker docker file is available in the folder
The Go from JS call is a bit more cumbersome than the Go JS call, especially on the JS side.
This is mainly due to the fact that you need to wait for the result of the Go callback to be sent to the JS side.
Let's try something else: why not organize a wasm binary file that will not be completed immediately after a callback call, but will continue to work and receive other calls.
This time let's start from Go, and like in our previous example, we need to create a callback and send it to the JS side.
Add a call counter to track how many times the function has been called.
Our new feature
Creating a callback and sending it to the JS side is the same as in the previous example:
But this time we have no channel
This is not satisfactory, our binary wasm will just hang in memory until the browser tab closes.
You can listen to the event
This time, the new function
Then we wrap it up in a callback using
Finally, replace the empty blocking
The final program looks like this:
Previously, on the JS side, loading the wasm binary file looked like this:
Let's adapt it to run a binary file immediately after downloading:
And replace the “Run” button with a message field and a button to call
Finally, a function
Now, when we press the “Print message” button, we should see a message of our choice and a call counter printed in the browser console.
If we check the box “Preserve log” of the browser console and refresh the page, we will see the message “Bye Wasm!”.

Sources are available in a folder
As you can see, the learned
At the moment, it is not possible to return a value to JS directly from the Go callback.
It should be borne in mind that all callbacks are executed in the same goroutin, so if you do some blocking operations in the callback, do not forget to create a new goroutin, otherwise you will block all other callbacks.
All the basic functions of the language are already available, including concurrency. For now, all goroutins will work in the same thread, but this will change in the future .
In our examples, we used only the fmt package from the standard library, but everything is available that is not trying to escape from the sandbox.
It seems that the file system is supported through Node.js.
Finally, how about performance? It would be interesting to run some tests to see how Go wasm compares with equivalent pure JS code. Someone hajimehoshi made measurements, how different environments work with integers, but the technique is not very clear.

Do not forget that Go 1.11 has not even been officially released yet. In my opinion, very good for experimental technology. Those who are interested in performance tests may torment their browser .
The main niche, as the author notes, is the transfer of already existing go code from the server to the client. But with new standards, you can do completely offline applications , and the wasm code is saved in compiled form. It is possible to transfer many utilities to web, agree, it is convenient?

In February 2017, a member of the go team Brad Fitzpatrick offered to make support for WebAssembly in the language. Four months later, in November 2017, the author GopherJS Richard Muziol began to implement the idea. And, finally, the full implementation was in the master. Developers will receive wasm around August 2018, with version 1.11 go . As a result, the standard library takes on almost all the technical difficulties with importing and exporting functions familiar to you, if you have already tried compiling C in wasm. It sounds promising. Let's see what can be done with the first version.

All examples in this article can be run from docker containers that are in the author's repository :
docker container run -dP nlepage/golang_wasm:examples
# Find out which host port is used
docker container ls
Then go to localhost : 32XXX /, and go from one link to another.
Hi, wasm!
Creating a basic “hello world” and concepts is already fairly well documented (even in Russian ), so let's just move on to more subtle things more quickly.
The most necessary is a freshly compiled version of Go that supports wasm. I will not describe the installation step by step , just know what is needed already in the master.
If you don’t want to worry about it, Dockerfile c go is available in the golub-wasm repository on github , or even faster you can take an image from nlepage / golang_wasm .
Now you can write the traditional one
helloworld.go
and compile it with the following command:GOOS=js GOARCH=wasm go build -o test.wasm helioworld.go
The environment variables GOOS and GOARCH are already set in the nlepage / golang_wasm image, so you can use a file
Dockerfile
like this to compile:FROM nlepage/golang_wasm
COPY helloworld.go /go/src/hello/
RUN go build -o test.wasm hello
The final step is to use the files
wasm_exec.html
and those wasm_exec.js
available in the go repository каталоге misc/wasm
or in the docker nlepage / golang_wasm image in the directory /usr/local/go/misc/wasm/
for execution test.wasm
in the browser (wasm_exec.js expects a binary file test.wasm
, so use this name). You just need to give 3 static files using nginx, for example, then wasm_exec.html will display the “run” button (it will turn on only if it is
test.wasm
loaded correctly). It is noteworthy that
test.wasm
it is necessary to serve with the MIME type application/wasm
, otherwise the browser will refuse to execute it. (for example, nginx needs an updated mime.types file ). You can use the nginx image from nlepage / golang_wasmwhich already includes the corrected MIME type,
wasm_exec.html
and wasm_exec.js
in the code> / usr / share / nginx / html / directory. Now click the “run” button, then open the console of your browser, and you will see the console.log greeting (“Hello Wasm!”).

A full example is available here .
Call JS from Go
Now that we have successfully launched the first WebAssembly binary file compiled from Go, let's take a closer look at the possibilities provided.
The new syscall / js package is included in the standard library, consider the main file -
js.go
. A new type
js.Value
is available that represents the JavaScript value. It offers a simple API for managing JavaScript variables:
js.Value.Get()
andjs.Value.Set()
return and set the values of the object's fields.js.Value.Index()
andjs.Value.SetIndex()
access the object by index for read and write.js.Value.Call()
calls the object's method as a function.js.Value.Invoke()
calls the object itself as a function.js.Value.New()
calls the new operator and uses its own knowledge as a constructor.- A few more methods for getting the javascript value in the appropriate type go, for example
js.Value.Int()
orjs.Value.Bool()
.
And additional interesting methods:
js.Undefined()
will give js.Value appropriateundefined
.js.Null()
will give thejs.Value
appropriatenull
.js.Global()
returnsjs.Value
, giving access to the global scope.js.ValueOf()
accepts primitive go types and returns the correctjs.Value
Instead of displaying a message in os.StdOut, let's display it in the alert window using
window.alert()
. Since we are in the browser, the global scope is a window, so you first need to get alert () from the global scope:
alert := js.Global().Get("alert")
Now we have a variable
alert
, in the form js.Value
, which is a reference to window.alert
JS, and we can use the function call through js.Value.Invoke()
:alert.Invoke("Hello wasm!")
As you can see, there is no need to call js.ValueOf () before passing Invoke arguments, it takes an arbitrary number
interface{}
and passes values through ValueOf itself. Now our new program should look like this:
package main
import (
"syscall/js"
)
funcmain() {
alert := js.Global().Get("alert")
alert.Invoke("Hello Wasm!")
}
As in the first example, you just need to create a file with the name
test.wasm
, and leave wasm_exec.html
it wasm_exec.js
as it was. Now, when we press the “Run” button, an alert window appears with our message.
A working example is in the folder
examples/js-call
.Call Go from JS.
Calling JS from Go is quite simple, let's take a closer look at the package
syscall/js
, the second file to view is callback.go
.js.Callback
type wrapper for Go function, for use in JS.js.NewCallback()
a function that accepts a function (the receiving slicejs.Value
and not returning anything), and returnsjs.Callback
.- Some mechanics to manage active callbacks and
js.Callback.Release()
that should be called to destroy the callback. js.NewEventCallback()
similarlyjs.NewCallback()
, but the wrapped function takes only 1 argument - the event.
Let's try to do something simple: run Go
fmt.Println()
by JS. We'll make some changes to
wasm_exec.html
be able to get a callback from Go to call it.asyncfunctionrun() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.ImportObject); // сброс экземпляра
}
This launches the wasm binary and waits for it to complete, then reinitializes it for the next run.
Let's add a new function that will receive and save the Go callback and change the state
Promise
upon completion:let printMessage // Our reference to the Go callbacklet printMessageReceived // Our promiselet resolvePrintMessageReceived // Our promise resolver functionsetPrintMessage(callback) {
printMessage = callback
resolvePrintMessageReceived()
}
Now let's adapt the function
run()
to use callback:asyncfunctionrun() {
console.clear()
// Create the Promise and store its resolve function
printMessageReceived = newPromise(resolve => {
resolvePrintMessageReceived = resolve
})
const run = go.run(inst) // Start the wasm binaryawait printMessageReceived // Wait for the callback reception
printMessage('Hello Wasm!') // Invoke the callback await run // Wait for the binary to terminate
inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance
}
And this is on the JS side!
Now in the Go part you need to create a callback, send it to the JS side and wait for the function to be needed.
var done = make(chanstruct{})
Then must write a real function
printMessage()
:funcprintMessage(args []js.Value) {
message := args[0].Strlng()
fmt.Println(message)
done <- struct{}{} // Notify printMessage has been called
}
The arguments are passed through the slice
[]js.Value
, so you need to call js.Value.String()
in the first slice element to get the message in the Go line. Now we can wrap this function in a callback:
callback := js.NewCallback(printMessage)
defer callback.Release() // to defer the callback releasing is a good practice
Then call the JS function
setPrintMessage()
, just like when calling window.alert()
:setPrintMessage := js.Global.Get("setPrintMessage")
setPrintMessage.Invoke(callback)
The last thing to do is wait for the callback call in main:
<-done
This last part is important because the callbacks are executed in a dedicated goroutine, and the main goroutine must wait for a callback, otherwise the wasm binary will be stopped prematurely.
The resulting Go program should look like this:
package main
import (
"fmt""syscall/js"
)
var done = make(chanstruct{})
funcmain() {
callback := js.NewCallback(prtntMessage)
defer callback.Release()
setPrintMessage := js.Global().Get("setPrintMessage")
setPrIntMessage.Invoke(callback)
<-done
}
funcprintMessage(args []js.Value) {
message := args[0].Strlng()
fmt.PrintIn(message)
done <- struct{}{}
}
As in the previous examples, create a file with the name
test.wasm
. You also need to replace wasm_exec.html
our version, and wasm_exec.js
we can reuse it. Now, when you press the “run” button, as in our first example, the message is printed in the browser console, but this time it is much better! (And more difficult.) The
working example in the baker docker file is available in the folder
examples/go-call
.Long job
The Go from JS call is a bit more cumbersome than the Go JS call, especially on the JS side.
This is mainly due to the fact that you need to wait for the result of the Go callback to be sent to the JS side.
Let's try something else: why not organize a wasm binary file that will not be completed immediately after a callback call, but will continue to work and receive other calls.
This time let's start from Go, and like in our previous example, we need to create a callback and send it to the JS side.
Add a call counter to track how many times the function has been called.
Our new feature
printMessage()
will print the received message and the value of the counter:var no intfuncprintMessage(args []js.Value) {
message := args[0].String()
no++
fmt.Printf("Message no %d: %s\n", no, message)
}
Creating a callback and sending it to the JS side is the same as in the previous example:
callback := js.NewCallback(printMessage)
defer callback.Release()
setPrintMessage := js.Global().Get("setPrintMessage")
setPrIntMessage.Invoke(callback)
But this time we have no channel
done
to notify us of the termination of the main gorutin. One way could be to permanently block the main goroutin empty select{}
:select{}
This is not satisfactory, our binary wasm will just hang in memory until the browser tab closes.
You can listen to the event
beforeunload
on the page, you will need a second callback to receive the event and notify the main gorutina through the channel:var beforeUnloadCh = make(chanstruct{})
This time, the new function
beforeUnload()
will only accept the event as a single js.Value
argument:funcbeforeUnload(event js.Value) {
beforeUnloadCh <- struct{}{}
}
Then we wrap it up in a callback using
js.NewEventCallback()
and register it on the JS side:beforeUnloadCb := js.NewEventCallback(0, beforeUnload)
defer beforeUnloadCb.Release()
addEventLtstener := js.Global().Get("addEventListener")
addEventListener.Invoke("beforeunload", beforeUnloadCb)
Finally, replace the empty blocking
select
for reading from the channel beforeUnloadCh
:<-beforeUnloadCh
fmt.Prtntln("Bye Wasm!")
The final program looks like this:
package main
import (
"fmt""syscall/js"
)
var (
no int
beforeUnloadCh = make(chanstruct{})
)
funcmain() {
callback := js.NewCallback(printMessage)
defer callback.Release()
setPrintMessage := js.Global().Get("setPrintMessage")
setPrIntMessage.Invoke(callback)
beforeUnloadCb := js.NewEventCallback(0, beforeUnload)
defer beforeUnloadCb.Release()
addEventLtstener := js.Global().Get("addEventListener")
addEventListener.Invoke("beforeunload", beforeUnloadCb)
<-beforeUnloadCh
fmt.Prtntln("Bye Wasm!")
}
funcprintMessage(args []js.Value) {
message := args[0].String()
no++
fmt.Prtntf("Message no %d: %s\n", no, message)
}
funcbeforeUnload(event js.Value) {
beforeUnloadCh <- struct{}{}
}
Previously, on the JS side, loading the wasm binary file looked like this:
const go = new Go()
let mod, inst
WebAssembly
.instantiateStreaming(fetch("test.wasm"), go.importObject)
.then((result) => {
mod = result.module
inst = result.Instance
document.getElementById("runButton").disabled = false
})
Let's adapt it to run a binary file immediately after downloading:
(asyncfunction() {
const go = new Go()
const { instance } = await WebAssembly.instantiateStreaming(
fetch("test.wasm"),
go.importObject
)
go.run(instance)
})()
And replace the “Run” button with a message field and a button to call
printMessage()
:<inputid="messageInput"type="text"value="Hello Wasm!"><buttononClick="printMessage(document.querySelector('#messagelnput').value);"id="prtntMessageButton"disabled>
Print message
</button>
Finally, a function
setPrintMessage()
that accepts and stores a callback should be simpler:let printMessage;
functionsetPrintMessage(callback) {
printMessage = callback;
document.querySelector('#printMessageButton').disabled = false;
}
Now, when we press the “Print message” button, we should see a message of our choice and a call counter printed in the browser console.
If we check the box “Preserve log” of the browser console and refresh the page, we will see the message “Bye Wasm!”.

Sources are available in a folder
examples/long-running
on github.So what is next?
As you can see, the learned
syscall/js
API does its job and allows you to write complex things with a small amount of code. You can write to the author , if you know the easier way. At the moment, it is not possible to return a value to JS directly from the Go callback.
It should be borne in mind that all callbacks are executed in the same goroutin, so if you do some blocking operations in the callback, do not forget to create a new goroutin, otherwise you will block all other callbacks.
All the basic functions of the language are already available, including concurrency. For now, all goroutins will work in the same thread, but this will change in the future .
In our examples, we used only the fmt package from the standard library, but everything is available that is not trying to escape from the sandbox.
It seems that the file system is supported through Node.js.
Finally, how about performance? It would be interesting to run some tests to see how Go wasm compares with equivalent pure JS code. Someone hajimehoshi made measurements, how different environments work with integers, but the technique is not very clear.

Do not forget that Go 1.11 has not even been officially released yet. In my opinion, very good for experimental technology. Those who are interested in performance tests may torment their browser .
The main niche, as the author notes, is the transfer of already existing go code from the server to the client. But with new standards, you can do completely offline applications , and the wasm code is saved in compiled form. It is possible to transfer many utilities to web, agree, it is convenient?