Go Tips: WebAssembly and Vugu
Tags: go, webassembly, vugu
Published: 2021-02-09 11:05:37

I've been learning [Rust](https://www.rust-lang.org/) and [WebAssembly](https://webassembly.org/) in my free time for a few months already, nothing really crazy to be honest, just reading some docs, articles and blogs, watching some videos and attending a few live seminars to understand more. Rust and WebAssembly are closely related to each other and in practice _I should be_ learning Rust first, but the truth is that most of my time has been dedicated to explore WebAssembly more deeply, specifically learning about the progress already made in Go as well as some ways to interact with WebAssembly using different packages and whatnot.

There are a lot of good articles and videos that cover really interesting technical things about how Go and WebAssembly work:

* [Go's official wiki](https://github.com/golang/go/wiki/WebAssembly), 
* [Go and WebAssembly: running Go programs in your browser](https://blog.gopheracademy.com/advent-2018/go-in-the-browser/) by Agniva De Sarker, with a nice [live example](https://agniva.me/shimmer/),
* [A Journey into WebAssembly](https://alexlab.me/writings/webassembly-journey/) by Alex Lab,
* [An introduction to WebAssembly | DevNation Day 2020](https://www.youtube.com/watch?v=39bRX8PIXwo) by Guy Royse,
* [Level up your web apps with WebAssembly](https://learning.oreilly.com/videos/level-up-your/0636920335801/) by Robert Aboukhalil,
* [LondonGophers 15/08/2018: Get Going with WebAssembly](https://www.youtube.com/watch?v=iTrx0BbUXI4&t=4s) by Johan Brandhorst.

Funny enough during [GopherCon 2020](https://mariocarrion.com/2020/11/15/gophercon-govircon-2020.html) there was a presentation called [Go is Not Just on Your Server, it's in Your Browser: The Story of and Introduction to Vugu (Go+WebAssembly UI library)](https://www.gophercon.com/agenda/session/233435) by [Brad Peabody](https://www.gophercon.com/agenda/speakers/614176), that covered [`Vugu`](https://www.vugu.org/) _A modern UI library for Go+WebAssembly_ that really piqued my interested.

**And** funny enough (again!) I've been working, just recently, on an internal tool that happened to need some frontend code, the perfect excuse to start using Vugu for real.

I started about two weeks ago and I already released a working version of this internal tool that happens to be interacting with our backend APIs, everything works nicely to be honest, and the cool thing is that everything is written in Go.

However, because of the lack of experience using WebAssembly and Vugu I initially had some problems, let me elaborate more about how I solved those next. By the way all the code is available [on Gitlab](https://gitlab.com/MarioCarrion/blog-examples/-/tree/main/2020/12/20), feel fee to explore it and run it locally.

## Custom template for rendering Vugu Pages

The official documentation mentions [Full-HTML Mode support](https://www.vugu.org/doc/files/markup) but I haven't been able to figure it out, in the end my solution was to render this template through the server that is delivering all the assets:

```go
templ := fmt.Sprintf(/* here define the HTML template to be used */)

h := simplehttp.New(wd, false)
h.PageHandler = &simplehttp.PageHandler{
	Template:         template.Must(template.New("_page_").Parse(templ)),
	TemplateDataFunc: simplehttp.DefaultTemplateDataFunc,
}
```

Please refer to the final example, specifically the [definition of the template](https://gitlab.com/MarioCarrion/blog-examples/-/blob/78a99b43785cd521dadc9932ef78216ee3d41103/2020/12/20/server.go#L17-71) as well as how [this is being used](https://gitlab.com/MarioCarrion/blog-examples/-/blob/78a99b43785cd521dadc9932ef78216ee3d41103/2020/12/20/server.go#L106).

## wasm `main` arguments via Javascript.

A tiny bit related to the problem above. If we need to pass in information to the final wasm artifact we should be using something like:

```javascript
WebAssembly.instantiateStreaming(fetch("/main.wasm"), go.importObject).then((result) => {
    go.argv = ["main.wasm", "-parameter", "%s", "-parameter2", "something"];
    go.run(result.instance);
});
```

For example, in cases we need to indicate configuration details that happen to be only available via [environment variables](https://mariocarrion.com/2019/11/20/golang-aws-ssm-env-vars-package.html) through the original server.

## `/health` check using simplehttp

If you use `simplehttp` (via `github.com/vugu/vugu/simplehttp`) to build the server that delivers the assets, then you may need a way to define a `health-like` endpoint for allowing your autoscaling configuration to work, this is easily solved by implementing a wrapper for `PageHandler`, something like:

```go
health := func(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/health" {
			// do some actual health checks
			w.WriteHeader(http.StatusOK)
			return
		}

		h.ServeHTTP(w, r)
	})
}

h := simplehttp.New(wd, false)

h.PageHandler = health(&simplehttp.PageHandler{/* actual configuration */})
```

## Render smaller wasm files

To date, the final compiled wasm files are a bit large, to reduce their filesize you need a combination of two things:

1. To use [`brotli`](https://github.com/google/brotli) to compress the final wasm file and,
1. To use [`gzipped.FileServer`](https://github.com/lpar/gzipped) (via `github.com/lpar/gzipped/v2`) to deliver that compressed asset with the corresponding headers if the client supports them.

Something like the following should work:

```go
h.StaticHandler = gzipped.FileServer(gzipped.Dir(wd))
```

Don't forget about `http.StripPrefix` in case your requested path if different than the local one. 

For context, the final files look like this:

```sh
4.6M Dec 20 14:35 main.wasm
980K Dec 20 14:35 main.wasm.br
```

## Downloading a file.

Finally, this one is interesting because it involves passing data from Go to Javascript via the standard library. In the linked example, this is triggered when clicking the ["Download file!" button](https://gitlab.com/MarioCarrion/blog-examples/-/blob/78a99b43785cd521dadc9932ef78216ee3d41103/2020/12/20/root.vugu#L7-9):

```go
func (r *Root) HandleDownloadFile(e vugu.DOMEvent) {
	var output bytes.Buffer

	// literal copy/paste from the encoding/csv godoc

	records := [][]string{
		{"first_name", "last_name", "username"},
		{"Rob", "Pike", "rob"},
		{"Ken", "Thompson", "ken"},
		{"Robert", "Griesemer", "gri"},
	}

	w := csv.NewWriter(&output)

	for _, record := range records {
		if err := w.Write(record); err != nil {
			log.Fatalln("error writing record to csv:", err)
		}
	}

	w.Flush()

	if err := w.Error(); err != nil {
		log.Fatal(err)
	}

	// important part

	dst := js.Global().Get("Uint8Array").New(len(output.Bytes()))
	_ = js.CopyBytesToJS(dst, output.Bytes())
	js.Global().Call("downloadFile", dst, "text/csv", "usernames.csv")
}
```

The really important bit is the last 3 lines of the method

```go
dst := js.Global().Get("Uint8Array").New(len(output.Bytes()))
_ = js.CopyBytesToJS(dst, output.Bytes())
js.Global().Call("downloadFile", dst, "text/csv", "usernames.csv")
```

* [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is used because it represents an array of 8-bit unsigned integers, and recall `byte` is an alias to `uint8` therefore making them equivalent.
* `downloadFile()` is called to pass in the data from Go to original javascript function.

This allows passing in data from Go to Javascript which in the end triggers a download on the frontend side.

### Final thoughts

I'm really enjoying Vugu (and WebAssembly), I understand the well known limitations, like users require a modern browser, but somehow if you have control of who your users are then using Vugu for building web applications is a great excuse.