【译】Go和WebAssembly:在浏览器中运行Go程序

在过去很长一段时间里,Javascript是Web开发人员中的通用语言。如果你想写一个稳定成熟的 Web 应用程序,用javascript几乎是唯一的方法。

WebAssembly(也称为wasm)将很快改变这种情况。使用WebAssembly可以用任何语言编写Web应用程序。在本文中,我们将了解如何编写Go程序并使用wasm在浏览器中运行它们。

但首先,什么是WebAssembly

webassembly.org 将其定义为“基于堆栈的虚拟机的二进制指令格式”。这是一个很好的定义,但让我们将其分解为我们可以轻松理解的内容。

从本质上讲,wasm是一种二进制格式; 就像ELF,Mach和PE一样。唯一的区别是它适用于虚拟编译目标,而不是实际的物理机器。为何虚拟?因为不同于 C/C++ 二进制文件,wasm二进制文件不针对特定平台。因此,您可以在Linux,Windows和Mac中使用相同的二进制文件而无需进行任何更改。 因此,我们需要另一个“代理”,它将二进制文件中的wasm指令转换为特定于平台的指令并运行它们。通常,这个“代理”是一个浏览器,但从理论上讲,它也可以是其他任何东西。

这为我们提供了一个通用的编译目标,可以使用我们选择的任何编程语言构建Web应用程序!只要我们编译为wasm格式,我们就不必担心目标平台。就像我们编写一个Web应用程序一样,但是现在我们有了用我们选择的任何语言编写它的优势。

你好 WASM

让我们从一个简单的“hello world”程序开始,但是要确保您的Go版本至少为1.11。我们可以这样写:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("hello wasm")
}

保存为test.go。看起来像是一个普通的Go程序。现在让我们将它编译为wasm平台程序。我们需要设置GOOSGOARCH

$GOOS=js GOARCH=wasm go build -o test.wasm test.go

现在我们生成了 wasm 二进制文件。但与原生系统不同,我们需要在浏览器中运行它。为此,还需要再做一点工作来实现这一目标:

  • Web服务器来运行应用
  • 一个index.html文件,其中包含加载wasm二进制文件所需的一些js代码。
  • 还有一个js文件,它作为浏览器和我们的wasm二进制文件之间的通信接口。

我喜欢把它想象成制作The PowerPuff Girls所需要的东西。

然后,BOOM,我们有了一个WebAssembly应用程序!

现在Go目录中已经包含了html和js文件,因此我们将其复制过来。

$cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
$cp "$(go env GOROOT)/misc/wasm/wasm_exec.html" .
$# we rename the html file to index.html for convenience.
$mv wasm_exec.html index.html
$ls -l
total 8960
-rw-r--r-- 1 agniva agniva    1258 Dec  6 12:16 index.html
-rwxrwxr-x 1 agniva agniva 6721905 Sep 24 12:28 serve
-rw-rw-r-- 1 agniva agniva      76 Dec  6 12:08 test.go
-rwxrwxr-x 1 agniva agniva 2425246 Dec  6 12:09 test.wasm
-rw-r--r-- 1 agniva agniva   11905 Dec  6 12:16 wasm_exec.js

serve是Go二进制文件,是一个Web服务器。但几乎任何Web服务器都可以。(译者注:原文并没有提供serve二进制文件的源代码,相信聪明的你一定知道怎样编写。)

一旦运行它,并打开浏览器。可以看到一个Run按钮,点击它,将执行我们的应用程序。然后我们点击它并检查控制台:

真牛,我们刚刚在Go中编写了一个程序并在浏览器中运行它。

到现在为止一切顺利。但这是一个简单的“hello world”程序。真实的Web应用程序需要与DOM交互。我们需要响应按钮单击事件,从文本框中获取输入数据,并将数据发送回DOM。现在我们将构建一个最小的图像编辑器,它将使用所有这些功能。

DOM API

