我利用空闲时间学习Rust和WebAssembly已经有几个月了,说实话,没有什么真正疯狂的东西,只是阅读一些文档、文章和博客,看一些视频和参加一些现场研讨会来了解更多。Rust和WebAssembly密切相关,实际上我应该先学习Rust,但事实上,我的大部分时间都用于更深入地探索WebAssembly,特别是学习Go中已经取得的进展,以及使用不同的包和其他东西与WebAssembly互动的一些方法。
有很多好的文章和视频,涵盖了关于Go和WebAssembly如何工作的真正有趣的技术内容。
- Go的官方维基。
- Go and WebAssembly: running Go programs in your browserby Agniva De Sarker, with a nicelive example,
- Alex Lab的《WebAssembly之旅》。
- WebAssembly介绍|Guy Royse的DevNation Day 2020。
- Robert Aboukhalil的《用WebAssembly提升你的Web应用水平》。
- 伦敦地鼠 15/08/2018:Johan Brandhorst撰写的《开始使用WebAssembly》。
有趣的是,在GopherCon 2020期间,有一个名为Go不只是在你的服务器上,它还在你的浏览器中的演讲。Vugu(Go+WebAssembly UI库)的故事和介绍,作者Brad Peabody,内容包括 Vugu一个用于Go+WebAssembly的现代UI库,真正引起了我的兴趣。
而有趣的是(再次!)我一直在工作,就在最近,在一个内部工具上,碰巧需要一些前端代码,这是开始真正使用Vugu的完美借口。
我从两周前开始,已经发布了这个内部工具的工作版本,恰好与我们的后端API交互,说实话,一切工作都很顺利,最酷的是一切都用Go编写。
然而,由于缺乏使用WebAssembly和Vugu的经验,我最初遇到了一些问题,接下来让我详细介绍一下我是如何解决这些问题的。顺便说一下,所有的代码都可以在Gitlab上找到,欢迎探索并在本地运行它。
渲染Vugu页面的自定义模板
官方文档中提到了对全HTML模式的支持,但我还没能弄明白,最后我的解决方案是通过提供所有资产的服务器来渲染这个模板。
templ := fmt.Sprintf(/* here define the HTML template to be used */)
h := simplehttp.New(wd, false)
h.PageHandler = &simplehttp.PageHandler{
Template: template.Must(template.New("_page_").Parse(templ)),
TemplateDataFunc: simplehttp.DefaultTemplateDataFunc,
}
wasmmain arguments via Javascript.
与上面的问题有一点关系。如果我们需要向最终的wasm工件传递信息,我们应该使用类似的东西。
WebAssembly.instantiateStreaming(fetch("/main.wasm"), go.importObject).then((result) => {
go.argv = ["main.wasm", "-parameter", "%s", "-parameter2", "something"];
go.run(result.instance);
});
例如,在我们需要表明配置细节的情况下,恰好只能通过环境变量通过原始服务器获得。
/health 使用simplehttp检查
如果你使用simplehttp (通过github.com/vugu/vugu/simplehttp )来构建交付资产的服务器,那么你可能需要一种方法来定义一个health-like 端点,以允许你的自动缩放配置工作,这可以通过实现一个PageHandler 的包装器来轻松解决,类似于。
health := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/health" {
// do some actual health checks
w.WriteHeader(http.StatusOK)
return
}
h.ServeHTTP(w, r)
})
}
h := simplehttp.New(wd, false)
h.PageHandler = health(&simplehttp.PageHandler{/* actual configuration */})
渲染更小的wasm文件
到目前为止,最终编译的wasm文件有点大,为了减少它们的文件大小,你需要两个方面的结合。
- 要使用
brotli来压缩最终的wasm文件和。 - 要使用
gzipped.FileServer(通过github.com/lpar/gzipped/v2)将压缩后的资产与相应的头文件(如果客户端支持的话)一起交付。
像下面这样的东西应该可以。
h.StaticHandler = gzipped.FileServer(gzipped.Dir(wd))
不要忘了http.StripPrefix ,以防你要求的路径与本地路径不同。
为了说明情况,最终的文件看起来像这样。
4.6M Dec 20 14:35 main.wasm
980K Dec 20 14:35 main.wasm.br
下载一个文件
最后,这个很有趣,因为它涉及到通过标准库从Go向Javascript传递数据。在链接的例子中,这是在点击"下载文件!"按钮时触发的。
func (r *Root) HandleDownloadFile(e vugu.DOMEvent) {
var output bytes.Buffer
// literal copy/paste from the encoding/csv godoc
records := [][]string{
{"first_name", "last_name", "username"},
{"Rob", "Pike", "rob"},
{"Ken", "Thompson", "ken"},
{"Robert", "Griesemer", "gri"},
}
w := csv.NewWriter(&output)
for _, record := range records {
if err := w.Write(record); err != nil {
log.Fatalln("error writing record to csv:", err)
}
}
w.Flush()
if err := w.Error(); err != nil {
log.Fatal(err)
}
// important part
dst := js.Global().Get("Uint8Array").New(len(output.Bytes()))
_ = js.CopyBytesToJS(dst, output.Bytes())
js.Global().Call("downloadFile", dst, "text/csv", "usernames.csv")
}
真正重要的部分是方法的最后3行
dst := js.Global().Get("Uint8Array").New(len(output.Bytes()))
_ = js.CopyBytesToJS(dst, output.Bytes())
js.Global().Call("downloadFile", dst, "text/csv", "usernames.csv")
Uint8Array之所以使用,是因为它代表一个8位无符号整数的数组,并且记得byte是uint8的别名,因此使它们等同。downloadFile()被调用来将数据从围棋传递到原始的Javascript函数。
这样就可以把数据从Go中传到Javascript中,最后在前台触发下载。
总结
我真的很喜欢Vugu(和WebAssembly),我理解众所周知的局限性,比如用户需要一个现代的浏览器,但不知为何,如果你能控制你的用户是谁,那么使用Vugu来构建Web应用程序就是一个很好的借口。