0%

go wasm

介绍

在 go 1.11 的时候 go 官方向 go 实验性的添加了 WebAssembly 支持,也就是说 golang 可以编译 为 wasm 以供 JavaScript 进行调用。

在这里我想说一下我的感受,在编写代码时应该是主要编写 go 的代码供 JavaScript 调用 而不是在 在 go 中调用 JavaScript 代码。

目前 go 还只是实验性的支持 wasm 以后可能会变更,我会尽量跟进。

前期准备

这里我使用的是 go1.13 进行演示,ide 使用的是 goland

由于目前只是实验性的支持,在编写代码时 引用 syscall/js 会爆红 需要在ide中将变量环境 指定为 OS=js ARCH=wasm goland 的设置在 seting -> Go -> Build Tags && Vendoring 中进行更改。

浏览器 我使用的是 chrome 包括 js 部分演示我也是在 chrome 下进行。

首先把创建一个新项目作为本次演示

创建一个新文件夹 nweb 并使用 ide 打开它,接下来的所有演示都将在这里进行。然后开启 go mod,在 nweb 目录内执行 go mod init nweb

由于国内直接拉起依赖可能比较慢,你还可以将 GOPROXY 设置为 https://goproxy.cn 具体请参考 https://github.com/goproxy/goproxy.cn/blob/master/README.zh-CN.md goland 的设置在 seting -> go -> Go Modules(vgo)

前期准备

这里主要参考 go wiki

创建 main.go 并写入以下内容

package main

import "fmt"

func main() {
    fmt.Println("Hello, WebAssembly!")
}

然后将 main.go 编译为 main.wasm

GOOS=js GOARCH=wasm go build -o main.wasm

创建 index.html 并写入以下内容

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

将 JavaScript 支持文件移动到本目录

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

下载 go 版的 简单 web 服务器

go get -v -u github.com/shurcooL/goexec

然后运行它

goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

然后打开 chrome 访问 http://127.0.0.1:8080/ 并按下 F12 打开 DevTools 不出意外的话你应该会看到 控制台上输出 Hello, WebAssembly!

接下来就正式开始了,在每次更改 go 代码时 你需要对 go 代码进行重新编译,重启 goexec 并对网页进行刷新。

例子

上面的前期准备是 go wiki 对 wasm 进行的演示接下来我们玩一点不一样的。

go 调用 JavaScript 函数

package main

import "syscall/js"

func main() {
    // 执行属性的对用方法 这里执行的方法类似于 // fmt.Println("Hello World!")
    js.Global().Get("console").Call("log","Hello World!")
}

重新编译后切换到 chrome 刷新一下 网页 不出意外的话,你的开发者工具中的控制台会输出 Hello World!

JavaScript 调用 go 函数

package main

import (
    "fmt"
    "syscall/js"
)

func sayHello(this js.Value, args []js.Value) interface{} { //函数的定义格式就是这样 因为这是 js.FuncOf 要求的

    fmt.Println("Hello World!") // 执行我们自己的代码

    // return nil 这样可能比较符合 go 的习惯
    return js.Undefined() // 返回 JavaScript 的 undefined
}

func main() {
    js.Global().Set("sayHello", js.FuncOf(sayHello)) // 将 go 函数注册为 JavaScript 函数

    c := make(chan struct{}) // 这两句不是多余的 需要阻止程序的退出以使 JavaScript 能正常调用
    <-c
}

重新编译后切换到 chrome 刷新一下, 然后在开发者工具(F12)终端中输入 sayHello() 进行调用, 不出意外的话,你的开发者工具中的控制台会输出 Hello World!

将 JavaScript 函数作为参数传递给 go 进行调用

这是很有用的 这样就可以 go 提供处理过后的数据,JavaScript 自己决定数据如何使用

package main

import (
    "syscall/js"
)

func autoincrement(this js.Value, args []js.Value) interface{} { //函数的定义格式就是这样 因为这是 js.FuncOf 要求的
    fn := args[0] // 获取输入的第一个参数

    for i := 0; i < 10;i++{
        // 这里我默认输入的参数是 JavaScript 函数 // 非函数调用 Invoke() 会 panic
        fn.Invoke(i) // Invoke 的意思是向 JavaScript函数的括号里插入值
    }

    // return nil 这样可能比较符合 go 的习惯
    return js.Undefined() // 返回 JavaScript 的 undefined
}

func main() {
    js.Global().Set("autoincrement", js.FuncOf(autoincrement)) // 将 go 函数注册为 JavaScript 函数

    c := make(chan struct{}) // 这两句不是多余的 需要阻止程序的退出以使 JavaScript 能正常调用
    <-c
}

重新编译并刷新网页

在 chrome 开发者工具控制台 定义一个函数 用来处理我们 go 函数生成的数据

类似于这样

function print(msg){
    console.log(msg)
}

然后调用它

autoincrement(print)

最后输出结果类似于这样

参看资料

https://github.com/golang/go/wiki/WebAssembly

https://golang.org/pkg/syscall/js/

https://talks.godoc.org/github.com/chai2010/awesome-go-zh/chai2010/chai2010-golang-wasm.slide#1