Go Wiki:WebAssembly
引言
Go 1.11 增加了一個實驗性的 WebAssembly 埠。Go 1.12 對其部分功能進行了改進,Go 1.13 預計會有進一步的改進。Go 1.21 增加了一個針對 WASI 系統呼叫 API 的新埠。
WebAssembly 在其主頁上被描述為
WebAssembly(縮寫為 Wasm)是一種用於基於堆疊的虛擬機器的二進位制指令格式。Wasm 被設計為高階語言(如 C/C++/Rust)編譯的可移植目標,支援在 Web 上部署客戶端和伺服器應用程式。
如果您是 WebAssembly 的新手,請閱讀入門部分,觀看一些Go WebAssembly 演講,然後檢視下面的更多示例。
JavaScript (GOOS=js) 埠
入門
本頁面假設您已安裝功能正常的 Go 1.11 或更新版本。有關故障排除,請參閱安裝故障排除頁面。
如果您使用的是 Windows,我們建議您使用 Git Bash 等 BASH 模擬系統來遵循本教程。
對於 Go 1.23 及更早版本,本文所需的 wasm 支援檔案位於
misc/wasm中,並且在執行諸如lib/wasm/wasm_exec.js之類的檔案操作時應替換路徑。
編譯一個基本的 Go 包以用於 Web
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
設定 GOOS=js 和 GOARCH=wasm 環境變數以編譯 WebAssembly
$ GOOS=js GOARCH=wasm go build -o main.wasm
這將構建包並生成一個名為 main.wasm 的可執行 WebAssembly 模組檔案。 .wasm 副檔名將使其更容易在後續透過 HTTP 以正確的 Content-Type 標頭提供服務。
請注意,您只能編譯主包。否則,您將獲得一個無法在 WebAssembly 中執行的目標檔案。如果您有一個想要與 WebAssembly 一起使用的包,請將其轉換為一個主包並構建一個二進位制檔案。
要在瀏覽器中執行 main.wasm,我們還需要一個 JavaScript 支援檔案和一個 HTML 頁面來將所有內容連線在一起。
複製 JavaScript 支援檔案
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .
建立一個 index.html 檔案
<html>
<head>
<meta charset="utf-8"/>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body></body>
</html>
如果您的瀏覽器尚不支援 WebAssembly.instantiateStreaming,您可以使用polyfill。
然後從 Web 伺服器提供這三個檔案(index.html、wasm_exec.js 和 main.wasm)。例如,使用goexec
# install goexec: go install github.com/shurcooL/goexec@latest
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
或者使用您自己的基本 HTTP 伺服器命令。
注意:編譯器和 wasm_exec.js 支援檔案必須使用相同的 Go 主要版本。也就是說,如果 main.wasm 檔案是使用 Go 1.N 版本編譯的,則相應的 wasm_exec.js 檔案也必須從 Go 1.N 版本複製。不支援其他組合。
注意:對於 goexec 命令在類 Unix 系統上工作,您必須將 Go 的路徑環境變數新增到 shell 的 profile 中。Go 的入門指南對此進行了解釋
將 /usr/local/go/bin 新增到 PATH 環境變數中。您可以透過將此行新增到 /etc/profile(對於系統範圍安裝)或 $HOME/.profile 來實現
export PATH=$PATH:/usr/local/go/bin
注意:對配置檔案所做的更改可能要等到下次登入計算機時才會生效
最後,導航到 https://:8080/index.html,開啟 JavaScript 除錯控制檯,您應該會看到輸出。您可以修改程式,重新構建 main.wasm,然後重新整理以檢視新輸出。
使用 Node.js 執行 WebAssembly
可以使用 Node.js 而不是瀏覽器來執行編譯後的 WebAssembly 模組,這對於測試和自動化很有用。
首先,請確保已安裝 Node 並且它在您的 PATH 中。
然後,將 $(go env GOROOT)/lib/wasm 新增到您的 PATH 中。這將允許 go run 和 go test 在 PATH 搜尋中找到 go_js_wasm_exec 並使用它來直接用於 js/wasm
$ export PATH="$PATH:$(go env GOROOT)/lib/wasm"
$ GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test
PASS
ok example.org/my/pkg 0.800s
如果您正在 Go 本身工作,這還將允許您無縫執行 run.bash。
go_js_wasm_exec 是一個包裝器,允許在 Node 中執行 Go Wasm 二進位制檔案。預設情況下,它可以在 Go 安裝的 lib/wasm 目錄中找到。
如果您不想將任何內容新增到 PATH 中,您也可以在手動執行 go run 或 go test 時,將 -exec 標誌設定為 go_js_wasm_exec 的位置。
$ GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec"
PASS
ok example.org/my/pkg 0.800s
最後,包裝器也可以直接執行 Go Wasm 二進位制檔案
$ GOOS=js GOARCH=wasm go build -o mybin .
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./mybin
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -c
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./pkg.test
PASS
ok example.org/my/pkg 0.800s
在瀏覽器中執行測試
您還可以使用 wasmbrowsertest 在瀏覽器中執行測試。它自動化了啟動 Web 伺服器的工作,並使用無頭 Chrome 在其中執行測試並將日誌中繼到您的控制檯。
和以前一樣,只需 go get github.com/agnivade/wasmbrowsertest 即可獲取二進位制檔案。將其重新命名為 go_js_wasm_exec 並將其放置到您的 PATH 中
$ mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec
$ export PATH="$PATH:$GOPATH/bin"
$ GOOS=js GOARCH=wasm go test
PASS
ok example.org/my/pkg 0.800s
或者,使用 exec 測試標誌。
GOOS=js GOARCH=wasm go test -exec="$GOPATH/bin/wasmbrowsertest"
與 DOM 互動
請參閱https://pkg.go.dev/syscall/js。
另外
-
app:一個相容 PWA、基於 React 的框架,帶有自定義工具。 -
dom:一個用於簡化 DOM 操作的庫正在開發中。 -
dom:JavaScript DOM API 的 Go 繫結。 -
domui:一個純 Go 框架,用於建立完整的 GUI 應用程式。 -
gas:用於 WebAssembly 應用程式的基於元件的框架。 -
GoWebian:一個用純 Go 構建頁面並新增 WebAssembly 繫結的庫。
-
hogusuru:一個高階 webassembly 框架,直接在 GO 中實現了瀏覽器的大部分功能(包括 indexeddb、serviceworker、websocket 等)。 -
VECTY:使用 WebAssembly 在 Go 中構建響應式和動態 Web 前端,與 React 和 VueJS 等現代 Web 框架競爭。
-
vert:Go 和 JS 值之間的 WebAssembly 互操作。 -
vue:用於 WebAssembly 應用程式的漸進式框架。 -
Vugu:一個 wasm Web UI 庫,具有 HTML 佈局和 Go 應用程式邏輯、單檔案元件、快速開發和原型設計工作流程。
-
webapi:DOM、HTML、WebGL 等的繫結生成器和生成的繫結。
Canvas
- 一個新的canvas 繪相簿——看起來效率很高。
使用 net/http 配置 fetch 選項
您可以使用 net/http 庫從 Go 發出 HTTP 請求,它們將被轉換為 fetch 呼叫。但是,fetch 選項和 http 客戶端選項之間沒有直接對映。為了實現這一點,我們有一些特殊的標頭值被識別為 fetch 選項。它們是 -
-
js.fetch:mode:Fetch API 模式設定的一個選項。有效值為:“cors”、“no-cors”、“same-origin”、“navigate”。預設值為“same-origin”。 -
js.fetch:credentials:Fetch API 憑據設定的一個選項。有效值為:“omit”、“same-origin”、“include”。預設值為“same-origin”。 -
js.fetch:redirect:Fetch API 重定向設定的一個選項。有效值為:“follow”、“error”、“manual”。預設值為“follow”。
因此,舉例來說,如果我們想在發出請求時將模式設定為“cors”,它將是這樣的
req, err := http.NewRequest("GET", "https://:8080", nil)
req.Header.Add("js.fetch:mode", "cors")
if err != nil {
fmt.Println(err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
// handle the response
請隨時訂閱#26769以獲取更多上下文和可能更新的資訊。
Chrome 中的 WebAssembly
如果您執行較新版本的 Chrome,有一個標誌 (chrome://flags/#enable-webassembly-baseline) 可以啟用 Liftoff,即他們的新編譯器,這應該會顯著縮短載入時間。更多資訊請參見此處。
更多示例
通用
- Shimmer - 使用 Go 在 wasm 中進行影像轉換。即時演示。
- 影片過濾 - 來自網路攝像頭的影片過濾器(原始碼)
- HandyTools - 提供諸如 base64 編碼/解碼、轉換 Unix 時間等工具(即時演示)
Canvas (2D)
- GoWasm 實驗 - 演示了幾種常見呼叫型別的工作程式碼
- Gomeboycolor-wasm
- 實驗性 Gameboy Color 模擬器的 WASM 埠。相關部落格文章包含一些有趣的技術見解。
- TinyGo canvas
- 這使用 TinyGo 而不是標準 Go 編譯,生成一個 19.37kB(壓縮) 的 wasm 檔案。
- Car and Mouse
- 一個遊戲,您透過用游標引導一輛小的 Canvas 繪製汽車來獲得積分
資料庫
- TiDB-Wasm - 在瀏覽器中執行 TiDB(一個 golang 資料庫)在 Wasm 上。
WebGL canvas (3D)
- 基本三角形(原始碼) - 在 WebGL 中建立一個基本三角形
- 相同的功能,移植到 TinyGo(原始碼) - 約 14kB 壓縮(主線 Go 版本的 3%)
- 旋轉立方體(原始碼) - 在 WebGL 中建立一個旋轉立方體
- 相同的功能,移植到 TinyGo(原始碼) - 約 23kB 壓縮(主線 Go 版本的 4%)
- Splashy(原始碼) - 在螢幕上點選以生成顏料…
WASI (GOOS=wasip1) 埠
入門 (WASI)
Go 1.21 引入了 WASI 作為支援的平臺。要為 WASI 構建,請使用 wasip1 埠
$ GOOS=wasip1 GOARCH=wasm go build -o main.wasm
官方部落格有一篇關於使用 WASI 埠的有用介紹:https://golang.com.tw/blog/wasi。
Go WebAssembly 演講
編輯器配置
- 為 WebAssembly 配置 GoLand 和 Intellij Ultimate - 展示了在 GoLand 和 Intellij Ultimate 中使 Wasm 工作的確切步驟
除錯
WebAssembly 尚不支援偵錯程式,因此您現在需要使用傳統的 println() 方法在 JavaScript 控制檯上顯示輸出。
一個官方的 WebAssembly 除錯小組已經成立,旨在解決這個問題,一些初步的調查和提案正在進行中
如果您對偵錯程式方面感興趣,請參與並幫助推動這項工作。:smile
分析 WebAssembly 檔案的結構
WebAssembly Code Explorer 對於視覺化 WebAssembly 檔案的結構很有用。
- 點選左側的十六進位制值將突出顯示其所屬的部分,以及右側的相應文字表示
- 點選右側的一行將突出顯示左側的十六進位制位元組表示
減小 Wasm 檔案的大小
目前,Go 生成的 Wasm 檔案很大,最小可能的大小約為 2MB。如果您的 Go 程式碼匯入庫,此檔案大小可能會急劇增加。超過 10MB 很常見。
目前(暫時)有兩種主要方法可以減小此檔案大小
-
手動壓縮 .wasm 檔案。
- 使用
gz壓縮將約 2MB(最小檔案大小)的 WASM 示例檔案減小到約 500kB。最好使用 Zopfli 進行 gzip 壓縮,因為它比gzip --best效果更好,但執行時間也更長。 - 使用 Brotli 進行壓縮,檔案大小明顯優於 Zopfli 和
gzip --best,並且壓縮時間介於兩者之間。這個 (新的)Brotli 壓縮器看起來不錯。
來自 @johanbrandhorst 的示例
示例 1
大小 命令 壓縮時間 16M (未壓縮大小) 不適用 2.4M brotli -o test.wasm.br test.wasm53.6s 3.3M go-zopfli test.wasm3m 2.6s 3.4M gzip --best test.wasm2.5s 3.4M gzip test.wasm0.8s 示例 2
大小 命令 壓縮時間 2.3M (未壓縮大小) 不適用 496K brotli -o main.wasm.br main.wasm5.7s 640K go-zopfli main.wasm16.2s 660K gzip --best main.wasm0.2s 668K gzip main.wasm0.2s 使用 https://github.com/lpar/gzipped 之類的工具,在可用時自動提供帶有正確標頭的壓縮檔案。
- 使用
-
改用 TinyGo 生成 Wasm 檔案。
TinyGo 支援 Go 語言的一個子集,目標是嵌入式裝置,並有一個 WebAssembly 輸出目標。
雖然它確實有侷限性(尚未完全實現 Go),但它仍然相當強大,並且生成的 Wasm 檔案……很小。大約 10kB 並不罕見。“Hello world”示例只有 575 位元組。如果您對其進行
gz -6壓縮,它會下降到 408 位元組。:wink該專案也在積極開發中,因此其功能正在迅速擴充套件。有關使用 TinyGo 和 WebAssembly 的更多資訊,請參見 https://tinygo.org/docs/guides/webassembly/。
其他 WebAssembly 資源
- Awesome-Wasm - 大量其他 Wasm 資源的列表。非 Go 特定。
此內容是 Go Wiki 的一部分。