Страница веб-приложения на Go: заменить Javascript на Go во фронтенде
Интересует один вопрос (возможно глупый).
Можно ли при создании фронта веб-приложения заменить JS в графических элементах(всплывающие окна, вложенные списки, и т.д.) на методы из Go?
P.S.: прошу палками не бить и помидорами не закидывать))
Ответы (1 шт):
Короткий ответ
Да, можно. Но геморройно-о-о-о
Развёрнутый
Современные браузеры умеют загружать модули WebAssembly (WASM). Go умеет компилировать в WASM, и есть обвязка для корректной загрузки WASM модуля в браузер.
Вот пример. Этот код на Go
- находит на странице элемент
#_targetи пишет в него приветствие - находит на странице кнопку
#_goBtnи прикрепляет к ней обработчик кликов - Go-шную функциюonClick - функция
onClickпишет в#_targetсколько раз на неё нажали.
package main
import (
"fmt"
"log"
"syscall/js"
)
func onClick(this js.Value, args []js.Value) any {
count++
target := js.Global().Get("document").Call("getElementById", "_target")
if target.IsNull() {
log.Panic("No element with id _target")
}
html := fmt.Sprintf("<p>Button clicked %d times</p>", count)
target.Set("innerHTML", html)
return js.ValueOf(true)
}
var done = make(chan struct{})
var count = 0
func main() {
defer func() { log.Println("WASM execution finished") }()
log.Println("WASM execution started")
target := js.Global().Get("document").Call("getElementById", "_target")
if target.IsNull() {
log.Panic("No element with id _target")
}
html := "<p>Hello from Go!</p>"
target.Set("innerHTML", html)
btn := js.Global().Get("document").Call("getElementById", "_goBtn")
if btn.IsNull() {
log.Panic("No element with id _goBtn")
}
btn.Set("onclick", js.FuncOf(onClick))
btn.Set("disabled", false)
// Wait until `done` is closed
<-done
}
На что нужно обратить внимание.
если функция
mainзавершается,onClickперестаёт работать. В консоль выводится сообщение об ошибкеGo application finished. Поэтому функцияmainдолжна работать вечно. Этого можно достичь многими разными способами, в примереmainждёт сигнал из канала. Пока в каналdoneне будет что-то записано, или канал не будет закрыт,mainбудет висеть в ожидании.В HTML нельзя написать
<button onclick=wasm.onClick>Click me!</button>. WASM модуль, собранный в Go, не экспортирует функции. Поэтому обработчики необходимо назначать в Go-шной функцииmain.Насколько я знаю, для Go пока нет аналога jQuery, поэтому все операции можно выполнять только через DOM API, во всеми этими
getElementById,appendи ручным перебором элементов в списках.
Как компилировать:
GOOS=js GOARCH=wasm go build -o example.wasm .
Пример HTML страницы, которая использует example.wasm:
<!doctype html>
<!--
Основано на wasm_exe.html, созданном The Go Authors.
Файл wasm_exec.html лежит у вас в <GOROOT>/misc/wasm/,
где <GOROOT> - каталог, в который установлен дистрибутив Go,
печатается командой `go env GOROOT`
-->
<html>
<head>
<meta charset="utf-8">
<title>Пример Go WASM</title>
</head>
<body>
<!-- Скрипт wasm_exec.js лежит в <GOROOT>/misc/wasm/ -->
<script src="wasm_exec.js"></script>
<!-- поменяйте путь к загружаемому модулю wasm в вызове WebAssembly.instantiateStreaming -->
<script>
if (!WebAssembly.instantiateStreaming) {
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("example.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
}).catch((err) => {
console.error(err);
});
async function run() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
// автоматический запуск `func main()` из wasm
window.onload = run
</script>
<div id="_target"></div>
<button id="_goBtn" disabled>Click Me!</button>
</body>
</html>
Пример с Makefile для сборки и запуска: https://github.com/pakuula/StackOverflow/tree/main/go/1551838
Далее можете погуглить go wasm web
PS. Работало в go1.19 и go1.21, не работало в go1.20