原创分享 Go 的 WebAssembly 初见

ding · 2021年01月09日 · 378 次阅读

Go WebAssembly

介 绍

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

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

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

另外如果感兴趣的话,推荐一本柴树杉、丁尔男两位大佬的《WebAssembly 标准入门》,估计也快发售了。

WebAssembly 简介

WebAssembly 是一种新兴的网页虚拟机标准,它的设计目标包括高可移植性、高安全性、高效率(包括载入效率和运行效率)、尽可能小的程序体积,WebAssembly 程序在 JavaScript 环境下的使用方法、WebAssembly 汇编语言和二进制格式。另外 WebAssembly 有好几个干爹:万维网联盟、 Mozilla、微软、谷歌、 苹果。

Hello Wasm

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

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

  1. 创建一个项目 hello-wasm-go

hello-wasm-go

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

import "fmt"

func main() {
    fmt.Println("Hello, WebAssembly!")
}
  1. 然后将 main.go 编译为 main.wasm
GOOS=js GOARCH=wasm go build -o main.wasm
  1. 创建 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>
  1. 将 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 并对网页进行刷新。

GoWebAssembly

在 js 里面调用 go 的 wasm

接下来我们玩一点不一样的,go 调用 JavaScript 函数并且回显到前端页面,也就是在前端调用我们使用 go 写的代码逻辑。

  1. 首先我们创建一个main.goindex.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>
  1. 编译代码 GOOS=js GOARCH=wasm go build -o main.wasm
  2. 复制依赖文件cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

  3. 启动静态服务器

goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
  1. 你也可以通过其他方式启动一个服务器来浏览。

效 果👍

运行效果

相关函数解析

  • 通过 js.Global().Get("btn")document.Call("getElementById", "hello")两种方式获取到 DOM 元素。
  • btnEle 调用 addEventListener 为 btn 绑定点击事件 sayHello
  • helloEle 调用 Set("innerHTML", ...) 渲染计算结果。
  • JavaScript 代码中WebAssembly.instantiateStreaming(....)是加载我们 go 代码编译的 wasm 文件,然后网页就具备我们刚才使用 go 写的功能了。

生命周期

WebAssembly生命周期

在 go 里面调用其他语言的 wasm

通过上面的例子可以看出来,我们写的 go 代码可以编译成 wasm 扔给浏览器执行我们的逻辑,反之我们也可以通过 go 来操作其他语言编译的 wasm 文件,并且调用里面的函数。

  1. 首先我们需要安装一个依赖库
  2. 然后我们开启 cgo
  3. 然后把我们准备一个 tests.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

操作步骤截图

  1. 然后在我们的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!
}

  1. 执行
go run main.go 
22

小 结

从上面的例子我们可以看出来,wasm 应用案例不止于此,例如很多第三方的发送短信的 API 接口 SDK 都是不同语言版本的代码实现,如果换成 wasm,直接写一套代码编译成 wasm,提供一个函数,其他语言加载这个 wasm 文件就可以了,只需要一个 wasm 解决了.....

相关资料

Javascript 的 API 文档

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
1楼 已删除
ding 关闭了讨论 01月10日 10:29
ding 重新开启了讨论 01月10日 10:29
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册