Trying to understand wasm in go, so I wrote the below that:
- Manipulate DOM
- Call JS function
- Define a function that can called by JS
first 2 steps are fine, but the last one is not working as expected, as I got the JavaScript error function undefined
, my code is below, the issue I have is in the function sub
package main
import (
"syscall/js"
)
// func sub(a, b float64) float64
func sub(this js.Value, inputs []js.Value) interface{} {
return inputs[0].Float() - inputs[1].Float()
}
func main() {
c := make(chan int) // channel to keep the wasm running, it is not a library as in rust/c/c , so we need to keep the binary running
js.Global().Set("sub", js.FuncOf(sub))
alert := js.Global().Get("alert")
alert.Invoke("Hi")
println("Hello wasm")
num := js.Global().Call("add", 3, 4)
println(num.Int())
document := js.Global().Get("document")
h1 := document.Call("createElement", "h1")
h1.Set("innerText", "This is H1")
document.Get("body").Call("appendChild", h1)
<-c // pause the execution so that the resources we create for JS keep available
}
compiled it to wasm as:
GOOS=js GOARCH=wasm go build -o main.wasm wasm.go
Copied the wasm_exec.js
file to the same working folder as:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
My HTML file is:
<!DOCTYPE html>
<html lang="en">
<head>
<title>WASM</title>
<script src="http://localhost:8080/www/lib.js"></script>
<!-- WASM -->
<script src="http://localhost:8080/www/wasm_exec.js"></script>
<script src="http://localhost:8080/www/loadWasm.js"></script>
</head>
<body>
</body>
<script>
console.log(sub(5,3));
</script>
</html>
The lib.js
is:
function add(a, b){
return a b;
}
The loadWasm.js
is:
async function init(){
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("http://localhost:8080/www/main.wasm"),
go.importObject
);
go.run(result.instance);
}
init();
The server code is:
package main
import (
"fmt"
"html/template"
"net/http"
)
func wasmHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("www/home.html"))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Access-Control-Allow-Origin", "*")
err := tmpl.Execute(w, nil)
if err != nil {
fmt.Println(err)
}
})
}
func main() {
fs := http.StripPrefix("/www/", http.FileServer(http.Dir("./www")))
http.Handle("/www/", fs)
http.Handle("/home", wasmHandler())
http.ListenAndServe(":8080", nil)
}
The output i got is:
UPDATE
I tried using the TinyGO example as below, but got almost the same issue:
//wasm.go
package main
// This calls a JS function from Go.
func main() {
println("adding two numbers:", add(2, 3)) // expecting 5
}
// module from JavaScript.
func add(x, y int) int
//export multiply
func multiply(x, y int) int {
return x * y
}
Compliled it as:
tinygo build -o main2.wasm -target wasm -no-debug
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" .
And server.go
as:
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./www"
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)
}))
}
And the JS code as:
const go = new Go(); // Defined in wasm_exec.js
go.importObject.env = {
'main.add': function(x, y) {
return x y
}
// ... other functions
}
const WASM_URL = 'main.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
// Calling the multiply function:
console.log('multiplied two numbers:', exports.multiply(5, 3));
CodePudding user response:
I found the solution, that I need something to detect and confirm that wasm
had been loaded and ready for processing, same the one used in JS to check if the document is ready:
if (document.readyState === 'complete') {
// The page is fully loaded
}
// or
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
// document ready
}
};
So, as wasm
initiation function in my code is async
I used the below in JS:
<!DOCTYPE html>
<html lang="en">
<head>
<title>WASM</title>
<!-- WASM -->
<script src="http://localhost:8080/www/wasm_exec.js"></script>
<script src="http://localhost:8080/www/loadWasm.js"></script>
</head>
<body>
</body>
<script>
(async () => {
try {
await init();
alert("Wasm had been loaded")
console.log(multiply(5, 3));
} catch (e) {
console.log(e);
}
})();
/***** OR ****/
(async () => {
await init();
alert("Wasm had been loaded")
console.log(multiply(5, 3));
})().catch(e => {
console.log(e);
});
/*************/
</script>
</html>
This helped me been sure that the document is ready to process and call the wasm function.
The wasm
loading function simply became:
async function init(){
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("http://localhost:8080/www/main.wasm"),
go.importObject
);
go.run(result.instance);
}