DEV Community

Cover image for (Tiny)Go to  WebAssembly
Sendil Kumar N
Sendil Kumar N

Posted on • Updated on • Originally published at sendilkumarn.com

(Tiny)Go to WebAssembly

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/
Enter fullscreen mode Exit fullscreen mode

Create a file called main.go inside the go folder and enter the following contents:

package main

func main() {
    println("Hello World!!!")
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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:

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)
        }))
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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. ❤️

Discussion (5)

Collapse
timonweb profile image
Tim Kamanin 🚀

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?

Collapse
sendilkumarn profile image
Sendil Kumar N Author • Edited on

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

Collapse
timonweb profile image
Tim Kamanin 🚀

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?

Thread Thread
sendilkumarn profile image
Sendil Kumar N Author

Yeah there are some limitations. I am drafting a post on that. I am also experimenting a bit. Sooner will have a complete list.

Collapse
emaphp profile image
Emmanuel Antico

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.