Go Wiki:WebAssembly
簡介
Go 1.11 加入 WebAssembly 的實驗性移植。Go 1.12 已改善部分內容,而 Go 1.13 預計會有進一步的改善。Go 1.21 加入新的移植目標,鎖定 WASI 系統呼叫 API。
如 首頁 中所述
WebAssembly(簡稱 Wasm)是一種為基於堆疊的虛擬機器設計的二進位指令格式。Wasm 是針對 C/C++/Rust 等高階語言進行編譯的可移植目標,讓客戶端和伺服器應用程式可以在網路部署。
如果您不瞭解 WebAssembly,請閱讀 入門,觀看一些 Go WebAssembly 的演講,然後查看下方的 其他範例。
JavaScript (GOOS=js) 移植
入門
此頁面假設已安裝運作良好的 Go 1.11 或更新版本。如需疑難排解協助,請參閱 安裝疑難排解 頁面。
如果您使用 Windows,我們建議您使用 Git Bash 等 BASH 模擬器系統來遵循本教學。
對於 Go 1.23 及其之前的版本,本文所需要的 Webassembly 支援檔案位於
misc/wasm
中,在對檔案例如lib/wasm/wasm_exec.js
執行操作時應該替換路徑。
為 Web 編譯基本 Go 套件
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 檔案副檔名將使其更容易在稍後使用正確的 Content-Type 標頭透過 HTTP 提供服務。
請注意,你只能編譯主套件。否則,你會取得無法在 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。
然後從網路伺服器提供這三個檔案 (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 的路徑環境變數 新增到殼層的 profile
中。Go 的入門指南有解釋如何執行這項操作
將 /usr/local/go/bin 新增到 PATH 環境變數。這可以透過將這一列新增到你的 /etc/profile (對於系統級安裝) 或 $HOME/.profile 中來達成
export PATH=$PATH:/usr/local/go/bin
請注意:對設定檔所做的變更可能要等到下次你登入電腦後才會套用
最後,導航至 https://127.0.0.1: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,在你的瀏覽器中執行測試。它會自動執行啟動網路伺服器的工作,並使用無界面的 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 框架,實現瀏覽器中的大多數功能(包括索引式資料庫、ServiceWorker、WebSocket 等),可直接以 GO 進行存取。 -
VECTY:使用 WebAssembly 在 Go 中建立有回應式且動態的網頁前端,與 React 和 VueJS 等現代網頁框架競爭。
-
vert
:Go 與 JS 值之間的 WebAssembly 互通。 -
vue
:進步的 WebAssembly 應用程式框架。 -
Vugu:一個 wasm 網頁 UI 函式庫,採用 HTML 布局、Go 進行應用程式邏輯、單檔案元件、快速開發和原型製作工作流程。
-
webapi
:結合產生器與 DOM、HTML、WebGL 等已產生的結合。
畫布
使用 net/http 時組態提取選項
你可以使用 net/http 函式庫從 Go 發出 HTTP 要求,它們會轉換成 提取 呼叫。然而,提取 選項 與 http 客戶端 選項之間沒有直接對應。為達成此目的,我們有一些特別標題值可以辨識為提取選項。這些值包括 -
-
js.fetch:mode
:提取 API 模式設定的選項。有效值包括:「cors」、「no-cors」、「same-origin」、「navigate」。預設值為「same-origin」。 -
js.fetch:credentials
:提取 API 認證設定的選項。有效值包括:「omit」、「same-origin」、「include」。預設值為「same-origin」。 -
js.fetch:redirect
:提取 API 重新導向設定的選項。有效值包括:「follow」、「error」、「manual」。預設值為「follow」。
因此,以範例來說,如果我們希望在發出要求時設定模式為「cors」,類似於
req, err := http.NewRequest("GET", "https://127.0.0.1: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 時間等工具 (實況 示範)
畫布 (2D)
- GoWasm 實驗 - 展示針對多種常見呼叫類型運作的程式碼
- Gomeboycolor-wasm
- WASM 移植的實驗性 Gameboy Color 模擬器。相配的部落格文章 有一些有趣的技術見解。
- TinyGo 畫布
- 使用 TinyGo,而非標準 Go 編譯,產生一個 19.37kB(壓縮) 的 wasm 檔案。
- 車子和老鼠
- 一個遊戲,你可以透過用游標引導一個小畫布繪製的車子,來獲得積分
資料庫
- TiDB-Wasm - 在 Wasm 上,用瀏覽器執行 TiDB,一個 golang 資料庫。
WebGL 畫布(3D)
- 基本的三角形 (原始碼) - 在 WebGL 中建立一個基本的三角形
- 同樣的程式,移植到 TinyGo (原始碼) - 壓縮後約 14kB(主線 Go 版的三趴大小)
- 旋轉的立方體 (原始碼) - 在 WebGL 中建立一個旋轉的立方體
- 同樣的程式,移植到 TinyGo (原始碼) - 壓縮後約 23kB(主線 Go 版的四趴大小)
- Splashy (原始碼) - 在螢幕上點來點去產生顏料…
WASI (GOOS=wasip1) 移植
開始使用(WASI)
Go 1.21 加入 WASI 作為一個受支援的平台。要建立給 WASI,請使用 wasip1
埠
$ GOOS=wasip1 GOARCH=wasm go build -o main.wasm
官方部落格有關於使用 WASI 埠的實用指南:https://go.dev.org.tw/blog/wasi。
Go WebAssembly 演講
編輯器組態
- 設定 GoLand 和 Intellij Ultimate 以使用 WebAssembly - 指出在 GoLand 和 Intellij Ultimate 中啟用 Wasm 所需的步驟
除錯
WebAssembly 目前尚不支援任何除錯工具,因此您目前必須使用老式的 println()
方法在 JavaScript 主控台中顯示輸出。
為了解決這個問題,已成立官方的 WebAssembly 除錯小組,並陸續進行初步調查和提議
如果您有興趣參與除錯工作,請盡情參與並協助推動。:smile
分析 WebAssembly 檔案的結構
WebAssembly 程式碼瀏覽器 可用於將 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.wasm
53.6 秒 3.3M go-zopfli test.wasm
3 分鐘 2.6 秒 3.4M gzip --best test.wasm
2.5 秒 3.4M gzip test.wasm
0.8 秒 範例 2
大小 指令 壓縮時間 2.3M (未壓縮大小) 不適用 496K brotli -o main.wasm.br main.wasm
5.7 秒 640K go-zopfli main.wasm
16.2 秒 660K gzip --best main.wasm
0.2 秒 668K gzip main.wasm
0.2 秒 使用像 https://github.com/lpar/gzipped 這樣的東西,在有壓縮檔和正確的標頭時自動提供服務。
- 使用
-
使用 TinyGo 來產生 Wasm 檔。
TinyGo 支援針對嵌入式裝置的 Go 語言子集,並有 WebAssembly 輸出目標。
雖然它有一些限制(還不是完整的 Go 實作),但它仍然相當有能力,且產生的 Wasm 檔案非常小。大約 10kB 並不罕見。「Hello world」範例是 575 位元組。如果你使用
gz -6
壓縮,它會降到 408 位元組。 :wink這個專案也積極開發中,因此它的功能正快速擴充。見 https://tinygo.org/docs/guides/webassembly/ 以取得更多有關使用 TinyGo 和 WebAssembly 的資訊。
其他 WebAssembly 資源
- Awesome-Wasm - 一份涵蓋更深入的 Wasm 資源清單。不特定於 Go。
這個內容是 Go Wiki 的一部分。