但首先,要使Go代码与浏览器进行交互,我们需要一个DOM API。我们有syscall/js库来帮助我们解决这个问题。它是一个非常简单却功能强大的DOM API形式,我们可以在其上构建我们的应用程序。在我们制作应用程序之前,让我们快速了解它的一些功能。

回调

为了响应DOM事件,我们声明了回调并用这样的事件将它们连接起来:

import "syscall/js"

// Declare callback
cb := js.NewEventCallback(js.PreventDefault, func(ev js.Value) {
	// handle event
})


// Hook it up with a DOM event
js.Global().Get("document").
	Call("getElementById", "myBtn").
	Call("addEventListener", "click", cb)


// Call cb.Release() on your way out.

更新DOM

要从Go中更新DOM,我们可以

import "syscall/js"

js.Global().Get("document").
		Call("getElementById", "myTextBox").
		Set("value", "hello wasm")

您甚至可以调用JS函数并操作本机JS对象,如 FileReaderCanvas。查看syscall/js文档以获取更多详细信息。

正确的 Web 应用程序

接下来我们将构建一个小应用程序,它将获取输入的图像,然后对图像执行一些操作,如亮度,对比度,色调,饱和度,最后将输出图像发送回浏览器。 每个效果都会有滑块,用户可以更改这些效果并实时查看目标图像的变化。

首先,我们需要从浏览器获取输入的图像给到我们的Go代码,以便可以处理它。为了有效地做到这一点,我们需要采取一些不安全的技巧,这里跳过具体细节。拥有图像后,它完全在我们的控制之下,我们可以自由地做任何事情。下面是图像加载器回调的简短片段,为简洁起见略有简化:

onImgLoadCb = js.NewCallback(func(args []js.Value) {
	reader := bytes.NewReader(inBuf) // inBuf is a []uint8 slice where our image is loaded
	sourceImg, _, err := image.Decode(reader)
	if err != nil {
		// handle error
	}
	// Now the sourceImg is an image.Image with which we are free to do anything!
})

js.Global().Set("loadImage", onImgLoadCb)

然后我们从效果滑块中获取用户值,并操纵图像。我们使用了很棒的bild库。下面是回调的一小部分:

import "github.com/anthonynsimon/bild/adjust"

contrastCb = js.NewEventCallback(js.PreventDefault, func(ev js.Value) {
	delta := ev.Get("target").Get("valueAsNumber").Float()
	res := adjust.Contrast(sourceImg, delta)
})

js.Global().Get("document").
		Call("getElementById", "contrast").
		Call("addEventListener", "change", contrastCb)

在此之后,我们将目标图像编码为jpeg并将其发送回浏览器。这是完整的应用程序:

加载图片:

改变对比:

改变色调:

太棒了,我们可以在浏览器中本地操作图像而无需编写一行Javascript! 源代码可以在这里找到。

请注意,所有这些都是在浏览器本身中完成的。这里没有Flash插件,Java Applet或Silverlight。而是使用浏览器本身支持的开箱即用的WebAssembly。

最后的话

我的一些结束语:

  • 由于Go是一种垃圾收集语言,因此整个运行时都在wasm二进制文件中。因此,二进制文件通常有几MB的大小。与C/Rust等其他语言相比,这仍然是一个痛点; 因为向浏览器发送MB级数据并不理想。但是,如果wasm规范本身支持GC,那么这可能会改变。

  • Go中的Wasm支持正式进行试验。syscall/js API本身也在不断变化,未来可能会发生变化。如果您发现错误,请随时在我们issues报告问题。

  • 与所有技术一样,WebAssembly也不是一颗银弹。有时,简单的JS更快更容易编写。然而,wasm规范本身正在开发中,并且即将推出更多功能。线程支持就是这样一个特性。

希望这篇文章展示了WebAssembly的一些很酷的方面,以及如何使用Go编写功能齐全的Web应用程序。如果您发现错误,请尝试一下,并提出问题。如果您需要任何帮助,请随时访问 #webassembly频道。

原文链接

Go and WebAssembly: running Go programs in your browser