Go to WebAssembly

什么是WebAssembly?

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.

让我们用tinygo来写一个WebAssembly的模块,然后用js调用吧。首先下载TINYGO:(https://tinygo.org/,请下载对应版本的二进制文件)。然后将bin/tinygo(or tinygo.exe)加入到PATH路径。

然后先创建一个main.go文件(文件名必须是main.go,因为js里有用到文件名),文件内容如下:

package main

// This calls a JS function from Go.
func main() {
    println("Go: Hello World") // expecting 5
    println("Go: add(2+3):", add(2, 3)) // expecting 5
    println("Go: multiply(2*3):", multiply(2, 3)) // expecting 6
}

// This function is imported from JavaScript, as it doesn't define a body.
// You should define a function named 'main.add' in the WebAssembly 'env'
// module from JavaScript.
func add(x, y int) int

// This function is exported to JavaScript, so can be called using
// exports.multiply() in JavaScript.
//go:export multiply
func multiply(x, y int) int {
    return x * y;
}

然后用tinygo编译这个go文件,生成wasm文件。

目前最新版的tinygo0.12.0,最高只支持golang 1.13(目前最新版本的golang1.14),编译的命令中文件名必须是main.go,不能写”./main.go”

tinygo build -o wasm.wasm -target wasm main.go

Error:  tinygo build -o wasm.wasm -target wasm ./main.go

 

然后用go写一个简单的文件服务器(用于启动http服务器,因为wasm加载必须在http/https的环境下),注意对于wasm文件,要设置Content-Type为application/wasm

package main

import (
    "log"
    "net/http"
    "strings"
)

const dir = "./html"

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)
    }))}

然后在服务器目录下创建html目录,目录里新建index.html文件(注意js代码中设置env的代码):

<html>
<head>
    <title>WebAssembly</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script type="text/javascript">
    const go = new Go(); // Defined in wasm_exec.js
    const WASM_URL = 'wasm.wasm';
    go.importObject.env['main.go.add'] = function (x, y) {
        return x + y
    };
    var wasm;

    console.log("'instantiateStreaming' in WebAssembly => " + ('instantiateStreaming' in WebAssembly ? "Y" : "N"));
    if ('instantiateStreaming' in WebAssembly) {
        WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
            wasm = obj.instance;
            go.run(wasm);
            console.log('JS: multiplied two numbers:', wasm.exports.multiply(5, 3));
        }).catch((err) => {
            console.error(err);
        });
    } else {
        fetch(WASM_URL).then(resp =>
            resp.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
                wasm = obj.instance;
                go.run(wasm);
            })
        )
    }

</script>
</body>
</html>

index.html里面引用的wasm_exec.js文件就在tinygo下载包里,wasm.wasm就是我们刚刚编译生成的文件。

浏览器里打开这个网页 http://localhost:8080/index.html,发现控制台就能输出:

23:25:58.955 ‘instantiateStreaming’ in WebAssembly => Y

23:25:58.964 Go: Hello World

23:25:58.964 Go: add(2+3): 5

23:25:58.964 Go: multiply(2*3): 6

23:25:58.964 JS: multiplied two numbers: 15

23:25:58.966 Fetch finished loading: GET “http://localhost:8080/wasm.wasm”.

References:

https://webassembly.org/

https://tinygo.org/