前言
在本文中,我們將使用 Golang WebAssembly 創建一個簡單的 TODO 應用,完全使用 Golang 來操作 DOM,整個代碼的複雜度不高,但足以用來學習 Golang 撰寫 WebAssembly 的基本技巧。
成果
環境
- go version go1.19.3 linux/amd64
讓開發順利的前置作業
若是使用 Visual Studio Code 開發 WebAssembly,大概率會遇到 could not import syscall/js
的提示,這是因為 syscall/js
是在 wasm
架構上才能引用的,而 VSCode 預設不會將 wasm
視為編譯的目標架構 (GOARCH
),這時候需要在 .vscode
目錄中創建如下內容的 settings.json
,讓 VSCode 知道我們的目標架構是 wasm
1 | { |
TODO 應用原始碼說明
Source Code On GitHub 提供完整的程式碼可以參考,原始碼中有非常詳細的註解,請讀者直接閱讀註解來學習,這裡僅說明 syscall/js
相關函數的使用方法。
js.Value
對 DOM 操作取得的資料 (包含 Element, 資料等等) 為 js.Value
型態。例如:js.Global()
為取得 window
物件 (在 Browser 環境) 或 global
物件 (在 Node 環境) 的 Function,回傳的型態為 js.Value
。
js.Value.Get()
js.Value.Get()
可以取得該物件的屬性 (Property)。
例如:js.Global().Get("document")
就是取得 window
物件的 document
Property,即等同 JavaScript 的 window.document
。
1 | document = js.Global().Get("document") |
js.Value.Call()
js.Value.Call()
可以呼叫該物件的方法 (Method),並提供輸入參數。
例如:document.Call("getElementById", "root")
就是呼叫 document
物件的 getElementById
Method 並將 "root"
作為該 Method 的輸入參數,即等同 JavaScript 的 document.getElementById("root")
。
1 | root = document.Call("getElementById", "root") |
js.Value.Set()
js.Value.Set()
可以設定該物件的 Property,並提供要設定的值。
例如:header.Set("textContent", "Golang Web Assembly for TODO :)")
就是設定該 Element 的 Property textContent
為 "Golang Web Assembly for TODO :)"
,即等同 JavaScript 的 header.textContent = "Golang Web Assembly for TODO :)"
。
1 | header.Set("textContent", "Golang Web Assembly for TODO :)") |
js.Value.Bool()
當確定 js.Value
為 Bool
型態時,可以透過 js.Value.Bool()
將其轉換為 Bool
。
例如:event.Get("currentTarget").Get("checked").Bool()
就是將 event.currentTarget.checked
的值轉為 Bool
1 | todo.Completed = event.Get("currentTarget").Get("checked").Bool() |
當然還有像 js.Value.String()
, js.Value.Int()
等等取值的 Methods 可以使用,這裡就不一一列舉。要注意的一點是若轉換失敗會 panic
,例如:該 js.Value
不是 Bool
,卻使用 Bool()
Method 取值則會 panic
。
js.FuncOf
當需要將 Golang 的 Function 暴露給 Browser 使用時,必須使用 js.FuncOf
將符合以下簽名形式的 Function 包裝起來
1 | func(this js.Value, args []js.Value) any |
this
: 能透過此參數使用 JavaScript 中的this
物件。args
: 呼叫該 Function 時所帶的輸入參數。- 回傳值:從 Golang 回傳給 Browser 的值。
在原始碼中會看到每個 addEventListener
都是使用這樣的 Function,例如
1 | checkBox.Call("addEventListener", "change", js.FuncOf(func(this js.Value, args []js.Value) any { |
編譯
指定 GOARCH=wasm
與 GOOS=js
,同時將編譯出的 wasm
檔案存到 ./build/
目錄下且命名為 main.wasm
1 | GOARCH=wasm GOOS=js go build -o ./build/main.wasm |
index.html
導入 main.wasm
到 html
使用前,還有一件重要的事要先做,也就是需要先引用 wasm_exec.js
(它的位置在 $(go env GOROOT)/misc/wasm/wasm_exec.js
,可以將它複製到目標目錄下),我們才可以執行 new Go()
,再透過 WebAssembly.instantiateStreaming
取得 main.wasm
並編譯成 Browser 可以執行的代碼,最後再使用 go.run()
運行就大功告成
1 | <head> |
進一步練習的方向
(難度 ★☆☆) 讓 Task 的順序可以被調整。
(難度 ★★☆) 將資料儲存在 Browser,讓資料在頁面重新整理後不會消失。
(難度 ★★★) 目前 Re-render 機制是當每修改一個資料就要執行一次整個清單的 Render,這樣的機制會造成效率低下,讀者可以思考如何只重新 Render 需要 Render 的地方。
結語
在這篇文章中,我們學習了如何使用 Golang WebAssembly 編寫一個 TODO 應用,然後將其編譯成 (*.wasm
),並將 *.wasm
引入到 HTML 頁面中。最後,我們成功地創建了一個可運行在瀏覽器中的 TODO 應用程序。
使用 WebAssembly,可以編寫高效的應用程序,並將其部署到 Web 平臺上。由於 WebAssembly 能夠與現有 Web 技術相互操作,因此它在實現高效計算、加速圖形和數據處理等方面具有潛力。希望這篇文章能夠幫助讀者更好地理解如何使用 Golang WebAssembly 編寫 Web 應用程序。