WebAssembly

WebAssembly 實戰 – 讓 Go 與 JS 在瀏覽器上共舞

Larry Lu
Starbugs Weekly 星巴哥技術專欄
8 min readNov 3, 2019

--

如果你有在關注前端技術的話應該多少有聽過 WebAssembly(Wasm) 這東西,WebAssembly 是一種跑在瀏覽器上的低階語言,他在瀏覽器上能 以接近原生應用的效能執行,所以以前受限於效能而比較難做到的那些應用(高畫質遊戲、繪圖軟體等等),以後很有可能在網頁上就可以做到,光想到就覺得好興奮啊啊啊

雖然 WebAssembly 聽起來很厲害,但他的語法可不是普通的難寫

下圖是我用 WebAssembly 寫了個 fib 函數來求費氏數列的某一項。其他語言的 n-1 在 Wasm 裡面必須寫成 (i32.sub (get_local $n) (i32.const 1)) 才行,真的很有組合語言的 fu 呢 😂,原本兩三行就能解決的 fib 現在非得寫成好幾行不可,光想到頭就痛了

fib in Wasm

慶幸的是現在很多語言(C++/Go/Rust)都能透過 Emscripten 編譯成 WebAssembly,所以其實你不需要真的寫 WebAssembly,只需要把你寫完的 C++/Go/Rust 程式碼編譯成 Wasm 就好

如果從前端技術發展的角度來看:只要瀏覽器可以執行 Wasm,那各語言的開發者就能用他們熟悉的語言開發功能,然後再編譯成 Wasm 就能在瀏覽器上執行。也就是說

瀏覽器終於不是只能跑 JavaScript,而是可以跑任何能編譯成 Wasm 的語言 🎉🎉🎉

而這篇文章就是要實際用 Go 完成一些功能、編譯成 Wasm 在網頁上執行,並且比較 Wasm 跟原生 JS 的 效能

雖然說會用到 Go,但如果你對他不熟悉也沒關係,因為 Go 這個語言還滿好學的,稍微看一下官網的 A Tour of Go 就可以上工了~

瀏覽器支援

雖然 WebAssembly 這東西聽起來很高科技XD,但其實主流的瀏覽器都已經支援了哦~除非你的網站需要支援 IE 或是比較舊款的手機,不然基本上不會有問題

目前也已經有人用 WebAssembly 做出一些應用,像下面這個專案是把 Vim 編譯成 Wasm,並且讓他跑在瀏覽器上

