在 go 1.11 的时候 go 官方向 go 实验性的添加了 WebAssembly 支持,也就是说 golang 可以编译 为 wasm 以供 JavaScript 进行调用。
在这里我想说一下我的感受,在编写代码时应该是主要编写 go 的代码供 JavaScript 调用 而不是在 在 go 中调用 JavaScript 代码。
目前 go 还只是实验性的支持 wasm 以后可能会变更,后面我会尽量跟进。
另外如果感兴趣的话,推荐一本柴树杉、丁尔男两位大佬的《WebAssembly 标准入门
》,估计也快发售了。
WebAssembly 是一种新兴的网页虚拟机标准,它的设计目标包括高可移植性、高安全性、高效率(包括载入效率和运行效率)、尽可能小的程序体积,WebAssembly 程序在 JavaScript 环境下的使用方法、WebAssembly 汇编语言和二进制格式。另外 WebAssembly 有好几个干爹:万维网联盟、 Mozilla、微软、谷歌、 苹果。
由于目前只是实验性的支持,在编写代码时 引用 syscall/js
会爆红 需要在goland
中将变量环境 指定为 OS=js ARCH=wasm goland
的设置在 seting -> Go -> Build Tags && Vendoring
中进行更改。
浏览器 我使用的是 chrome 包括 js 部分演示我也是在 chrome 下进行。
hello-wasm-go
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
GOOS=js GOARCH=wasm go build -o main.wasm
<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>
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 调用 JavaScript 函数并且回显到前端页面,也就是在前端调用我们使用 go 写的代码逻辑。
main.go
和index.html
// Copyright (c) 2020 HigKer
// Open Source: MIT License
// Author: SDing <deen.job@qq.com>
// Date: 2021/1/7 - 4:26 下午 - UTC/GMT+08:00
package main
import (
"fmt"
"syscall/js"
)
// 通过Go代码操作页面dom节点
var (
document = js.Global().Get("document")
nameEle = document.Call("getElementById", "name")
helloEle = document.Call("getElementById", "hello")
btnEle = js.Global().Get("btn")
)
func sayHello(this js.Value, args []js.Value) interface{} {
name := nameEle.Get("value").String()
if len(name) == 0 {
name = "github.com/higker/hello-wasm-go"
}
str := fmt.Sprintf("Hello,%s", name)
helloEle.Set("innerHTML", js.ValueOf(str))
return nil
}
func main() {
done := make(chan int, 0)
btnEle.Call("addEventListener", "click", js.FuncOf(sayHello))
<-done
}
下面是 index.html 内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello WebAssembly For Golang</title>
</head>
<body style="text-align: center">
<p>You Name:</p>
<input id="name" type="text">
<button id="btn">Running</button>
<h1 id="hello"></h1>
</body>
<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>
</html>
GOOS=js GOARCH=wasm go build -o main.wasm
复制依赖文件cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
启动静态服务器
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
js.Global().Get("btn")
或 document.Call("getElementById", "hello")
两种方式获取到 DOM 元素。addEventListener
为 btn 绑定点击事件 sayHello
。Set("innerHTML", ...)
渲染计算结果。WebAssembly.instantiateStreaming(....)
是加载我们 go 代码编译的 wasm 文件,然后网页就具备我们刚才使用 go 写的功能了。通过上面的例子可以看出来,我们写的 go 代码可以编译成 wasm 扔给浏览器执行我们的逻辑,反之我们也可以通过 go 来操作其他语言编译的 wasm 文件,并且调用里面的函数。
# Enable cgo 启动cgo
export CGO_ENABLED=1; export CC=gcc;
# 拉取我们需要的解析库
github.com/wasmerio/go-ext-wasm
注意这里有一个坑,官方那个 github 主页是让你去下载
go get github.com/wasmerio/wasmer-go
,其实目前不是这个了,✅正确方式去下载这个包github.com/wasmerio/go-ext-wasm
项目官方地址https://github.com/wasmerio/wasmer-go
main.go
文件中写入package main
import (
"fmt"
wasm "github.com/wasmerio/go-ext-wasm/wasmer"
)
func main() {
// 将WebAssembly模块读取为字节
bytes, _ := wasm.ReadBytes("tests.wasm")
// 实例化WebAssembly模块
instance, _ := wasm.NewInstance(bytes)
defer instance.Close()
// 从WebAssembly实例获取`sum`导出的函数。
sum := instance.Exports["sum"]
// 用Go标准值调用导出的函数。WebAssembly
// 推断类型,并自动转换值
result, _ := sum(11, 11)
// 结果
fmt.Println(result) // 22!
}
go run main.go
22
从上面的例子我们可以看出来,wasm 应用案例不止于此,例如很多第三方的发送短信的 API 接口 SDK 都是不同语言版本的代码实现,如果换成 wasm,直接写一套代码编译成 wasm,提供一个函数,其他语言加载这个 wasm 文件就可以了,只需要一个 wasm 解决了.....
相关技术交流群