Библиотека GopherJS в Golang

—

от автора

Привет, Хабр!

GopherJS позволяет переводить Go-код в JavaScript — он предоставляет полноценную совместимость с большинством пакетов стандартной библиотеки Go. Также Gopher поддерживает горутины и каналы!

В статье в общих деталях рассмотрим эту замечательную библиотеку.

Установим:

go get -u github.com/gopherjs/gopherjs

Основы работы

gopherjs build — с этой командой можно компилировать Go-код в JavaScript. Она аналогична команде go build, но вместо создания исполняемого файла Go она генерирует файл .js. Например:

package main  import "github.com/gopherjs/gopherjs/js"  func main() {     js.Global.Get("document").Call("write", "Привет, Хабр!") }

Для компиляции этого файла в JavaScript:

gopherjs build main.go

Это создаст файл main.js, который можно подключить к HTML-странице.

gopherjs install аналогична gopherjs build, но вместо сохранения файла JavaScript в текущем каталоге, она устанавливает его в $GOPATH/bin или $GOBIN.

syscall/js позволяет Go-коду взаимодействовать с JavaScript объектами и функциями в браузере.

Допустим, хочется изменить содержимое элемента на веб-странице. Сначала нужно получить доступ к этому элементу:

package main  import (     "syscall/js" )  func main() {     // получение доступа к элементу DOM     document := js.Global().Get("document")     header := document.Call("getElementById", "header")      // изменение текста элемента     header.Set("innerHTML", "Новый заголовок") }

Код на Go скомпилируется с GopherJS и изменит содержимое элемента с идентификатором header на «Новый заголовок»:

Можно обрабатывать события DOM, используя функции обратного вызова:

package main  import (     "syscall/js" )  func main() {     document := js.Global().Get("document")     button := document.Call("getElementById", "myButton")          clickFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {         js.Global().Get("console").Call("log", "Кнопка нажата!")         return nil     })          button.Call("addEventListener", "click", clickFunc)     defer clickFunc.Release() // очистка памяти }

В примере обработчик события добавляется к кнопке, и при каждом нажатии кнопки в консоль будет выводиться сообщение «Кнопка нажата!».

Можно создать простую анимацию с помощью GopherJS, где к примеру, элемент будет двигаться по горизонтали вправо и влево на страничке:

package main  import (     "syscall/js"     "math"     "time" )  func main() {     window := js.Global()     document := window.Get("document")     body := document.Get("body")      // создаем элемент div и добавляем его в тело документа     div := document.Call("createElement", "div")     div.Set("innerHTML", "Анимированный блок")     div.Get("style").Set("position", "absolute")     div.Get("style").Set("top", "40px")     div.Get("style").Set("width", "100px")     div.Get("style").Set("height", "100px")     div.Get("style").Set("backgroundColor", "red")     body.Call("appendChild", div)      startTime := time.Now()      // функция для обновления позиции элемента     var updatePosition js.Func     updatePosition = js.FuncOf(func(this js.Value, args []js.Value) interface{} {         // вычисляем прошедшее время         elapsed := time.Since(startTime).Seconds()          // вычисляем новую позицию, используя функцию синуса для создания движения влево и вправо         left := 150 + 100*math.Sin(elapsed)          // обновляем позицию элемента         div.Get("style").Set("left", js.ValueOf(left).String()+"px")          // запрашиваем следующий кадр анимации         window.Call("requestAnimationFrame", updatePosition)         return nil     })      // запускаем анимацию     window.Call("requestAnimationFrame", updatePosition)     defer updatePosition.Release() // Освобождаем ресурсы }

Интеграция с React или Angular

Основная идея в такой интеграции будет состоять в том, чтобы написать код на Go, который GopherJS скомпилирует в JS, способный взаимодействовать с React или Angular. Рассмотрим, как это можно сделать на примере React.

Для этого будем юзать пакетmyitcv.io/react:

package main  import (     "github.com/gopherjs/gopherjs/js"     "myitcv.io/react" )  type HelloMessage struct {     react.ComponentDef }  func (h *HelloMessage) Render() *js.Object {     return react.JSX("div", nil, "Hello ", h.Props().Get("name")) }  func main() {     js.Global.Set("HelloMessage", react.CreateFactory(new(HelloMessage))) }

В HTML файле можно использовать этот компонент, как обычный React-компонент после того, как код скомпилирован GopherJS:

<!DOCTYPE html> <html> <head>     <meta charset="UTF-8">     <title>React with GopherJS</title> </head> <body>     <div id="app"></div>     <script src="your_compiled_gopherjs_script.js"></script>     <script>         ReactDOM.render(             React.createElement(HelloMessage, {name: "World"}),             document.getElementById('app')         );     </script> </body> </html>

Далее компилируем код с помощью GopherJS видим результат.

Насчет горутин и каналов

Горутины в GopherJS работают аналогично их работе в Go, с ними можно выполнять функции асинхронно:

package main  import ( "github.com/gopherjs/gopherjs/js" "time" )  func printNumbers() { for i := 1; i <= 5; i++ { js.Global.Get("console").Call("log", i) time.Sleep(time.Millisecond * 300) } }  func main() { go printNumbers()  for i := 6; i <= 10; i++ { js.Global.Get("console").Call("log", i) time.Sleep(time.Millisecond * 300) } }

Функция printNumbers запустится как горутина.

Каналы в GopherJS используются для синхронизации и обмена данными между горутинами:

package main  import ( "github.com/gopherjs/gopherjs/js" )  func sendToChannel(ch chan int) { for i := 1; i <= 5; i++ { ch <- i js.Global.Call("setTimeout", js.MakeFunc(func(this js.Value, args []js.Value) interface{} { return nil }), 300) } close(ch) }  func main() { ch := make(chan int)  go sendToChannel(ch)  for value := range ch { js.Global.Get("console").Call("log", value) } }

Функция sendToChannel отправляет числа от 1 до 5 в канал, а основная функция main читает эти значения. Закрытие канала после отправки всех значений гарантирует, что цикл в main завершится после получения всех данных.

Синхронизация горутин может быть также выполнена с использованием sync.WaitGroup, который позволяет дождаться завершения работы всех горутин:

package main  import ( "github.com/gopherjs/gopherjs/js" "sync" )  func performTask(id int, wg *sync.WaitGroup) { defer wg.Done() js.Global.Get("console").Call("log", "Task", id, "started") js.Global.Call("setTimeout", js.MakeFunc(func(this js.Value, args []js.Value) interface{} { js.Global.Get("console").Call("log", "Task", id, "completed") return nil }), 1000*id) }  func main() { var wg sync.WaitGroup wg.Add(3) for i := 1; i <= 3; i++ { go performTask(i, &wg) } wg.Wait() js.Global.Get("console").Call("log", "All tasks completed") }

Тесты

Можно использовать QUnit вместе с GopherJS QUnit bindings. После написания тестов можно автоматизировать их выполнение с помощью Agouti, она позволяет автоматом открывать браузер, переходить на нужную страницу с тестами и анализировать результаты.

Пример:

package main  import ( "github.com/sclevine/agouti" "log" )  func main() { driver := agouti.ChromeDriver() if err := driver.Start(); err != nil { log.Fatalf("Failed to start driver: %v", err) } page, err := driver.NewPage() if err != nil { log.Fatalf("Failed to open page: %v", err) }  if err := page.Navigate("http://localhost:10000"); err != nil { log.Fatalf("Failed to navigate: %v", err) }  // проверяем элементы на странице, используя Agouti методы passed, err := page.Find("#test-result").Text() if err != nil { log.Fatalf("Failed to find test results: %v", err) }  log.Println("Test passed:", passed) }

Для модульного тестирования GopherJS кода, можно юзать дефолтный пакет testing в Go. Пишем тесты так же, как и для обычных Go приложений, но запускаем их через GopherJS:

package main  import "testing"  func TestExample(t *testing.T) { expected := "Hello, GopherJS" result := MyFunction() if result != expected { t.Errorf("Test failed, expected '%s', got '%s'", expected, result) } }

Затем этот тест можно запустить с помощью команды gopherjs test.

Подробней с библиотекой можно ознакомиться здесь.


В рамках курса OTUS «Golang Developer. Professional» пройдут открытые уроки, присоединяйтесь:

  • 16 мая: Классические ошибки при собеседовании на позицию middle+ Go-разработчика. Записаться

  • 23 мая: Изучаем методы трассировки программ: метрики. Записаться


ссылка на оригинал статьи https://habr.com/ru/articles/811573/