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=jsGOARCH=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.htmlwasm_exec.jsmain.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 rungo testPATH 搜索中找到 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 rungo 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

另外

画布

在使用 net/http 时配置 fetch 选项

您可以使用 net/http 库从 Go 发出 HTTP 请求,它们将被转换为 fetch 调用。但是,fetch 选项 和 http 客户端 选项之间没有直接映射。为了实现这一点,我们有一些被识别为 fetch 选项的特殊标头值。它们是 -

因此,例如,如果我们想在发出请求时将模式设置为“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,他们的新编译器,它应该会显着缩短加载时间。更多信息 在这里

更多示例

一般

画布 (2D)

数据库

WebGL 画布 (3D)

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 *目前* 还不支持调试器,所以现在只能使用传统的 println() 方法在 JavaScript 控制台中显示输出。

已经成立了官方的 WebAssembly 调试小组 来解决这个问题,并且已经进行了一些初步的调查和提案

如果您对调试器方面感兴趣,请参与进来并帮助推动这项工作。:smile

分析 WebAssembly 文件的结构

WebAssembly 代码浏览器 可用于可视化 WebAssembly 文件的结构。

减小 Wasm 文件的大小

目前,Go 会生成很大的 Wasm 文件,最小可能的大小约为 2MB。如果您的 Go 代码导入库,则文件大小会显著增加。10MB+ 很常见。

目前有两种主要方法可以减小文件大小

  1. 手动压缩 .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 的工具,可以在可用时自动使用正确的标头提供压缩文件。

  2. 使用 TinyGo 生成 Wasm 文件。

    TinyGo 支持针对嵌入式设备的 Go 语言子集,并且具有 WebAssembly 输出目标。

    虽然它有一些局限性(还没有完整的 Go 实现),但它仍然非常强大,生成的 Wasm 文件很小。大约 10kB 不足为奇。“Hello world” 示例只有 575 字节。如果使用 gz -6 压缩,它会降到 408 字节。:wink

    该项目也积极开发中,因此其功能迅速扩展。有关使用 TinyGo 的 WebAssembly 的更多信息,请参阅 https://tinygo.org/docs/guides/webassembly/

其他 WebAssembly 资源


此内容是 Go Wiki 的一部分。