在过去很长一段时间里,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平台程序。我们需要设置GOOS
和GOARCH
。
$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对象,如 FileReader
或Canvas
。查看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频道。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。