В этом посте сравнивается GopherJS с Go 1.11 и его новым экспериментальным портом WebAssembly, предлагается, как библиотеки привязки веб-API могут развиваться для поддержки обоих компиляторов, и предоставляется контекст истории и будущих возможностей Go в браузере.

Предпосылки и будущее

Go 1.11 и GopherJS 1.11–1 знаменуют собой важную и захватывающую веху на пути Go в браузере. В Go 1.11 добавлен экспериментальный порт для WebAssembly, что дает программистам Go второй выбор инструмента для компиляции кода Go в формат, поддерживаемый современными веб-браузерами. Его предоставил проекту Go Ричард Мусиол, создатель самого GopherJS.

Ричард начал проект GopherJS в августе 2013 года с целью дать программистам возможность писать фронтенд-код на Go. В то время основные браузеры предоставляли только один вариант исполняемого формата: язык JavaScript. У GopherJS не было выбора, кроме как использовать его в качестве цели компиляции.

GopherJS изначально был очень простым и поддерживал несколько выражений Go, но затем превратился в продвинутый компилятор Go, который поддерживал почти все, что есть в спецификации языка Go, включая горутины, каналы и оператор select.

WebAssembly - это современный двоичный формат, разработанный специально как цель компиляции для языков высокого уровня. Он все еще находится на ранних стадиях разработки с многими запланированными будущими улучшениями после MVP, и он имеет потенциал для достижения производительности и эффективности размера двоичного файла, превосходящей возможности компиляции в JavaScript.

Наличие двух рабочих компиляторов Go, которые могут работать с браузерами, полезно для здоровья экосистемы Go. Это похоже на gc и gccgo, два внутренних компилятора Go. Это создает новые возможности и помогает обнаруживать проблемы или обходить их. В настоящее время будут некоторые программы и варианты использования, которые лучше работают при компиляции с Go 1.11 в WebAssembly, а другие, где GopherJS работает лучше.

Пакеты привязки API браузера

Авторы пакетов, создавшие привязки для различных веб-API, ранее ориентировались только на компилятор GopherJS и github.com/gopherjs/gopherjs/js пакет для вызова JavaScript. В Go 1.11 есть новый экспериментальный syscall/js пакет в стандартной библиотеке для той же цели, который предлагает похожий, но не идентичный API.

Авторы существующих привязок API браузера могут добавить поддержку WebAssembly. Это очень полезно, поскольку позволяет пользователям писать программы Go, которые могут быть скомпилированы как с GopherJS в JavaScript, так и с Go 1.11 в WebAssembly. Это можно сделать несколькими способами, описанными ниже.

Рассмотрим следующий пример привязки Go для небольшого подмножества DOM Web API. Он начинается с таргетинга только на GopherJS, например:

// +build js
// Package window is a binding for a tiny subset of the DOM Web API,
// concerned with the Window interface as documented at
// https://developer.mozilla.org/en-US/docs/Web/API/Window.
package window
import "github.com/gopherjs/gopherjs/js"
// Alert displays an alert dialog with the specified message
// and an OK button. See its reference at
// https://developer.mozilla.org/en-US/docs/Web/API/Window/alert.
func Alert(message string) {
    js.Global.Call("alert", message)
}

Мы будем использовать эту простую библиотеку, чтобы продемонстрировать различные подходы к добавлению поддержки WebAssembly.

Использование тегов сборки

Одна из пословиц Иди гласит:

Системный вызов всегда должен быть защищен тегами сборки.

Пакеты github.com/gopherjs/gopherjs/js и syscall/js не исключение. Мы можем использовать теги сборки (также известные как ограничения сборки) для защиты их использования.

Раздел архитектура в README GopherJS отмечает:

Значение GOARCH для GopherJS составляет js. Вы можете использовать его как ограничение сборки: // +build js.

Go 1.11 использует значения GOOS=js и GOARCH=wasm для WebAssembly. Это означает, что // +build js ограничение сборки будет соответствовать обоим, и нам нужно использовать более конкретное // +build js,!wasm ограничение сборки, чтобы отличать код для GopherJS от кода для WebAssembly. Это выглядит так:

// +build js,!wasm
// Package window ...
package window
import "github.com/gopherjs/gopherjs/js"
// Alert displays ...
func Alert(message string) {
    js.Global.Call("alert", message)
}

Приведенный выше код будет использоваться при компиляции пакета window с GopherJS. Для WebAssembly добавляется еще один файл .go с // +build js,wasm ограничением сборки:

// +build js,wasm
// Package window ...
package window
import "syscall/js"
// Alert displays ...
func Alert(message string) {
    js.Global().Call("alert", message)
}

Обратите внимание, что он имеет другое ограничение сборки, импортирует syscall/js и использует немного другой API.

Этот подход требует от авторов библиотеки большей работы по обслуживанию, но обеспечивает максимальную гибкость и наименьшие накладные расходы. Это также предотвращает утечку внутренней js информации оболочкой в ​​свой общедоступный API.

Использование обертки gopherwasm

Другой подход - использовать оболочку github.com/gopherjs/gopherwasm/js. Эта библиотека абстрагируется от github.com/gopherjs/gopherjs/js и syscall/js API и представляет syscall/js-подобный API, который поддерживается как GopherJS, так и Go 1.11 WebAssembly.

Чтобы добавить поддержку WebAssembly в наш исходный пример привязки GopherJS, нам нужно только изменить единственный файл .go, чтобы импортировать gopherwasm и использовать его API:

// +build js
// Package window ...
package window
import "github.com/gopherjs/gopherwasm/js"
// Alert displays ...
func Alert(message string) {
    js.Global().Call("alert", message)
}

Этот подход предлагает преимущество, заключающееся в том, что авторам библиотеки приходится поддерживать только один файл .go. Однако он добавляет некоторую косвенность и не позволяет по-прежнему полагаться на особенности github.com/gopherjs/gopherjs/js API, которые не поддерживаются gopherwasm (например, js теги полей структуры и то, что описано здесь).

***

Это два наиболее распространенных подхода, применяемых на практике. Вы можете найти несколько реальных примеров добавления поддержки WebAssembly по адресу:

Могут быть рассмотрены и другие подходы, такие как создание отдельных пакетов для GopherJS и WebAssembly и многое другое. Вам нужно будет найти то, что лучше всего подходит для ваших нужд.

В общем, добавление поддержки WebAssembly более целесообразно для высокоуровневых привязок GopherJS, которые предоставляют идиоматический API Go и не раскрывают *js.Object детали в общедоступном API. Это хорошая практика, когда вы думаете о js пакетах как об эквиваленте других syscall пакетов: лучше избегать утечки таких низкоуровневых деталей в общедоступные API, с которыми пользователи могут иметь дело.

Заключение

Поддержка WebAssembly в Go 1.11 открывает доступ к Go в браузере более широкой аудитории и способствует развитию технологий в этом захватывающем новом пространстве.

Это прекрасное время для создания новых вещей, экспериментов, выяснения того, что возможно и что хорошо работает, чтобы улучшить состояние Go в браузере. Делитесь тем, что вы создаете, с другими сусликами. Удачи и приятного времяпровождения!

¹ В то время asm.js только что был анонсирован и начал появляться в некоторых браузерах. Было слишком рано начинать таргетинг на него сразу.