從 Devtool 也可以看到他確實是用 WebAssembly 實作的哦(黃框處 application/wasm

上面說了這麼多都是在講 WebAssembly 技術的近況,接下來終於要開始寫扣了(摩拳擦掌

事前準備

等等會用到 Go 提供的 wasm_exec.js 作為 Wasm 跟 JS 之間溝通的橋樑,另外還需要啟動一個 Web Server(我是用 simplehttpserver),先把他們準備好

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
npm install -g simplehttpserver

Hello World in Go

View on Github

學任何語言都要從 Hello World 開始學起,用 Wasm 當然也是,先用 Go 寫一個簡單的 Hello World

編譯時加上 GOOS=jsGOARCH=wasm,這樣 Go 就會編譯出一個 main.wasm

GOOS=js GOARCH=wasm go build -o main.wasm

為了讓瀏覽器載入編譯完的 Wasm 檔案,再寫一個 index.html 來抓 main.wasm

之後下 simplehttpserver . 把 Web Server 跑起來就能在瀏覽器 Console 看到可愛的 Hello World,而且這個 Hello World 是用 Go 寫的哦,很神奇吧~

syscall/js

上面已經成功跑出 Go 寫的 Hello World,很多文章也都只有講到這裡。但我猜會用 WebAssembly 大部分都是為了解決效能的問題,所以來說說要怎麼用 Go 寫一些高效能的 function 給 JS 用

為了達到這個目的,Go 有提供一個 syscall/js package 在處理 Go 跟 JS 之間的型別轉換、函數綁定:譬如說把 int 對應到 Number、還有讓 JS 能夠使用 Go 寫的函數,如此一來就能把需要效能的地方用 Go 寫

下面的例子是用 Go 實作一個 add 負責把 args[0]args[1] 相加,並且把 add 放到全域給 JS 用

View on Github

因為 add function 已經被編譯成 WebAssembly 並放到全域,所以在 Console 裡面可以直接用

還是要再強調一次,這個 add是用 Go 寫的哦,超酷的~

既然用 JS 跟 Go(WebAssembly) 都可以完成同樣的功能,那他們的效能比起來怎麼樣?

我分別用 Go 跟 JS,實作了同樣的演算法(Merge Sort)來排序一百萬個整數,大家猜猜 Wasm 的效能會快多少?是二倍五倍還是甚至十倍呢?

View on Github

其實測出來竟然沒快多少XDD,原本還以為會快個三五倍 😂,但不管是在 Chrome 還是 Firefox 上測,WebAssembly 大概都只快了 10% 左右,看來 Wasm 也沒想像中那麼厲害嘛

好啦認真說,我猜可能是因為 WebAssembly 的編譯、執行技術才剛發展不久,還有不少待改進的地方;而 JS 因為歷史悠久、瀏覽器早已對他做了各種最佳化,所以才會得到這樣的結果,相信以後 WebAssembly 還會越來越快

DOM Manipulation

除了把 Go 寫的 function 給 JS 用之外,其實 Go 也能透過 syscall/js 提供的 API 來取得 JS 全域變數、操作 DOM

以下面實作的 hello 來說,我先用 document.createElement 建立一個 h1 Element,設定好內容 Hello World 後再把他塞到 document.body 裡面去

View on Github

這時在瀏覽器裡面執行 hello() 就會看到新增的 Hello World 了~

其實 WebAssembly 本身是不能操作 DOM 的,是因為 exec_wasm.js 把這些 DOM 操作封裝成 WASM function 了才能這樣用

雖然這例子證明了 Go 可以操作 DOM,代表你可以 只用 Go 就完成整個前端應用,但我自己是覺得滿彆扭的啦,好好的 document.body 非得寫成 js.Global().Get("document").Get("body") 不可。如果只是寫個小範例還可以,要我用這種方式做一個專案我應該會先氣死

反思 Atwood’s Law

不知道各位有沒有聽過著名的 Atwood 定律:任何可以用 JavaScript 寫出來的應用,最終都會用 JavaScript 來寫

Any application that can be written in JavaScript, will eventually be written in JavaScript.

但在 WebAssembly 的快速發展之下,其他語言也紛紛開始搶食瀏覽器這塊大餅,就連跟前端八竿子打不著的 Go 都能開始寫前端了,以後 JS 還能不能維持現在的霸主地位好像也很難說

總結

回歸到這篇的標題 — 「讓 Go 與 JS 在瀏覽器上共舞」,看完這幾個小例子想必大家都對 Go 跟 WebAssembly 有更多認識,應該也知道 Go 跟 JS 要怎麼在瀏覽器上合作了。怕大家忘記再重點整理一下今天的內容:

  • 主流瀏覽器都有支援 WebAssembly
  • Go 可以寫 function 給 JS 用
  • Go 編譯成的 WebAssembly 比 JS 稍快一點
  • Go 可以藉由 syscall/js 來操作 DOM,但很難用

範例都放在 Github 上,歡迎各位看倌自己 clone 下來跑跑看,對於文章或是範例扣有什麼問題的話也歡迎在下方留言跟我討論,謝謝大家

延伸閱讀

--

--

Starbugs Weekly 星巴哥技術專欄
Starbugs Weekly 星巴哥技術專欄

Published in Starbugs Weekly 星巴哥技術專欄

一群技術人想要寫出一些好文章所建立的技術專欄。每週二一篇原創文章、一封電子報,歡迎大家訂閱!主網站: https://weekly.starbugs.dev/。

Larry Lu
Larry Lu

Written by Larry Lu

我是 Larry 盧承億,傳說中的 0.1 倍工程師。我熱愛技術、喜歡與人分享,專長是 JS 跟 Go,平常會寫寫技術文章還有參加各種技術活動,歡迎大家來找我聊聊~