【譯】Go和WebAssembly:在瀏覽器中執行Go程式

他鄉啟程發表於2019-01-14

在過去很長一段時間裡,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所需要的東西。

【譯】Go和WebAssembly:在瀏覽器中執行Go程式

然後,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和WebAssembly:在瀏覽器中執行Go程式

真牛,我們剛剛在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並將其傳送回瀏覽器。這是完整的應用程式:

載入圖片:

【譯】Go和WebAssembly:在瀏覽器中執行Go程式

改變對比:

【譯】Go和WebAssembly:在瀏覽器中執行Go程式

改變色調:

【譯】Go和WebAssembly:在瀏覽器中執行Go程式

太棒了,我們可以在瀏覽器中本地操作影像而無需編寫一行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

【譯】Go和WebAssembly:在瀏覽器中執行Go程式

相關文章