【譯】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平臺程式。我們需要設定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頻道。