Всем привет! 24 августа 2018 вышла версия Go 1.11 с экспериментальной поддержкой WebAssembly (Wasm). Технология интересная и у меня сразу возникло желание поэкспериментировать. Написать "Hello World" скучно (и он кстати есть в документации), тем более тренд прошедшего лета статьи из серии "Как сделать поиск пользователей по GitHub <вставить свой любимый JS-фреймворк>" Итак, данная статья является продолжением цикла. Вот предыдущие главы:
Как сделать поиск пользователей по GitHub используя React + RxJS 6 + Recompose
Как сделать поиск пользователей по GitHub без React + RxJS 6 + Recompose
Как сделать поиск пользователей по GitHub, используя Vue
Как сделать поиск пользователей по Github используя Angular
Как сделать поиск пользователей по Github используя VanillaJS  Задача: необходимо реализовать поиск пользователей по GitHub с вводом логина и динамическим формированием HTML. Как показывают предыдущие публикации делается это элементарно, но в нашем случае мы не будем использовать JavaScript. Должно получиться вот это:  Внимание! Данная статья не призывает бросать JS и переписывать все web-приложения на Go, а только показывает возможности работы с JS API из WASM. Прежде всего стоит обновиться до последней версии Go (на момент написания статьи 1.11)
Копируем два файла с поддержкой HTML & JS в свой проект Hello World и базовую настройку HTTP сервера я пропущу, подробности можно прочитать на Go Wiki Осуществляется через пакет syscall/js
В статье будут использоваться следующие функции и методы для управления JavaScript: Все методы имеют аналоги в JS, и станут понятнее по ходу статьи. Для начала создадим поле, в которое пользователь будет вводить логин для поиска, это будет классический тег input с placeholder.
В дальнейшем нам потребуется создание еще одного тега, поэтому мы сразу напишем конструктор HTML-элементов. Структура HTML-элемента содержит название тега tag (например input, div и т.д) и дополнительные параметры params (например: placeholder, id и т.д) Сам конструктор выглядит так: e := js.Global().Get("document").Call("createElement", el.tag) это аналог var e = document.createElement(tag) в JS
e.Set(attr, value) аналог e.setAttribute(attr, value)
Данный метод только создает элементы, но не добавляет их на страницу. Чтобы добавить элемент на страницу необходимо определить место его вставки. В нашем случае это div с id="box" (в wasm_exec.html строка <div id="box"></div>) В box.el храниться ссылка на основной контейнер нашего приложения.
js.Global().Get("document").Call("getElementById", "box") в JS это document.getElementById('box') Сам метод создания input-элемента: Данный метод возвращает ссылку на созданный элемент Полученные результаты необходимо выводить на страницу, давайте добавим div c id="search_result" по аналогии с input Создается контейнер и возвращается ссылка на элемент Настало время определить структуру для всего нашего Web-приложения inputBox и resultBox — ссылки на ранее созданные элементы <input placeholder="GitHub username"> и <div id="search_result"></div> соответственно Отлично! Мы добавили два элемента на страницу. Теперь пользователь может вводить данные в input и смотреть на пустой div, уже неплохо, но пока наше приложение не интерактивно. Давайте исправим это. Нам необходимо отслеживать когда пользователь вводит логин в input и получать эти данные, для этого подписываемся на событие keyup, сделать это очень просто e.Get("target").Get("value") — получение значения input, аналог event.target.value в JS, println(user) обычный console.log(user) Таким образом мы консолим все действия пользователя по вводу логина в input.
Теперь у нас есть данные, с которыми мы можем формировать запрос к GitHub API Запрашивать мы будем информацию по зарегистрированным пользователям: get-запрос на https://api.github.com/users/:username
Но сперва определим структуру ответа GitHub API Response — содержит ответ сервера, для нашего приложения нужен только статус Status string — он потребуется для вывода на странице ошибки.
Result — тело ответа в сокращенном виде, только необходимые поля. Сами запросы формируются через стандартный пакет net/http Теперь, когда у нас есть метод получение информации о пользователе с GitHub API давайте модифицируем userHandler() и попутно расширим структуру Web-приложения App добавив туда канал chan Search для передачи данных из горутины getUserCard() Прекрасно! Мы получили информацию о пользователе и у нас есть контейнер для вставки. Теперь нам нужен HTML-шаблон и разумеется какой-нибудь простой шаблонизатор. В нашем приложении будем использовать mustache — это популярный шаблонизатор с простой логикой.
Установка: go get github.com/cbroglie/mustache Сам HTML-шаблон user.mustache находится в директории tmpl нашего приложения и выглядит следующим образом: Все стили прописаны в web/style.css Следующий шаг — получить шаблон в виде строки и прокинуть его в наше приложение. Для этого опять расширяем структуру App добавив туда нужные поля. userTMPL — шаблон вывода информации о пользователе user.mustache. errorTMPL — шаблон обработки ошибок error.mustache Для получения шаблона из приложения используем обычный Get-запрос Шаблон есть, данные есть теперь попробуем отрендерить HTML-представление Это горутина, которая ожидает данные из канала <-a.search и рендерит HTML. Условно считаем, что если в данных из GitHub API есть ID-пользователя search.Result.ID то результат корректный, в противном случае возвращаем страницу ошибки.
data, _ := mustache.Render(tmpl, search) — рендерит готовый HTML, а a.resultBox.Set("innerHTML", data) выводит HTML на страницу Работает! Но есть одна проблема — если посмотреть в консоль мы увидим, что на каждое нажатие клавиши отправляется запрос к GitHub API, при таком раскладе мы быстро упремся в лимиты.  Решение — Debounce. Это функция, которая откладывает вызов другой функции на заданное время. То есть когда пользователь нажимает кнопку мы должны отложить запрос к GitHub API на X миллисекунд, при этом если срабатывает еще одно событие нажатие кнопки — запрос откладывается еще на X миллисекунд. Debounce в Go реализуется с помощью каналов. Рабочий вариант взял из статьи debounce function for golang Перепишем метод (a *App) userHandler() с учетом Debounce: Событие keyup срабатывает всегда, но запросы отправляются только через 1000ms после завершения последнего события. И напоследок немного улучшим наш UX, добавив индикатор загрузки "Loading..." и очистку контейнера в случае пустого input Конечный вариант метода (a *App) userHandler() выглядит так: Готово! Теперь у нас есть полноценный поиск пользователей по GitHub без единой строчки на JS. По моему это круто. Написать Web-приложение работающее с DOM на wasm возможно, но стоит ли это делать — вопрос. Во-первых, пока не понятно как тестировать код, во-вторых, в некоторых браузерах работает не стабильно (например в Хроме падало с ошибкой через раз, в FF с этим лучше), в-третьих вся работа с DOM осуществляется через JS API, что должно сказываться на производительности (правда замеры не делал, поэтому все субъективно) Кстати большинство примеров это работа с графикой в canvas и выволнение тяжелых вычислений, скорее всего wasm проектировался именно для этих задач. Хотя… время покажет. Исходники на github
Интерактивное демо на lovefrontend.ru