Go Wiki:WebAssembly
介绍
Go 1.11 添加了对 WebAssembly 的实验性移植。Go 1.12 改进了它的一些部分,预计 Go 1.13 将进一步改进。Go 1.21 添加了一个针对 WASI 系统调用 API 的新移植。
WebAssembly 在其 主页 上被描述为
WebAssembly(缩写为 Wasm)是一种针对基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为一种可移植的目标,用于编译像 C/C++/Rust 这样的高级语言,从而能够在网络上部署客户端和服务器应用程序。
如果您不熟悉 WebAssembly,请阅读 入门 部分,观看一些 Go WebAssembly 演讲,然后查看下面的 更多示例。
JavaScript (GOOS=js) 移植
入门
此页面假设您已安装 Go 1.11 或更高版本。有关故障排除,请参见 安装故障排除 页面。
如果您使用的是 Windows,建议您使用 Git Bash 等 BASH 模拟系统遵循本教程。
对于 Go 1.23 及更早版本,本文中需要的 wasm 支持文件位于
misc/wasm
中,在执行lib/wasm/wasm_exec.js
等文件的操作时应替换路径。
要为 Web 编译基本的 Go 包
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
设置 GOOS=js
和 GOARCH=wasm
环境变量以编译为 WebAssembly
$ GOOS=js GOARCH=wasm go build -o main.wasm
这将构建包并生成一个名为 main.wasm 的可执行 WebAssembly 模块文件。.wasm 文件扩展名将使其更容易使用正确的 Content-Type 标头在 HTTP 上提供服务。
请注意,您只能编译主包。否则,您将获得无法在 WebAssembly 中运行的目标文件。如果您有一个想要与 WebAssembly 一起使用的包,请将其转换为主包并构建二进制文件。
要在浏览器中执行 main.wasm,我们还需要一个 JavaScript 支持文件和一个 HTML 页面将所有内容连接在一起。
复制 JavaScript 支持文件
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .
创建一个 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>
如果您的浏览器尚不支持 WebAssembly.instantiateStreaming
,您可以使用 polyfill。
然后从 Web 服务器提供三个文件 (index.html
、wasm_exec.js
和 main.wasm
)。例如,使用 goexec
# install goexec: go install github.com/shurcooL/goexec@latest
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
或使用您自己的 基本 HTTP 服务器命令。
注意:编译器和 wasm_exec.js
支持文件的相同主要 Go 版本必须一起使用。也就是说,如果 main.wasm
文件使用 Go 版本 1.N 编译,则相应的 wasm_exec.js
文件也必须从 Go 版本 1.N 复制。其他组合不支持。
注意:要使 goexec
命令在类 Unix 系统上工作,您必须 将 Go 的路径环境变量 添加到 shell 的 profile
中。Go 的入门指南对此进行了说明
将 /usr/local/go/bin 添加到 PATH 环境变量。您可以通过将以下行添加到 /etc/profile(对于系统范围的安装)或 $HOME/.profile 来实现
export PATH=$PATH:/usr/local/go/bin
注意:对配置文件所做的更改可能在您下次登录计算机之前不会生效
最后,导航到 https://127.0.0.1:8080/index.html,打开 JavaScript 调试控制台,您应该会看到输出。您可以修改程序,重建 main.wasm
,并刷新以查看新的输出。
使用 Node.js 执行 WebAssembly
可以使用 Node.js 而不是浏览器执行编译后的 WebAssembly 模块,这对于测试和自动化很有用。
首先,确保 Node 已安装并在您的 PATH
中。
然后,将 $(go env GOROOT)/lib/wasm
添加到您的 PATH
中。这将允许 go run
和 go test
在 PATH
搜索中找到 go_js_wasm_exec
并使用它,使其仅对 js/wasm
有效。
$ export PATH="$PATH:$(go env GOROOT)/lib/wasm"
$ GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test
PASS
ok example.org/my/pkg 0.800s
如果您在 Go 本身上工作,这也将允许您无缝地运行 run.bash
。
go_js_wasm_exec
是一个包装器,允许在 Node 中运行 Go Wasm 二进制文件。默认情况下,它可能位于 Go 安装的 lib/wasm
目录中。
如果您不想向 PATH
添加任何内容,也可以在手动执行 go run
或 go test
时将 -exec
标志设置为 go_js_wasm_exec
的位置。
$ GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec"
PASS
ok example.org/my/pkg 0.800s
最后,包装器也可以用于直接执行 Go Wasm 二进制文件
$ GOOS=js GOARCH=wasm go build -o mybin .
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./mybin
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -c
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./pkg.test
PASS
ok example.org/my/pkg 0.800s
在浏览器中运行测试
您还可以使用 wasmbrowsertest 在浏览器中运行测试。它会自动执行启动 Web 服务器的任务,并使用无头 Chrome 在其中运行测试并将日志中继到您的控制台。
与之前一样,只需 go get github.com/agnivade/wasmbrowsertest
来获取二进制文件。将其重命名为 go_js_wasm_exec
并将其放置到您的 PATH
中
$ mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec
$ export PATH="$PATH:$GOPATH/bin"
$ GOOS=js GOARCH=wasm go test
PASS
ok example.org/my/pkg 0.800s
或者,使用 exec
测试标志。
GOOS=js GOARCH=wasm go test -exec="$GOPATH/bin/wasmbrowsertest"
与 DOM 交互
见 https://pkg.go.dev/syscall/js。
另外
-
app
:一个支持 PWA 的基于 React 的框架,带有自定义工具。 -
dom
:一个用于简化 DOM 操作的库正在开发中。 -
dom
:Go 对 JavaScript DOM API 的绑定。 -
domui
:一个纯 Go 框架,用于创建完整的 GUI 应用程序。 -
gas
:基于组件的 WebAssembly 应用程序框架。 -
GoWebian:一个库,用于使用纯 Go 构建页面并添加 WebAssembly 绑定。
-
hogusuru
:一个高级 WebAssembly 框架,它在 GO 中直接实现了浏览器的多数功能(包括 IndexedDB、Service Worker、WebSocket 等)。 -
VECTY:使用 WebAssembly 在 Go 中构建响应式和动态的 Web 前端,与 React 和 VueJS 等现代 Web 框架竞争。
-
vert
:Go 和 JS 值之间的 WebAssembly 互操作。 -
vue
:用于 WebAssembly 应用程序的渐进式框架。 -
Vugu:一个 wasm Web UI 库,它使用 Go 进行应用程序逻辑、单文件组件、快速开发和原型设计工作流程,以 HTML 布局为特色。
-
webapi
:一个绑定生成器以及为 DOM、HTML、WebGL 等生成的绑定。
画布
在使用 net/http 时配置 fetch 选项
您可以使用 net/http 库从 Go 发出 HTTP 请求,它们将被转换为 fetch 调用。但是,fetch 选项 和 http 客户端 选项之间没有直接映射。为了实现这一点,我们有一些被识别为 fetch 选项的特殊标头值。它们是 -
-
js.fetch:mode
:Fetch API 模式设置的选项。有效值为:“cors”、“no-cors”、“same-origin”、“navigate”。默认值为“same-origin”。 -
js.fetch:credentials
:Fetch API 凭据设置的选项。有效值为:“omit”、“same-origin”、“include”。默认值为“same-origin”。 -
js.fetch:redirect
:Fetch API 重定向设置的选项。有效值为:“follow”、“error”、“manual”。默认值为“follow”。
因此,例如,如果我们想在发出请求时将模式设置为“cors”,它将类似于
req, err := http.NewRequest("GET", "https://127.0.0.1:8080", nil)
req.Header.Add("js.fetch:mode", "cors")
if err != nil {
fmt.Println(err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
// handle the response
请随时订阅 #26769 以获取更多上下文信息以及可能更新的信息。
Chrome 中的 WebAssembly
如果您运行的是更新版本的 Chrome,则有一个标志 (chrome://flags/#enable-webassembly-baseline
) 来启用 Liftoff,他们的新编译器,它应该会显着缩短加载时间。更多信息 在这里。
更多示例
一般
- Shimmer - 使用 Go 在 wasm 中进行图像转换。实时 演示。
- 视频过滤 - 来自网络摄像头的视频过滤器 (源代码)
- HandyTools - 提供 base64 编码/解码、转换 Unix 时间等工具(实时 DEMO)
画布 (2D)
- GoWasm 实验 - 演示了多种常见调用类型的代码示例
- Gomeboycolor-wasm
- 实验性 Gameboy Color 模拟器的 WASM 移植版本。相关的 博客文章 包含一些有趣的技术见解。
- TinyGo 画布
- 这是使用 TinyGo 而不是标准 go 编译的,生成的文件大小为 **19.37kB(压缩)** 的 wasm 文件。
- 汽车和老鼠
- 一款游戏,通过用鼠标控制画布上的汽车来获得分数
数据库
- TiDB-Wasm - 在浏览器上使用 Wasm 运行 TiDB,一个 golang 数据库。
WebGL 画布 (3D)
- 基本三角形 (源代码) - 在 WebGL 中创建基本三角形
- 相同内容,移植到 TinyGo (源代码) - 压缩后约 14kB(主线 Go 版本大小的 3%)
- 旋转立方体 (源代码) - 在 WebGL 中创建旋转立方体
- 相同内容,移植到 TinyGo (源代码) - 压缩后约 23kB(主线 Go 版本大小的 4%)
- Splashy (源代码) - 点击屏幕周围生成油漆…
WASI (GOOS=wasip1) 移植
入门 (WASI)
Go 1.21 引入了 WASI 作为支持的平台。要构建 WASI,请使用 wasip1
移植
$ GOOS=wasip1 GOARCH=wasm go build -o main.wasm
官方博客对使用 WASI 移植版本有很好的介绍:https://golang.ac.cn/blog/wasi.
Go WebAssembly 讲座
编辑器配置
- 为 WebAssembly 配置 GoLand 和 Intellij Ultimate - 展示了在 GoLand 和 Intellij Ultimate 中配置 Wasm 的具体步骤
调试
WebAssembly *目前* 还不支持调试器,所以现在只能使用传统的 println()
方法在 JavaScript 控制台中显示输出。
已经成立了官方的 WebAssembly 调试小组 来解决这个问题,并且已经进行了一些初步的调查和提案
如果您对调试器方面感兴趣,请参与进来并帮助推动这项工作。:smile
分析 WebAssembly 文件的结构
WebAssembly 代码浏览器 可用于可视化 WebAssembly 文件的结构。
- 点击左侧的十六进制值将突出显示它所属的节,以及右侧相应的文本表示形式
- 点击右侧的某一行将突出显示左侧的十六进制字节表示形式
减小 Wasm 文件的大小
目前,Go 会生成很大的 Wasm 文件,最小可能的大小约为 2MB。如果您的 Go 代码导入库,则文件大小会显著增加。10MB+ 很常见。
目前有两种主要方法可以减小文件大小
-
手动压缩 .wasm 文件。
- 使用
gz
压缩会将约 2MB(最小文件大小)的示例 WASM 文件压缩到大约 500kB。最好使用 Zopfli 进行 gzip 压缩,因为它比gzip --best
效果更好,但运行时间长得多。 - 使用 Brotli 压缩,文件大小明显优于 Zopfli 和
gzip --best
,压缩时间也介于两者之间。这个 (新)Brotli 压缩器 看起来不错。
来自 @johanbrandhorst 的示例
示例 1
大小 命令 压缩时间 16M (未压缩大小) N/A 2.4M brotli -o test.wasm.br test.wasm
53.6s 3.3M go-zopfli test.wasm
3m 2.6s 3.4M gzip --best test.wasm
2.5s 3.4M gzip test.wasm
0.8s 示例 2
大小 命令 压缩时间 2.3M (未压缩大小) N/A 496K brotli -o main.wasm.br main.wasm
5.7s 640K go-zopfli main.wasm
16.2s 660K gzip --best main.wasm
0.2s 668K gzip main.wasm
0.2s 使用类似 https://github.com/lpar/gzipped 的工具,可以在可用时自动使用正确的标头提供压缩文件。
- 使用
-
使用 TinyGo 生成 Wasm 文件。
TinyGo 支持针对嵌入式设备的 Go 语言子集,并且具有 WebAssembly 输出目标。
虽然它有一些局限性(还没有完整的 Go 实现),但它仍然非常强大,生成的 Wasm 文件很小。大约 10kB 不足为奇。“Hello world” 示例只有 575 字节。如果使用
gz -6
压缩,它会降到 408 字节。:wink该项目也积极开发中,因此其功能迅速扩展。有关使用 TinyGo 的 WebAssembly 的更多信息,请参阅 https://tinygo.org/docs/guides/webassembly/。
其他 WebAssembly 资源
- Awesome-Wasm - 更详细的 Wasm 资源列表。不特定于 Go。
此内容是 Go Wiki 的一部分。