Artikel
asliPada bulan Februari 2017, seorang anggota tim Brad Fitzpatrick go
mengusulkan pembuatan dukungan WebAssembly dalam bahasa tersebut. Empat bulan kemudian, pada November 2017, penulis GopherJS Richard Muziol mulai menerapkan gagasan itu. Dan akhirnya, implementasi penuh ditemukan di master. Pengembang akan menerima wasme sekitar Agustus 2018, dengan versi
go 1.11 . Akibatnya, pustaka standar mengatasi hampir semua kesulitan teknis dengan mengimpor dan mengekspor fungsi yang Anda kenal jika Anda sudah mencoba mengkompilasi C dalam wasm. Kedengarannya menjanjikan. Mari kita lihat apa yang bisa dilakukan dengan versi pertama.
Semua contoh dalam artikel ini dapat diluncurkan dari wadah buruh pelabuhan yang ada
di repositori penulis :
docker container run -dP nlepage/golang_wasm:examples
Lalu pergi ke
localhost : 32XXX /, dan pergi dari satu tautan ke tautan lainnya.
Hai Wasm!
Penciptaan dasar "hello world" dan konsepnya sudah cukup
terdokumentasi dengan baik (bahkan
dalam bahasa Rusia ), jadi mari kita beralih ke hal-hal yang lebih halus.
Yang paling penting adalah versi Go yang baru dikompilasi yang mendukung wasm. Saya tidak akan
menjelaskan langkah demi langkah instalasi , hanya tahu bahwa apa yang dibutuhkan sudah di master.
Jika Anda tidak ingin khawatir tentang hal ini,
Dockerfile c go tersedia di
repositori golub-wasm di github , atau Anda dapat mengambil gambar dari
nlepage / golang_wasm lebih cepat.
Sekarang Anda dapat menulis
helloworld.go
tradisional dan kompilasi dengan perintah berikut:
GOOS=js GOARCH=wasm go build -o test.wasm helioworld.go
Variabel lingkungan GOOS dan GOARCH telah disetel di gambar
nlepage / golang_wasm , sehingga Anda dapat menggunakan file
Dockerfile
seperti ini untuk dikompilasi:
FROM nlepage/golang_wasm COPY helloworld.go /go/src/hello/ RUN go build -o test.wasm hello
Langkah terakhir adalah menggunakan file
wasm_exec.html
dan
wasm_exec.js
tersedia di repositori go di
misc/wasm
atau di docker image
nlepage / golang_wasm di
/usr/local/go/misc/wasm/
untuk menjalankan
test.wasm
di browser (wasm_exec.js mengharapkan file binary
test.wasm
, jadi kami menggunakan nama ini).
Anda hanya perlu memberikan 3 file statis menggunakan nginx, misalnya, lalu wasm_exec.html akan menampilkan tombol “run” (ini akan menyala hanya jika
test.wasm
dimuat dengan benar).
Patut dicatat bahwa
test.wasm
harus disajikan dengan
application/wasm
jenis MIME, jika tidak browser akan menolak untuk mengeksekusinya. (mis. nginx membutuhkan
file mime.types yang diperbarui ).
Anda dapat menggunakan gambar nginx dari
nlepage / golang_wasm , yang sudah menyertakan tipe MIME tetap,
wasm_exec.html
dan
wasm_exec.js
dalam kode> / usr / share / nginx / html / direktori.
Sekarang klik tombol "run", lalu buka konsol browser Anda dan Anda akan melihat ucapan console.log ("Hello Wasm!").
Contoh lengkap tersedia di
sini .
Panggil JS dari Go
Sekarang kita telah berhasil meluncurkan biner WebAssembly pertama yang dikompilasi dari Go, mari kita lihat lebih dekat fitur yang disediakan.
Paket syscall / js baru telah ditambahkan ke perpustakaan standar. Pertimbangkan file utama,
js.go
Tipe
js.Value
baru
js.Value
yang mewakili nilai JavaScript.
Ini menawarkan API sederhana untuk mengelola variabel JavaScript:
js.Value.Get()
dan js.Value.Set()
mengembalikan dan mengatur nilai bidang objek.js.Value.Index()
dan js.Value.SetIndex()
mengakses objek dengan membaca dan menulis indeks.js.Value.Call()
memanggil metode objek sebagai fungsi.js.Value.Invoke()
menyebut objek itu sendiri sebagai fungsi.js.Value.New()
memanggil operator baru dan menggunakan pengetahuannya sendiri sebagai konstruktor.- Beberapa metode lagi untuk mendapatkan nilai JavaScript dalam tipe Go yang sesuai, misalnya
js.Value.Int()
atau js.Value.Bool()
.
Dan metode menarik tambahan:
js.Undefined()
akan memberikan js.Value undefined
sesuai.js.Null()
akan memberikan js.Value
null
sesuai.js.Global()
akan mengembalikan js.Value
memberikan akses ke lingkup global.js.ValueOf()
menerima tipe Go primitif dan mengembalikan js.Value
benar
Alih-alih menampilkan pesan di os.StdOut, mari kita tampilkan di jendela notifikasi menggunakan
window.alert()
.
Karena kita berada di browser, ruang lingkup global adalah sebuah jendela, jadi pertama-tama Anda perlu mendapatkan lansiran () dari ruang lingkup global:
alert := js.Global().Get("alert")
Sekarang kita memiliki variabel
alert
, dalam bentuk
js.Value
, yang merupakan referensi ke
window.alert
JS, dan Anda dapat menggunakan fungsi untuk memanggil melalui
js.Value.Invoke()
:
alert.Invoke("Hello wasm!")
Seperti yang Anda lihat, tidak perlu memanggil js.ValueOf () sebelum meneruskan argumen ke Invoke, dibutuhkan
interface{}
sewenang-wenang dan meneruskan nilai melalui ValueOf itu sendiri.
Sekarang program baru kita akan terlihat seperti ini:
package main import ( "syscall/js" ) func main() { alert := js.Global().Get("alert") alert.Invoke("Hello Wasm!") }
Seperti pada contoh pertama, Anda hanya perlu membuat file bernama
test.wasm
, dan biarkan
wasm_exec.html
dan
wasm_exec.js
seperti semula.
Sekarang, ketika kita mengklik tombol "Jalankan", jendela peringatan muncul dengan pesan kami.
Contoh yang berfungsi ada di folder
examples/js-call
.
Panggil Pergi dari JS.
Memanggil JS dari Go cukup sederhana, mari kita lihat lebih dekat pada paket
syscall/js
, file kedua yang akan dilihat adalah
callback.go
.
js.Callback
tipe pembungkus untuk fungsi Go, untuk digunakan dalam JS.js.NewCallback()
fungsi yang mengambil fungsi (menerima sepotong js.Value
dan mengembalikan apa-apa), dan mengembalikan js.Callback
.- Beberapa mekanisme untuk mengelola panggilan balik aktif dan
js.Callback.Release()
, yang harus dipanggil untuk menghancurkan panggilan balik itu. js.NewEventCallback()
mirip dengan js.NewCallback()
, tetapi fungsi yang dibungkus hanya menerima 1 argumen - sebuah peristiwa.
Mari kita coba melakukan sesuatu yang sederhana: jalankan Go
fmt.Println()
dari sisi JS.
Kami akan membuat beberapa perubahan pada
wasm_exec.html
untuk bisa mendapatkan panggilan balik dari Go untuk memanggilnya.
async function run() { console.clear(); await go.run(inst); inst = await WebAssembly.instantiate(mod, go.ImportObject);
Ini meluncurkan binary wasme dan menunggu untuk menyelesaikannya, kemudian menginisialisasi ulang untuk menjalankan berikutnya.
Mari kita tambahkan fungsi baru yang akan menerima dan menyimpan panggilan balik Go dan mengubah status
Promise
setelah selesai:
let printMessage
Sekarang mari kita adaptasikan fungsi
run()
untuk menggunakan callback:
async function run() { console.clear()
Dan ini ada di pihak JS!
Sekarang, di bagian Go, Anda perlu membuat panggilan balik, mengirimkannya ke sisi JS dan menunggu fungsi yang diperlukan.
var done = make(chan struct{})
Maka mereka harus menulis fungsi
printMessage()
:
func printMessage(args []js.Value) { message := args[0].Strlng() fmt.Println(message) done <- struct{}{}
Argumen dilewatkan melalui slice
[]js.Value
, jadi Anda perlu memanggil
js.Value.String()
pada elemen slice pertama untuk mendapatkan pesan di baris Go.
Sekarang kita dapat membungkus fungsi ini dalam panggilan balik:
callback := js.NewCallback(printMessage) defer callback.Release()
Kemudian panggil fungsi JS
setPrintMessage()
, sama seperti memanggil
window.alert()
:
setPrintMessage := js.Global.Get("setPrintMessage") setPrintMessage.Invoke(callback)
Hal terakhir yang harus dilakukan adalah menunggu panggilan balik dipanggil di utama:
<-done
Bagian terakhir ini penting karena callback dijalankan dalam goroutine khusus, dan goroutine utama harus menunggu panggilan balik dipanggil, jika biner wasm akan dihentikan sebelum waktunya.
Program Go yang dihasilkan akan terlihat seperti ini:
package main import ( "fmt" "syscall/js" ) var done = make(chan struct{}) func main() { callback := js.NewCallback(prtntMessage) defer callback.Release() setPrintMessage := js.Global().Get("setPrintMessage") setPrIntMessage.Invoke(callback) <-done } func printMessage(args []js.Value) { message := args[0].Strlng() fmt.PrintIn(message) done <- struct{}{} }
Seperti pada contoh sebelumnya, buat file bernama
test.wasm
. Kita juga perlu mengganti
wasm_exec.html
dengan versi kita, dan
wasm_exec.js
dapat menggunakan kembali
wasm_exec.js
.
Sekarang, ketika Anda menekan tombol "run", seperti dalam contoh pertama kami, pesan dicetak di konsol browser, tetapi kali ini jauh lebih baik! (Dan lebih keras.)
Contoh yang berfungsi dalam tawaran file buruh pelabuhan tersedia di folder
examples/go-call
.
Kerja panjang
Memanggil Go dari JS sedikit lebih rumit daripada memanggil JS dari Go, terutama di sisi JS.
Hal ini terutama disebabkan oleh fakta bahwa Anda perlu menunggu sampai hasil panggilan balik Go diteruskan ke sisi JS.
Mari kita coba sesuatu yang lain: mengapa tidak mengatur binary wasm, yang tidak akan berakhir tepat setelah panggilan balik, tetapi akan terus bekerja dan menerima panggilan lain.
Kali ini, mari kita mulai dari sisi Go, dan seperti pada contoh kita sebelumnya, kita perlu membuat panggilan balik dan mengirimkannya ke sisi JS.
Tambahkan penghitung panggilan untuk melacak berapa kali fungsi telah dipanggil.
Fungsi
printMessage()
baru kami
printMessage()
akan mencetak pesan yang diterima dan nilai penghitung:
var no int func printMessage(args []js.Value) { message := args[0].String() no++ fmt.Printf("Message no %d: %s\n", no, message) }
Membuat panggilan balik dan mengirimnya ke sisi JS sama seperti pada contoh sebelumnya:
callback := js.NewCallback(printMessage) defer callback.Release() setPrintMessage := js.Global().Get("setPrintMessage") setPrIntMessage.Invoke(callback)
Tapi kali ini kami belum memiliki saluran untuk memberi tahu kami tentang penghentian goroutine utama. Salah satu caranya adalah dengan mengunci goroutin utama secara permanen dengan
select{}
kosong
select{}
:
select{}
Ini tidak memuaskan, binary wasm kami hanya akan menggantung di memori sampai tab browser ditutup.
Anda dapat mendengarkan acara
beforeunload
pada halaman, Anda akan membutuhkan panggilan balik kedua untuk menerima acara dan memberitahukan goroutine utama melalui saluran:
var beforeUnloadCh = make(chan struct{})
Kali ini, fungsi
beforeUnload()
baru hanya akan menerima acara tersebut, sebagai argumen tunggal
js.Value
:
func beforeUnload(event js.Value) { beforeUnloadCh <- struct{}{} }
Kemudian bungkus dengan callback menggunakan
js.NewEventCallback()
dan daftarkan di sisi JS:
beforeUnloadCb := js.NewEventCallback(0, beforeUnload) defer beforeUnloadCb.Release() addEventLtstener := js.Global().Get("addEventListener") addEventListener.Invoke("beforeunload", beforeUnloadCb)
Akhirnya, ganti
select
pemblokiran kosong dengan membaca dari saluran
beforeUnloadCh
:
<-beforeUnloadCh fmt.Prtntln("Bye Wasm!")
Program akhir terlihat seperti ini:
package main import ( "fmt" "syscall/js" ) var ( no int beforeUnloadCh = make(chan struct{}) ) func main() { callback := js.NewCallback(printMessage) defer callback.Release() setPrintMessage := js.Global().Get("setPrintMessage") setPrIntMessage.Invoke(callback) beforeUnloadCb := js.NewEventCallback(0, beforeUnload) defer beforeUnloadCb.Release() addEventLtstener := js.Global().Get("addEventListener") addEventListener.Invoke("beforeunload", beforeUnloadCb) <-beforeUnloadCh fmt.Prtntln("Bye Wasm!") } func printMessage(args []js.Value) { message := args[0].String() no++ fmt.Prtntf("Message no %d: %s\n", no, message) } func beforeUnload(event js.Value) { beforeUnloadCh <- struct{}{} }
Sebelumnya, di sisi JS, unduhan biner wasme tampak seperti ini:
const go = new Go() let mod, inst WebAssembly .instantiateStreaming(fetch("test.wasm"), go.importObject) .then((result) => { mod = result.module inst = result.Instance document.getElementById("runButton").disabled = false })
Mari kita adaptasi untuk menjalankan biner segera setelah memuat:
(async function() { const go = new Go() const { instance } = await WebAssembly.instantiateStreaming( fetch("test.wasm"), go.importObject ) go.run(instance) })()
Dan ganti tombol "Jalankan" dengan bidang pesan dan tombol untuk memanggil
printMessage()
:
<input id="messageInput" type="text" value="Hello Wasm!"> <button onClick="printMessage(document.querySelector('#messagelnput').value);" id="prtntMessageButton" disabled> Print message </button>
Akhirnya, fungsi
setPrintMessage()
, yang menerima dan menyimpan panggilan balik, harus lebih sederhana:
let printMessage; function setPrintMessage(callback) { printMessage = callback; document.querySelector('#printMessageButton').disabled = false; }
Sekarang, ketika kita mengklik tombol "Cetak pesan", Anda akan melihat pesan pilihan kami dan penghitung panggilan dicetak di konsol browser.
Jika kita mencentang kotak Preserve log dari konsol browser dan menyegarkan halaman, kita akan melihat pesan "Bye Wasm!".
Sumber tersedia di folder
examples/long-running
di github.
Lalu?
Seperti yang Anda lihat, API
syscall/js
dipelajari melakukan tugasnya dan memungkinkan Anda menulis hal-hal kompleks dengan sedikit kode. Anda dapat menulis kepada
penulis jika Anda tahu metode yang lebih sederhana.
Saat ini tidak mungkin untuk mengembalikan nilai ke JS langsung dari panggilan balik Go.
Ingatlah bahwa semua panggilan balik dilakukan di goroutin yang sama, jadi jika Anda melakukan beberapa operasi pemblokiran dalam panggilan balik tersebut, jangan lupa untuk membuat goroutin baru, jika tidak, Anda akan memblokir eksekusi semua panggilan balik lainnya.
Semua fitur bahasa dasar sudah tersedia, termasuk konkurensi. Untuk saat ini, semua goroutin akan bekerja dalam satu utas, tetapi ini
akan berubah di masa mendatang .
Dalam contoh kami, kami hanya menggunakan paket fmt dari perpustakaan standar, tetapi semuanya tersedia yang tidak mencoba untuk melarikan diri dari kotak pasir.
Sistem file tampaknya didukung melalui Node.js.
Akhirnya, bagaimana dengan kinerja? Akan menarik untuk menjalankan beberapa tes untuk melihat bagaimana Go wasm membandingkan dengan kode JS murni yang setara. Seseorang
hajimehoshi membuat pengukuran tentang bagaimana lingkungan yang berbeda bekerja dengan bilangan bulat, tetapi tekniknya tidak terlalu jelas.
Jangan lupa bahwa Go 1.11 belum dirilis secara resmi. Menurut saya itu sangat bagus untuk teknologi eksperimental. Mereka yang tertarik dengan tes kinerja
dapat menyiksa browser mereka .
Ceruk utama, seperti yang dicatat penulis, adalah transfer dari server ke klien dari kode go yang ada. Tetapi dengan standar baru, Anda dapat membuat
aplikasi sepenuhnya offline , dan kode wasm disimpan dalam bentuk yang dikompilasi. Anda dapat mentransfer banyak utilitas ke web, setuju, dengan nyaman?