Minimizing Go WebAssembly Binary Size
Minimizing Go WebAssembly Binary Size
https://dev.bitolog.com/minimizing-go-webassembly-binary-size/
![](https://dev.bitolog.com/wp-content/uploads/2020/10/wasm-size.png)
In both of the previous posts I’ve described different aspects of WebAssembly. The first of the two explained what WebAssembly is and how to run a simple Go WebAssembly application in the browser. The second post was a Go WebAssembly performance benchmark. If you are unfamiliar with Go WebAssembly, I’d recommend reading the first post before diving into this one. In this post I’m going to discuss the Go WebAssembly binary size.
While working with Go WebAssembly I’ve noticed that the .wasm
binary files are quite big. Not huge for my simple tests, but several MBs for a just a few functions.
That made me ask myself – is that the best we can get assuming we want
these files to be served over the web? In this blog post I’ll show you
how to minimize your Go WebAssembly binary size so it will be much more
web friendly.
Current State
Going back to my encoding example from the introductory post, you can see that the encode.wasm
Go WebAssembly binary file size is roughly 2MB:
![Current Go WebAssembly binary size](https://dev.bitolog.com/wp-content/uploads/2020/10/wasm-size-1-1.png)
Currently the encoding function code imports only two packages:
import (
"encoding/base64"
"syscall/js"
)
If I was to import another package, let’s say the fmt package, how do you think that will affect the .wasm
binary file size? let’s try it out:
![Go WebAssembly binary size with when adding the fmt package](https://dev.bitolog.com/wp-content/uploads/2020/10/wasm-size-fmt.png)
That’s a lot. Importing a simple and basic package such as fmt
costs us roughly 0.4MB. A takeaway from this is that you need to keep in mind that packages imports leads to a larger binary file.
For the rest of this post, I’ll remove the fmt
package and work with the original code.
First Try – Serving Gzipped Content
For my first attempt to reduce the .wasm
file size, I ran my server with a gzip handler. This means that every file being served to the browser is first compressed using gzip on the server side, and extracted by the browser. You can read more about the method of serving compressed files here.
For the purpose of this test I used the gziphandler package in order to create a handler that compresses server responses:
package main
import (
"github.com/NYTimes/gziphandler"
"log"
"net/http"
)
func main() {
err := http.ListenAndServe(":8080",
gziphandler.GzipHandler(http.FileServer(http.Dir("/path/to/served/files"))))
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
Now let’s see how much do we benefit by doing so:
![Go WebAssembly binary size with when gzipped](https://dev.bitolog.com/wp-content/uploads/2020/10/wasm-size-gzip.png)
Well this is not bad at all. Gzipping the content reduced the file size by about 75% from 2MB to 0.5MB. You can see that the time it takes to serve the file increased dramatically, that is because the gzip extraction process is more complex than simply serving static files.
Can we do it better?
Second Try – TinyGo
From the official homepage:
TinyGo is a project to bring the Go programming language to microcontrollers and modern web browsers by creating a new compiler based on LLVM.
[…] It can also be used to produce WebAssembly (WASM) code which is very compact in size.
TinyGo hompage – https://tinygo.org/
In other words – TinyGo is a Go compiler, focused on creating small binaries. Since TinyGo does not aim to compile any possible Go program, it lacks some language and packages support. In other words, this means that you might need to modify your code in order for it to be compiled by TinyGo.
Luckily for me, both of the packages my code imports are fully supported by TinyGo:
![](https://dev.bitolog.com/wp-content/uploads/2020/10/tinygo-headline.png)
![](https://dev.bitolog.com/wp-content/uploads/2020/10/tinygo-base64-support2.png)
![](https://dev.bitolog.com/wp-content/uploads/2020/10/tinygo-syscalljs-support2.png)
Now that we understand it’s purpose, let’s give it a try.
I chose to install TinyGo on my machine, you can also use a Docker image if you don’t want to install it locally. The official documentation on using TinyGo for WebAssembly is available here.
Compiling Using TinyGo
Now I needed to compile my main.go
file to encode.wasm
using the TinyGo compiler. This will replace the older 2MB file:
$ tinygo build -o encode.wasm -target wasm main.go
After creating the binary file, I then tried refreshing my browser session, only to get the following weird error:
Uncaught (in promise) TypeError: WebAssembly.instantiate(): Import #0 module="wasi_unstable" error: module is not an object or function
Well, turns out that the JavaScript glue code (which we talked about) used by the TinyGo compiler is a different one. So I had to replace it:
$ cp $(tinygo env TINYGOROOT)/targets/wasm_exec.js .
Now refreshing worked, and so did my basic application!
Let’s take a look a the file size: **drums**
![Go WebAssembly binary size with when compiled with TinyGo](https://dev.bitolog.com/wp-content/uploads/2020/10/wasm-size-tinygo.png)
Amazing! We got our .wasm
binary file down from 2MB to 86KB.
Key Takeaways
- Go WebAssembly is still in early stages but as you can see it’s quite ready to handle various use cases.
- Decreasing the binary file size can be a bit tricky for larger applications but is feasible.
- I would definitely recommend experimenting with this interesting technology which, in my opinion, we’ll see more and more of in the future as it matures.
As always, feel free to comment, like and share. See you on the next one!