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)编译的可移植目标,支持在 Web 上部署客户端和服务器应用程序。
如果您是 WebAssembly 的新手,请阅读入门部分,观看一些Go WebAssembly 演讲,然后查看下面的更多示例。
JavaScript (GOOS=js) 端口
入门
本页面假设您已安装功能正常的 Go 1.11 或更新版本。有关故障排除,请参阅安装故障排除页面。
如果您使用的是 Windows,我们建议您使用 Git Bash 等 BASH 仿真系统来遵循本教程。
对于 Go 1.23 及更早版本,本文所需的 wasm 支持文件位于
misc/wasm
中,并且在执行诸如lib/wasm/wasm_exec.js
之类的文件操作时应替换路径。
编译一个基本的 Go 包以用于 Web
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 文件扩展名将使其更容易在后续通过 HTTP 以正确的 Content-Type 标头提供服务。
请注意,您只能编译主包。否则,您将获得一个无法在 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://: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
:JavaScript DOM API 的 Go 绑定。 -
domui
:一个纯 Go 框架,用于创建完整的 GUI 应用程序。 -
gas
:用于 WebAssembly 应用程序的基于组件的框架。 -
GoWebian:一个用纯 Go 构建页面并添加 WebAssembly 绑定的库。
-
hogusuru
:一个高级 webassembly 框架,直接在 GO 中实现了浏览器的大部分功能(包括 indexeddb、serviceworker、websocket 等)。 -
VECTY:使用 WebAssembly 在 Go 中构建响应式和动态 Web 前端,与 React 和 VueJS 等现代 Web 框架竞争。
-
vert
:Go 和 JS 值之间的 WebAssembly 互操作。 -
vue
:用于 WebAssembly 应用程序的渐进式框架。 -
Vugu:一个 wasm Web UI 库,具有 HTML 布局和 Go 应用程序逻辑、单文件组件、快速开发和原型设计工作流程。
-
webapi
:DOM、HTML、WebGL 等的绑定生成器和生成的绑定。
Canvas
- 一个新的canvas 绘图库——看起来效率很高。
使用 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://: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 时间等工具(实时演示)
Canvas (2D)
- GoWasm 实验 - 演示了几种常见调用类型的工作代码
- Gomeboycolor-wasm
- 实验性 Gameboy Color 模拟器的 WASM 端口。相关博客文章包含一些有趣的技术见解。
- TinyGo canvas
- 这使用 TinyGo 而不是标准 Go 编译,生成一个 19.37kB(压缩) 的 wasm 文件。
- Car and Mouse
- 一个游戏,您通过用光标引导一辆小的 Canvas 绘制汽车来获得积分
数据库
- TiDB-Wasm - 在浏览器中运行 TiDB(一个 golang 数据库)在 Wasm 上。
WebGL canvas (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://go-lang.org.cn/blog/wasi。
Go WebAssembly 演讲
编辑器配置
- 为 WebAssembly 配置 GoLand 和 Intellij Ultimate - 展示了在 GoLand 和 Intellij Ultimate 中使 Wasm 工作的确切步骤
调试
WebAssembly 尚不支持调试器,因此您现在需要使用传统的 println()
方法在 JavaScript 控制台上显示输出。
一个官方的 WebAssembly 调试小组已经成立,旨在解决这个问题,一些初步的调查和提案正在进行中
如果您对调试器方面感兴趣,请参与并帮助推动这项工作。:smile
分析 WebAssembly 文件的结构
WebAssembly Code Explorer 对于可视化 WebAssembly 文件的结构很有用。
- 点击左侧的十六进制值将突出显示其所属的部分,以及右侧的相应文本表示
- 点击右侧的一行将突出显示左侧的十六进制字节表示
减小 Wasm 文件的大小
目前,Go 生成的 Wasm 文件很大,最小可能的大小约为 2MB。如果您的 Go 代码导入库,此文件大小可能会急剧增加。超过 10MB 很常见。
目前(暂时)有两种主要方法可以减小此文件大小
-
手动压缩 .wasm 文件。
- 使用
gz
压缩将约 2MB(最小文件大小)的 WASM 示例文件减小到约 500kB。最好使用 Zopfli 进行 gzip 压缩,因为它比gzip --best
效果更好,但运行时间也更长。 - 使用 Brotli 进行压缩,文件大小明显优于 Zopfli 和
gzip --best
,并且压缩时间介于两者之间。这个 (新的)Brotli 压缩器看起来不错。
来自 @johanbrandhorst 的示例
示例 1
大小 命令 压缩时间 16M (未压缩大小) 不适用 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 (未压缩大小) 不适用 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 的一部分。