-
Notifications
You must be signed in to change notification settings - Fork 17.8k
WebAssembly
:toc: :toc-title: :toclevels: 2 :icons:
Go 1.11 added an experimental port to WebAssembly. Go 1.12 has improved some parts of it, with further improvements expected in Go 1.13.
WebAssembly is described on its https://webassembly.org[home page] as:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
If you're new to WebAssembly read the https://github.com/golang/go/wiki/WebAssembly#getting-started[Getting Started] section, watch some of the https://github.com/golang/go/wiki/WebAssembly#go-webassembly-talks[Go WebAssembly talks], then take a look at the https://github.com/golang/go/wiki/WebAssembly#further-examples[Further examples] below.
This page assumes a functional Go 1.11 or newer installation. For troubleshooting, see the https://github.com/golang/go/wiki/InstallTroubleshooting[Install Troubleshooting] page.
To compile a basic Go package for the web:
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
Set GOOS=js
and GOARCH=wasm
environment variables to compile
for WebAssembly:
$ GOOS=js GOARCH=wasm go build -o main.wasm
That will build the package and produce an executable WebAssembly module file named main.wasm. The .wasm file extension will make it easier to serve it over HTTP with the correct Content-Type header later on.
To execute main.wasm in a browser, we'll also need a JavaScript support file, and a HTML page to connect everything together.
Copy the JavaScript support file:
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Create an index.html
file:
<html>
<head>
<meta charset="utf-8">
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body></body>
</html>
If your browser doesn't yet support WebAssembly.instantiateStreaming
,
you can use a https://github.com/golang/go/blob/b2fcfc1a50fbd46556f7075f7f1fbf600b5c9e5d/misc/wasm/wasm_exec.html#L17-L22[polyfill].
Then serve the three files (index.html
, wasm_exec.js
, and
main.wasm
) from a web server. For example, with
https://github.com/shurcooL/goexec#goexec[`goexec`]:
$ goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))'
Or use your own https://play.golang.org/p/pZ1f5pICVbV[basic HTTP server command].
Note: for the goexec
command to work on Unix-like systems, you must add the PATH environment variable for Go to your shell's profile
. Go's getting started guide explains this:
Add /usr/local/go/bin to the PATH environment variable. You can do this by adding this line to your /etc/profile (for a system-wide installation) or $HOME/.profile:
export PATH=$PATH:/usr/local/go/bin
Note: changes made to a profile file may not apply until the next time you log into your computer
Finally, navigate to http://localhost:8080/index.html, open the
JavaScript debug console, and you should see the output. You can
modify the program, rebuild main.wasm
, and refresh to see new
output.
It's possible to execute compiled WebAssembly modules using Node.js rather than a browser, which can be useful for testing and automation.
With Node installed and in your PATH
, set the -exec
flag to the
location of go_js_wasm_exec
when you execute go run
or go test
.
By default, go_js_wasm_exec
is in the misc/wasm
directory of your
Go installation.
$ GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec"
PASS
ok example.org/my/pkg 0.800s
Adding go_js_wasm_exec
to your PATH
will allow go run
and go test
to work for js/wasm
without having to manually provide the -exec
flag each time:
$ export PATH="$PATH:$(go env GOROOT)/misc/wasm"
$ GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test
PASS
ok example.org/my/pkg 0.800s
- https://www.youtube.com/watch?v=4kBvvk2Bzis[Building a Calculator with Go and WebAssembly] (https://tutorialedge.net/golang/go-webassembly-tutorial/[Source code])
- https://www.youtube.com/watch?v=iTrx0BbUXI4[Get Going with WebAssembly]
See https://godoc.org/syscall/js.
Also:
-
An experimental new framework https://github.com/vugu/vugu[Vugu] is worth trying out, if you're looking for something like VueJS. 😄
-
https://github.com/norunners/vue[vue - The progressive framework for WebAssembly applications.]
-
https://github.com/dennwc/dom[A library for streamlining DOM manipulation] is in development.
-
There is a https://gowebapi.github.io/[binding generator] that can be used.
-
https://github.com/norunners/vert[vert - WebAssembly interop between Go and JS values.]
- A new https://github.com/markfarnan/go-canvas[canvas drawing library] - seems pretty efficient. ** https://markfarnan.github.io/go-canvas/[Simple demo]
You can use the net/http library to make HTTP requests from Go, and they will be converted to https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API[fetch] calls. However, there isn't a direct mapping between the fetch https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters[options] and the http https://golang.org/pkg/net/http/#Client[client] options. To achieve this, we have some special header values that are recognized as fetch options. They are -
-
js.fetch:mode
: An option to the Fetch API mode setting. Valid values are: "cors", "no-cors", "same-origin", navigate". The default is "same-origin". -
js.fetch:credentials
: An option to the Fetch API credentials setting. Valid values are: "omit", "same-origin", "include". The default is "same-origin". -
js.fetch:redirect
: An option to the Fetch API redirect setting. Valid values are: "follow", "error", "manual". The default is "follow".
So as an example, if we want to set the mode as "cors" while making a request, it will be something like:
req, err := http.NewRequest("GET", "http://localhost:8080", nil)
req.Header.Add("js.fetch:mode", "cors")
if err != nil {
fmt.Println(err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
// handle the response
Please feel free to subscribe to https://github.com/golang/go/issues/26769[#26769] for more context and possibly newer information.
- https://github.com/golang/go/wiki/Configuring-GoLand-for-WebAssembly[Configuring GoLand and Intellij Ultimate for WebAssembly] - Shows the exact steps needed for getting Wasm working in GoLand and Intellij Ultimate
If you run a newer version of Chrome there is a flag (chrome://flags/#enable-webassembly-baseline
) to enable Liftoff, their new compiler, which should significantly improve load times. Further info https://chinagdg.org/2018/08/liftoff-a-new-baseline-compiler-for-webassembly-in-v8/[here].
WebAssembly doesn't yet have any support for debuggers, so you'll
need to use the good 'ol println()
approach for now to display
output on the JavaScript console.
An official https://github.com/WebAssembly/debugging[WebAssembly Debugging Subgroup] has been created to address this, with some initial investigation and proposals under way:
- https://fitzgen.github.io/wasm-debugging-capabilities/[WebAssembly Debugging Capabilities Living Standard] (https://github.com/fitzgen/wasm-debugging-capabilities[source code for the doc])
- https://yurydelendik.github.io/webassembly-dwarf/[DWARF for WebAssembly Target] (https://github.com/yurydelendik/webassembly-dwarf/[source code for the doc])
Please get involved and help drive this if you're interested in the Debugger side of things. 😄
https://wasdk.github.io/wasmcodeexplorer/[WebAssembly Code Explorer] is useful for visualising the structure of a WebAssembly file.
- Clicking on a hex value to the left will highlight the section it is part of, and the corresponding text representation on the right
- Clicking a line on the right will highlight the hex byte representations for it on the left
Go releases prior to 1.11.2 https://github.com/golang/go/issues/27961[have a bug] which can generate incorrect wasm code in some (rare) circumstances.
If your Go code compiles to wasm without problem, but produces an error like this when run in the browser:
CompileError: wasm validation error: at offset 1269295: type mismatch: expression has type i64 but expected f64
Then you're probably hitting this error.
The solution is to upgrade to Go 1.11.2 or later.
- https://github.com/agnivade/shimmer[Shimmer] - Image transformation in wasm using Go
- https://github.com/stdiopt/gowasm-experiments[GoWasm Experiments] - Demonstrates working code for several common call types ** https://stdiopt.github.io/gowasm-experiments/bouncy[bouncy] ** https://stdiopt.github.io/gowasm-experiments/rainbow-mouse[rainbow-mouse] ** https://stdiopt.github.io/gowasm-experiments/repulsion[repulsion] ** https://stdiopt.github.io/gowasm-experiments/bumpy[bumpy] - Uses the 2d canvas, and a 2d physics engine. Click around on the screen to create objects then watch as gravity takes hold! ** https://stdiopt.github.io/gowasm-experiments/arty/client[arty]
- https://github.com/djhworld/gomeboycolor-wasm[Gomeboycolor-wasm] ** WASM port of an experimental Gameboy Color emulator. The https://djhworld.github.io/post/2018/09/21/i-ported-my-gameboy-color-emulator-to-webassembly/[matching blog post] contains some interesting technical insights.
- https://bobcob7.github.io/wasm-basic-triangle/[Basic triangle] (https://github.com/bobcob7/wasm-basic-triangle[source code]) - Creates a basic triangle in WebGL
- https://bobcob7.github.io/wasm-rotating-cube/[Rotating cube] (https://github.com/bobcob7/wasm-rotating-cube[source code]) - Creates a rotating cube in WebGL
- https://stdiopt.github.io/gowasm-experiments/splashy[Splashy] (https://github.com/stdiopt/gowasm-experiments/tree/master/splashy[source code]) - Click around on the screen to generate paint...
At present, Go generates large Wasm files, with the smallest possible size being around ~2MB. If your Go code imports libraries, this file size can increase dramatically. 10MB+ is common.
There are two main ways (for now) to reduce this file size:
- Manually compress the .wasm file.
a. Using
gz
compression reduces the ~2MB (minimum file size) example WASM file down to around 500kB. It may be better to use https://github.com/google/zopfli[Zopfli] to do the gzip compression, as it gives better results thangzip --best
, however it does take much longer to run. b. Using https://github.com/google/brotli[Brotli] for compression, the file sizes are markedly better than both Zopfli andgzip --best
, and compression time is somwhere inbetween the two, too.
Examples from https://github.com/johanbrandhorst[@johanbrandhorst]
Example 1
[width="25%",cols="^m,e,e",frame="topbot",options="header"]]
|=======
| Size | Command | Compression time
|16M | (uncompressed size) | N/A
|2.4M | brotli -o test.wasm.br test.wasm
| 53.6s
|3.3M | go-zopfli test.wasm
| 3m 2.6s
|3.4M | gzip --best test.wasm
| 2.5s
|3.4M | gzip test.wasm
| 0.8s
|=======
Example 2
[width="25%",cols="^m,e,e",frame="topbot",options="header"]]
|=======
| Size | Command | Compression time
|2.3M | (uncompressed size) | N/A
|496K | brotli -o main.wasm.br main.wasm
| 5.7s
|640K | go-zopfli main.wasm
| 16.2s
|660K | gzip --best main.wasm
| 0.2s
|668K | gzip main.wasm
| 0.2s
|=======
Use something like https://github.com/lpar/gzipped to automatically serve compressed files with correct headers, when available.
2. Use https://github.com/tinygo-org/tinygo[TinyGo] to generate the Wasm file instead.
TinyGo supports a subset of the Go language targeted for embedded devices, and has a WebAssembly output target.
While it does have limitations (not yet a full Go implementation), it is still fairly capable and the generated Wasm files are... tiny. ~10kB isn't unusual. The "Hello world" example is 575 bytes. If you gz -6
that, it drops down to 408 bytes. 😉
This project is also very actively developed, so its capabilities are expanding out quickly. See https://tinygo.org/webassembly/webassembly/ for more information on using WebAssembly with TinyGo.
- https://github.com/mbasso/awesome-wasm[Awesome-Wasm] - An extensive list of further Wasm resources. Not Go specific.