Gophers not only look cute but also they are powerful.
I ❤️ statically typed languages and Golang is statically typed too.
Golang is syntactically similar to C, but with memory safety, Garbage Collection and CSP-style concurrency. - Wikipedia
We can compile Go into WebAssembly. This means we can write channels in Golang and run them on the browser.
WebAssembly in Golang is still in its early days. 🦄
Golang is very simple to write. Thus it is easy to compile the existing application straight into WebAssembly modules provided they don't have any file path or system level features which will not be available for WebAssembly during its runtime.
Hello World
Create a folder called go-wasm
with child folders out
and go
;
go-wasm/
|__ out/
|__ go/
Create a file called main.go
inside the go
folder and enter the following contents:
package main
func main() {
println("Hello World!!!")
}
Run the go file with go run go/main.go
. This will print Hello World
. Ain't that easy.
Now we can compile this into WebAssembly module and run as a WebAssembly.
GOOS=js GOARCH=wasm go build -o out/main.wasm go/main.go
This will generate the main.wasm
inside the out
folder. Unlike Rust, Golang does not generate any binding file. We can use the binding file available from the TinyGo's official repository here.
Download the file and move it inside the out
directory.
Create an index.html
file inside the out
directory and add the following contents:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Go wasm</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch('main.wasm'),go.importObject).then( res=> {
go.run(res.instance)
})
</script>
</body>
</html>
The index.html
loads the wasm_exec.js
file. Note that this file works for both Browser and NodeJS environment. It exports a Go
object.
Then we add a local script. Inside the script we do the following:
- Instantiate
Go
from thewasm_exec.js
. - Fetch the WebAssembly Module using instantiateStreaming
Run the local server from the folder to see the Hello World
.
But wait, we are writing Golang. Golang makes it fairly easy to write a simple WebServer in Golang. Let us write one.
Create a file called webServer.go
in the root directory with the following contents.
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./out"
func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Cache-Control", "no-cache")
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
This serves the files inside the out
folder. Now run the server using go run webServer.go
. 🎉 Isn't that easy?
Head over to the http://localhost:8080
and open the console to see the awesome Hello World
.
The directory structure should look like below.
├── go
│ └── main.go
└── out
├── index.html
├── wasm_exec.js
└── main.wasm
It is very important to note the file sizes of the generated WebAssembly module. And it is whooping 1.3MB for just a Hello World. That is huge right.
-rw-r--r-- 1 sendilkumar staff 482B Jul 5 23:20 index.html
-rwxr-xr-x 1 sendilkumar staff 1.3M Jul 5 23:19 main.wasm
-rw-r--r-- 1 sendilkumar staff 13K Jul 5 23:18 wasm_exec.js
Any performance WebAssembly might give, it is not desirable to have it if the modules are huge.
Don't worry we have Teeny TinyGO.
Tiny Go
The TinyGo is a project built to take Golang into microcontrollers and modern web browsers. They have a brand new compiler that compiles based on LLVM. With TinyGo we can generate teeny tiny libraries that are optimized to execute in the chips.
Check out how to install TinyGo here.
Once installed, you can use TinyGo to compile any Golang code. We can compile Golang into WebAssembly module using the following command.
tinygo build -o out/main.wasm -target wasm ./go/main.go
Now go to the browser and refresh the page to see Hello World
still printed.
The most important thing is the generated WebAssembly module is just 3.8K 🎉 🎉 🎉
-rw-r--r-- 1 sendilkumar staff 482B Jul 5 23:20 index.html
-rwxr-xr-x 1 sendilkumar staff 3.8K Jul 5 23:29 main.wasm
-rw-r--r-- 1 sendilkumar staff 13K Jul 5 23:18 wasm_exec.js
Since we have LLVM underneath we can tweak it further using the -opt
flag. The maximum size optimization is obtained with the -opt=z flag and TinyGo
uses this by default. Because those tiny devices have limited memory.
Keep tuning in the next post, let us re-create the classic Dev's offline page using TinyGo and WebAssembly.
I hope this gives you a motivation to start your awesome WebAssembly journey. If you have any questions/suggestions/feel that I missed something feel free to add a comment.
You can follow me on Twitter.
If you like this article, please leave a like or a comment. ❤️
Top comments (5)
tinygo bundle sizes look very promising, however didn't work for me. After compiling to main.wasm with tinygo I get the following error in Chrome console: 'Import #0 module="env" error: module is not an object or function'. What could be wrong here?
Ahh.. sorry I have to update it. Can you pull the wasm_exec.js from the tinygo repo instead of the attached file
Updated the post
It's working, thanks a lot for such a quick response! Btw, are there any limitations imposed by tinygo? Is it capable to compile any Go code to .wasm?
Yeah there are some limitations. I am drafting a post on that. I am also experimenting a bit. Sooner will have a complete list.
Great post. Couldn't work around the first example though. Apparently this works different on Linux archs because here it is suggested we generate wasm_exec.js by doing
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
. That did the trick for me.