01-WebAssembly

简介

Go 1.11向WebAssembly添加了一个实验端口。 Go 1.12对它的某些部分进行了改进,并有望在Go 1.13中进行进一步的改进。

WebAssembly在其主页上描述为:

WebAssembly(缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。 Wasm被设计为可移植目标,用于编译高级语言(如C/C++/Rust),从而可以在Web上为客户端和服务器应用程序进行部署。

如果不熟悉WebAssembly,请阅读下面的“入门”部分,观看下面的一些“Go WebAssembly讲座”,然后查看下面的更多“示例”。

入门

此页面假定Go 1.11或更高版本可以正常运行。有关故障排除,请参阅“安装故障排除”页面。

为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)/misc/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 get -u github.com/shurcooL/goexec
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

或者使用基本的HTTP服务器命令

/usr/local/go/bin添加到PATH环境变量中。可以通过将以下行添加到/etc/profile(用于系统范围的安装)或$HOME/ .profile中来完成此操作:

export PATH=$PATH:/usr/local/go/bin

注意:对配置文件的更改可能要等到下一次登录计算机后才能应用,或者执行source命令。

最后,浏览至http//localhost:8080/index.html,打开JavaScript调试控制台,应该会看到输出。可以修改程序,重建main.wasm,然后刷新以查看新的输出。

使用Node.js执行WebAssembly

可以使用Node.js而非浏览器执行已编译的WebAssembly模块,这对于测试和自动化非常有用。

安装Node.js并在PATH中设置好,在执行go rungo test时,将-exec标志设置为go_js_wasm_exec所在的目录。默认情况下,go_js_wasm_exec位于Go安装的misc/wasm目录中。

GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec"
PASS
ok      example.org/my/pkg      0.800s

在PATH中添加go_js_wasm_exec,可以在执行时不必每次都手动设置-exec标志,go rungo test能直接对js/wasm生效。

export PATH="$PATH:$(go env GOROOT)/misc/wasm"
GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!
GOOS=js GOARCH=wasm go test
PASS
ok      example.org/my/pkg  0.800s

在浏览器中运行测试

也可以使用wasmbrowsertest在浏览器中运行测试。它可以自动完成网络服务器的工作,并使用无头的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"

GoWebAssembly讲座

与DOM交互

详细查阅syscall/js包。

其他项目:

  • gas-WebAssembly应用程序的基于组件的框架
  • app-基于React兼容PWA的自定义工具框架
  • Vugu-wasm Web UI库,具有HTML布局,带有Go格式的应用程序逻辑,单个文件组件,快速开发和原型工作流程
  • vue -WebAssembly应用程序的渐进框架
  • dom-用于简化DOM操作的库
  • webapi-绑定生成器
  • vert-Go和JS值之间的WebAssembly互操作
  • GoWebian-用于纯粹地构建页面并添加WebAssembly绑定的库

Canvas

使用net/http时配置fetch options

可以使用net/http库从Go发出HTTP请求,这些请求将转换为fetch调用。 但是,fetch options 和 http 客户端选项之间没有直接映射。 为此,我们有一些特殊的标头值,这些标头值被认为是fetch options。如下表所示:

选项描述默认有效值其他有效值
js.fetch.mode设置Fetch API 模式same-origincorsno-corsnavigation
js.fetch.credentials设置Fetch API 证书same-originomitinclude
js.fetch.redirect设置Fetch API重定向followerrormanual

因此,如果想在发出请求时将模式设置为cors,则将类似于:

req, err := http.NewRequest("GET", "http://localhost: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,以获取更多信息和可能的更新。

编辑器配置

在Goland和Intellij Ultimate中设置WebAssembly的具体步骤,参考这里

Chrome中的WebAssembly

如果运行较新版本的Chrome,则会有一个标志(chrome://flags#enable-webassembly-baseline)启用新编译器Liftoff,这将大大缩短加载时间,更多信息在这里

调试

WebAssembly尚不支持调试器,因此,现在需要使用良好的 println()方法在JavaScript控制台上显示输出。

已经创建了一个官方的WebAssembly调试子组来解决此问题,并且正在进行一些初步调查和提议:

如果对调试器方面有兴趣,请参与并帮助实现这一目标。

分析WebAssembly文件的结构

WebAssembly代码资源管理器对于可视化WebAssembly文件的结构很有用。

  • 单击左侧的十六进制值将突出显示其所属的部分,并在右侧显示相应的文本表示形式
  • 单击右侧的一行将在左侧突出显示它的十六进制字节表示

已知错误

1.11.2之前的Go版本存在一个错误,该错误可能在某些(罕见)情况下生成错误的wasm代码。

如果您的Go代码可以毫无问题地编译为wasm,但是在浏览器中运行时会产生如下错误:CompileError: wasm validation error: at offset 1269295: type mismatch: expression has type i64 but expected f64,那么可能会遇到此错误。

解决方案是升级到Go 1.11.2或更高版本

示例

更多示例点击这里

减少Wasm文件的大小

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

目前有两种主要方法来减小此文件的大小:

1. 手动压缩.wasm文件

  1. 使用gz压缩将示例WASM文件的2MB(最小文件大小)减小到大约500kB。使用Zopfli进行gzip压缩可能会更好,因为它提供的结果比gzip --best更好,但是运行时间要长得多。
  2. 使用Brotli进行压缩,文件大小明显优于Zopfli和gzip --best,并且压缩时间也在两者之间。这种(新的)Brotli压缩工具看起来很合理。

压缩对比

例子1

大小命令压缩时间
16M未压缩大小N/A
2.4Mbrotli -o test.wasm.br test.wasm53.6s
3.3Mgo-zopfli test.wasm3m 2.6s
3.4Mgzip --best test.wasm2.5s
3.4Mgzip test.wasm0.8s

例子2

大小命令压缩时间
2.3M未压缩大小N/A
496Kbrotli -o main.wasm.br main.wasm5.7s
640Kgo-zopfli main.wasm16.2s
660Kgzip --best main.wasm0.2s
668Kgzip main.wasm0.2s

使用 https://github.com/lpar/gzipped 之类的东西来自动提供带有正确标题的压缩文件(如果可用)。

2. 使用TinyGo生成Wasm文件

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

尽管确实有局限性(尚无完整的Go实现),但它仍然相当强大,并且生成的Wasm文件很小。 10kB并不罕见。 “ Hello world”示例为575字节。如果您将gz -6设为gz,它会下降到408个字节。

该项目也非常积极地开发,因此其功能正在迅速扩展。有关将WebAssembly与TinyGo结合使用的更多信息,请参见这里

其他关于WebAssembly的资源

  • Awesome-Wasm-大量其他Wasm资源的清单。不是具体针对Go语言的。
上次修改: 14 April 2020