使用 VUE 和 Go 触摸 WebAssembly
本文将展示如何在 Go 中使用 WebAssembly。本文一起来学习如何从 Go 代码构建到 WebAssembly,通过VUE来展示使用 WebAssembly 的API。
本文涉及的 Go 需要 Go1.11 或更高版本的 Go 开发环境,这里将忽略 Go 环境的配置。前端将使用 VUE2 来构建。
文章涉及代码:https://github.com/QuintionTang/Go-WebAssembly
什么是 WebAssembly?
WebAssembly(wasm)是指一种可以在浏览器及其外围技术和工具中运行的编程语言。它以二进制格式表示并由堆栈机器实现处理。与 JavaScript 一样,它由浏览器直接解释,但正在开发和规范,目标是在速度方面超越 JavaScript。
WebAssembly 大多由 C、C++、Rust 等各种高级语言编译而成,而不是程序员直接编写二进制代码。同样在 Go 中,将 Go 代码编译为 WebAssembly 的功能从 Go1.11正式添加为 Go 的标准功能。
更多内容如下:
HelloWorld
Go 有一个叫做交叉编译的特性。不仅可以为正在编译的机器的体系结构和操作系统构建二进制文件,还可以为其他体系结构和操作系统构建二进制文件。
例如,在 macOS 上为 Windows 和 Linux 交叉编译二进制文件非常容易。可以通过指定以下环境 GOOS
变量来像往常一样进行交叉编译:GOARCH go bulid
。
# 为 Windows 编译(32 位)
$ GOOS=windows GOARCH=386 go build
# 为 Linux 编译(64 位)
$ GOOS=linux GOARCH=amd64 go build
创建目录 go-webassembly
,进入目录,再创建 helloworld
,进入 helloworld
目录,执行命令:
go mod init go-webassembly/helloworld
创建文件 main.go
,代码如下:
package main
func main() {
println("Hello, WebAssembly!")
}
前端实现将使用 VUE 框架来展示其调用效果,因此需要创建文件夹
vue
,将把 WebAssembly 生成的wasm
和js
文件存储到项目目录public/wasms
。
现在以交叉方式编译 WebAssembly,在目录下并执行如下命令。请注意,此处将输出文件名指定为选项,但即使不指定也可以构建。GOOS js GOARCH wasm go build-o
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/helloworld/main.wasm
执行完命令后,将在目录下生成文件 main.wasm
,同时将在 GOROOT
目录下生成 wasm_exec.js
文件,完整路径为 /usr/local/go/misc/wasm
,将文件复制到路径 /vue/public/wasms/helloworld/
下,命令如下:
cp /usr/local/go/misc/wasm/wasm_exec.js .
cp /usr/local/go/misc/wasm/wasm_exec_node.js .
cp /usr/local/go/misc/wasm/wasm_exec.html .
这样目录 /vue/public/wasms/helloworld
就有两个文件 js
和 wasm
,接下来执行命令:
node wasm_exec_node.js main.wasm
输出的结果如下:
Hello, WebAssembly!
接下来就是在 VUE 中来展示 Helloworld
的调用,在 public/index.html
中引入JS,如下:
<script src="./wasms/helloworld/wasm_exec.js"></script>
构建组件 Helloworld,完整代码如下:
<template>
<div class="card">
<div class="card-header">
<h4>Helloworld</h4>
</div>
<div class="card-body">
<p class="text-muted">
点击“运行”,在控制台输出日志 <code>Hello, WebAssembly!</code>
</p>
<div class="live-preview">
<button
@click="run()"
class="btn btn-success"
id="runButton"
disabled
>
运行
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Helloworld",
data() {
return {
go: null,
mod: null,
inst: null,
};
},
mounted() {
this.init();
},
methods: {
init() {
if (!WebAssembly.instantiateStreaming) {
WebAssembly.instantiateStreaming = async (
resp,
importObject
) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new window.Go();
this.go = go;
WebAssembly.instantiateStreaming(
fetch("/wasms/helloworld/main.wasm"),
go.importObject
)
.then((result) => {
console.log(result);
this.mod = result.module;
this.inst = result.instance;
document.getElementById("runButton").disabled = false;
})
.catch((err) => {
console.error(err);
});
},
async run() {
console.clear();
await this.go.run(this.inst);
this.inst = await WebAssembly.instantiate(
this.mod,
this.go.importObject
);
},
},
};
</script>
点击按钮“运行”,在浏览器控制台输入如下:
处理 JavaScript 对象
接下来,学习如何在 JavaScript 中使用对象,为了更好的处理 Go 中的 JavaScript 对象,将使用 Go1.11 标准包中包含的包 syscall/js
。
syscall/js
里面定义了个新的类型 js.Value
,它表示一个JavaScript值,它提供了一个简单的API来操纵任何类型的JavaScript值并与之交互。一个 js.ValueOf()
函数,它接受任何 Go 基本类型并返回相应的 js.Value
。
Go值和 JavaScript 值对应关系如下:
Go | JavaScript |
---|---|
js.Value | JavaScript 中的任何值 |
js.Func | function |
nil | null |
bool | Boolean |
integers 和 floats | Number |
string | String |
[]interface{} | new array |
map[string]interface{} | new object |
JavaScript 中的 js.Type
类型表示为类型。js.Type
类型定义如下,可以从 js.Value
类型的方法中检索。
type Type int
const (
TypeUndefined Type = iota
TypeNull
TypeBoolean
TypeNumber
TypeString
TypeSymbol
TypeObject
TypeFunction
)
js.Value
类型将所有 JavaScript 值表示为单一类型,因此如果每个方法都调用了一个意外的值,panic
就会导致崩溃。例如,Int
方法可以 js.Value
将类型的值视为数字并将 int
值作为 Go 类型检索。但是,js.Value
类型也可以处理函数和字符串值,所以当调用一个不是数字的值时,panic
会发生错误。
因此,js.Type
通过使用 type
值,js.Value
可以处理 type
值的具体类型,避免 panic
。例如,对于Int
方法,最好只在 Type
方法返回 js.TypeNumber
时调用。如下:
func printNumber(v js.Value) {
if v.Type() == js.TypeNumber {
fmt.Printf("%d\n", v.Int())
}
}
DOM 操作
可以在 Go 中使用 js.Value
来更好的操作 HTML Dom 对象。接下来创建目录 docments
,创建文件 main.go ,代码如下:
package main
import "syscall/js"
func main() {
// 获取全局对象(网页浏览器为window)
window := js.Global()
// window.document.getElementById("helloresult")
message := window.Get("document").Call("getElementById", "helloresult")
// HTML
message.Set("innerHTML", "Hello, WebAssembly")
}
接下来在前端创建一个 id="helloresult"
的 DOM 对象。
按照上面的流程,生成 wasm
文件:
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/document/main.wasm
事件处理
上面介绍了如何操作 DOM,现在来实现时间的处理。
package main
import "syscall/js"
func main() {
window := js.Global()
// window.document.getElementById("clickresult")
message := window.Get("document").Call("getElementById", "clickresult")
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
message.Set("innerHTML", "Go 事件触发")
return nil
})
// message.addEventListener("click", cb)
message.Call("addEventListener", "click", cb)
select {}
}
按照上面的流程,生成 wasm
文件:
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/events/main.wasm
总结
本文介绍了如何将 Go 代码构建为 WebAssembly、如果实现 Go 与 JavaScript 对象及回调函数。