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=jsGOARCH=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.htmlwasm_exec.jsmain.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 rungo testPATH 搜尋中找到 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 rungo 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 等的繫結生成器和生成的繫結。

  • webgen:在 HTML 中定義元件,並使用 webapi 為它們生成 Go 型別和建構函式。

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,即他們的新編譯器,這應該會顯著縮短載入時間。更多資訊請參見此處

更多示例

通用

Canvas (2D)

資料庫

  • TiDB-Wasm - 在瀏覽器中執行 TiDB(一個 golang 資料庫)在 Wasm 上。

WebGL canvas (3D)

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 尚不支援偵錯程式,因此您現在需要使用傳統的 println() 方法在 JavaScript 控制檯上顯示輸出。

一個官方的 WebAssembly 除錯小組已經成立,旨在解決這個問題,一些初步的調查和提案正在進行中

如果您對偵錯程式方面感興趣,請參與並幫助推動這項工作。:smile

分析 WebAssembly 檔案的結構

WebAssembly Code Explorer 對於視覺化 WebAssembly 檔案的結構很有用。

  • 點選左側的十六進位制值將突出顯示其所屬的部分,以及右側的相應文字表示
  • 點選右側的一行將突出顯示左側的十六進位制位元組表示

減小 Wasm 檔案的大小

目前,Go 生成的 Wasm 檔案很大,最小可能的大小約為 2MB。如果您的 Go 程式碼匯入庫,此檔案大小可能會急劇增加。超過 10MB 很常見。

目前(暫時)有兩種主要方法可以減小此檔案大小

  1. 手動壓縮 .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.6s
    3.3M go-zopfli test.wasm 3m 2.6s
    3.4M gzip --best test.wasm 2.5s
    3.4M gzip test.wasm 0.8s

    示例 2

    大小 命令 壓縮時間
    2.3M (未壓縮大小) 不適用
    496K brotli -o main.wasm.br main.wasm 5.7s
    640K go-zopfli main.wasm 16.2s
    660K gzip --best main.wasm 0.2s
    668K gzip main.wasm 0.2s

    使用 https://github.com/lpar/gzipped 之類的工具,在可用時自動提供帶有正確標頭的壓縮檔案。

  2. 改用 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 的一部分。