Skip to content

Commit 586023b

Browse files
johanbrandhorstdeadprogram
authored andcommitted
src/examples/wasm: Show both methods supported
Adds another example showing the simple case of executing main, adds a README explaining how everything fits together and how to execute the compiled code in the browser. Include a minimal webserver for local testing.
1 parent a00a51e commit 586023b

File tree

12 files changed

+244
-25
lines changed

12 files changed

+244
-25
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ commands:
7070
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
7171
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
7272
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
73-
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm
73+
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
74+
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
7475
test-linux:
7576
parameters:
7677
llvm:

src/examples/wasm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
html/*

src/examples/wasm/Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export: clean wasm_exec
2+
tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go
3+
cp ./export/wasm.js ./html/
4+
cp ./export/index.html ./html/
5+
6+
main: clean wasm_exec
7+
tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go
8+
cp ./main/index.html ./html/
9+
10+
wasm_exec:
11+
cp ../../../targets/wasm_exec.js ./html/
12+
13+
clean:
14+
rm -rf ./html
15+
mkdir ./html

src/examples/wasm/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# TinyGo WebAssembly examples
2+
3+
The examples here show two different ways of using WebAssembly with TinyGo;
4+
5+
1. Defining and exporting functions via the `//go:export <name>` directive. See
6+
[the export folder](./export) for an example of this.
7+
1. Defining and executing a `func main()`. This is similar to how the Go
8+
standard library implementation works. See [the main folder](./main) for an
9+
example of this.
10+
11+
## Building
12+
13+
Build using the `tinygo` compiler:
14+
15+
```bash
16+
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go
17+
```
18+
19+
This creates a `wasm.wasm` file, which we can load in JavaScript and execute in
20+
a browser.
21+
22+
This examples folder contains two examples that can be built using `make`:
23+
24+
```bash
25+
$ make export
26+
```
27+
28+
```bash
29+
$ make main
30+
```
31+
32+
## Running
33+
34+
Start the local webserver:
35+
36+
```bash
37+
$ go run main.go
38+
Serving ./html on http://localhost:8080
39+
```
40+
41+
`fmt.Println` prints to the browser console.
42+
43+
## How it works
44+
45+
Execution of the contents require a few JS helper functions which are called
46+
from WebAssembly. We have defined these in
47+
[wasm_exec.js](../../../targets/wasm_exec.js). It is based on
48+
`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly
49+
different. Ensure you are using the same version of `wasm_exec.js` as the
50+
version of `tinygo` you are using to compile.
51+
52+
The general steps required to run the WebAssembly file in the browser includes
53+
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
54+
`WebAssembly.instantiate` in some browsers:
55+
56+
```js
57+
const go = new Go(); // Defined in wasm_exec.js
58+
const WASM_URL = 'wasm.wasm';
59+
60+
var wasm;
61+
62+
if ('instantiateStreaming' in WebAssembly) {
63+
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
64+
wasm = obj.instance;
65+
go.run(wasm);
66+
})
67+
} else {
68+
fetch(WASM_URL).then(resp =>
69+
resp.arrayBuffer()
70+
).then(bytes =>
71+
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
72+
wasm = obj.instance;
73+
go.run(wasm);
74+
})
75+
)
76+
}
77+
```
78+
79+
If you have used explicit exports, you can call them by invoking them under the
80+
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
81+
example of this.
82+
83+
In addition to this piece of JavaScript, it is important that the file is served
84+
with the correct `Content-Type` header set.
85+
86+
```go
87+
package main
88+
89+
import (
90+
"log"
91+
"net/http"
92+
"strings"
93+
)
94+
95+
const dir = "./html"
96+
97+
func main() {
98+
fs := http.FileServer(http.Dir(dir))
99+
log.Print("Serving " + dir + " on http://localhost:8080")
100+
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
101+
if strings.HasSuffix(req.URL.Path, ".wasm") {
102+
resp.Header().Set("content-type", "application/wasm")
103+
}
104+
105+
fs.ServeHTTP(resp, req)
106+
}))
107+
}
108+
```
109+
110+
This simple server serves anything inside the `./html` directory on port `8080`,
111+
setting any `*.wasm` files `Content-Type` header appropriately.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
3+
<html>
4+
5+
<head>
6+
<meta charset="utf-8" />
7+
<title>Go WebAssembly</title>
8+
<meta name="viewport" content="width=device-width, initial-scale=1" />
9+
<script src="wasm_exec.js" defer></script>
10+
<script src="wasm.js" defer></script>
11+
</head>
12+
13+
<body>
14+
<h1>WebAssembly</h1>
15+
<p>Add two numbers, using WebAssembly:</p>
16+
<input type="number" id="a" value="2" /> + <input type="number" id="b" value="2" /> = <input type="number"
17+
id="result" readonly />
18+
</body>
19+
20+
</html>

src/examples/wasm/wasm.go renamed to src/examples/wasm/export/wasm.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ func add(a, b int) int {
1616
//go:export update
1717
func update() {
1818
document := js.Global().Get("document")
19-
a_str := document.Call("getElementById", "a").Get("value").String()
20-
b_str := document.Call("getElementById", "b").Get("value").String()
21-
a, _ := strconv.Atoi(a_str)
22-
b, _ := strconv.Atoi(b_str)
23-
result := a + b
19+
aStr := document.Call("getElementById", "a").Get("value").String()
20+
bStr := document.Call("getElementById", "b").Get("value").String()
21+
a, _ := strconv.Atoi(aStr)
22+
b, _ := strconv.Atoi(bStr)
23+
result := add(a, b)
2424
document.Call("getElementById", "result").Set("value", result)
2525
}

src/examples/wasm/wasm.js renamed to src/examples/wasm/export/wasm.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const WASM_URL = '../../../wasm.wasm';
3+
const WASM_URL = 'wasm.wasm';
44

55
var wasm;
66

@@ -14,7 +14,7 @@ function init() {
1414

1515
const go = new Go();
1616
if ('instantiateStreaming' in WebAssembly) {
17-
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
17+
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
1818
wasm = obj.instance;
1919
go.run(wasm);
2020
updateResult();
@@ -23,7 +23,7 @@ function init() {
2323
fetch(WASM_URL).then(resp =>
2424
resp.arrayBuffer()
2525
).then(bytes =>
26-
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
26+
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
2727
wasm = obj.instance;
2828
go.run(wasm);
2929
updateResult();

src/examples/wasm/main/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# WebAssembly main execution example
2+
3+
A simple hello world that prints to the browser console.
4+
5+
## License
6+
7+
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
8+
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.

src/examples/wasm/main/index.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!doctype html>
2+
<!--
3+
Copyright 2018 The Go Authors. All rights reserved.
4+
Use of this source code is governed by a BSD-style
5+
license that can be found in the LICENSE file.
6+
-->
7+
<html>
8+
9+
<head>
10+
<meta charset="utf-8">
11+
<title>Go wasm</title>
12+
</head>
13+
14+
<body>
15+
<!--
16+
Add the following polyfill for Microsoft Edge 17/18 support:
17+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/encoding.min.js"></script>
18+
(see https://caniuse.com/#feat=textencoder)
19+
-->
20+
<script src="wasm_exec.js"></script>
21+
<script>
22+
if (!WebAssembly.instantiateStreaming) { // polyfill
23+
WebAssembly.instantiateStreaming = async (resp, importObject) => {
24+
const source = await (await resp).arrayBuffer();
25+
return await WebAssembly.instantiate(source, importObject);
26+
};
27+
}
28+
29+
const go = new Go();
30+
let mod, inst;
31+
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
32+
mod = result.module;
33+
inst = result.instance;
34+
document.getElementById("runButton").disabled = false;
35+
}).catch((err) => {
36+
console.error(err);
37+
});
38+
39+
async function run() {
40+
console.clear();
41+
await go.run(inst);
42+
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
43+
}
44+
</script>
45+
46+
<button onClick="run();" id="runButton" disabled>Run</button>
47+
</body>
48+
49+
</html>

src/examples/wasm/main/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func main() {
8+
fmt.Println("Hello world!")
9+
}

0 commit comments

Comments
 (0)