Введение в WebAssembly и Go
WebAssembly (WASM) — это низкоуровневый бинарный формат, который позволяет запускать код, написанный на языках, отличных от JavaScript, непосредственно в браузере. Go (Golang) с его мощной стандартной библиотекой и отличной поддержкой параллелизма стал одним из лучших кандидатов для компиляции в WASM. В этой статье мы разберем процесс работы с WebAssembly на Go от установки инструментов до развертывания готового решения.
Современные веб-приложения требуют высокой производительности, и WebAssembly предоставляет возможность запускать код, написанный на Go, с почти нативной скоростью. Это открывает двери для переноса тяжелых вычислений, игровых движков и систем обработки данных прямо в браузер.
Установка и настройка окружения
Прежде чем начать, убедитесь, что у вас установлена последняя версия Go (1.21+). Для работы с WebAssembly потребуется компилятор tinygo (опционально, для уменьшения размера бинарников) или стандартный go build.
Базовые требования
- Go 1.21 или новее
- Браузер с поддержкой WebAssembly (Chrome, Firefox, Safari, Edge)
- Базовое понимание JavaScript
Настройка переменных окружения
# Устанавливаем GOOS и GOARCH для кросс-компиляции
export GOOS=js
export GOARCH=wasm
# Проверяем, что все настроено правильно
go env GOOS GOARCH
# Должны увидеть: js wasm
Также необходимо скопировать файл поддержки wasm_exec.js из стандартной библиотеки Go:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./
Написание и компиляция первого WASM-модуля
Создадим простой модуль, который будет экспортировать функцию для сложения двух чисел и выводить сообщение в консоль браузера.
Код на Go
package main
import "syscall/js"
func add(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return "Error: need two arguments"
}
a := args[0].Int()
b := args[1].Int()
return a + b
}
func main() {
// Регистрируем функцию в глобальном объекте
js.Global().Set("goAdd", js.FuncOf(add))
// Блокируем горутину, чтобы WASM не завершился
select {}
}
Компиляция в WASM
go build -o main.wasm main.go
После компиляции у вас появится файл main.wasm размером около 2-3 МБ (с стандартным компилятором). Для продакшена рекомендуется использовать TinyGo, который уменьшает размер до 10-50 КБ.
Интеграция с JavaScript и HTML
Теперь создадим HTML-страницу, которая загружает и использует наш WASM-модуль.
HTML-шаблон
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go WebAssembly Demo</title>
</head>
<body>
<h1>Go + WebAssembly</h1>
<input type="number" id="num1" value="10">
<input type="number" id="num2" value="20">
<button onclick="calculate()">Сложить</button>
<p>Результат: <span id="result">0</span></p>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(
fetch("main.wasm"),
go.importObject
).then((result) => {
go.run(result.instance);
console.log("WASM загружен!");
});
function calculate() {
const a = parseInt(document.getElementById('num1').value);
const b = parseInt(document.getElementById('num2').value);
const result = goAdd(a, b);
document.getElementById('result').textContent = result;
}
</script>
</body>
</html>
Работа с памятью и сложными структурами
Для передачи строк, массивов или структур данных между Go и JavaScript используйте syscall/js. Пример работы с массивом:
func processArray(this js.Value, args []js.Value) interface{} {
jsArray := args[0]
length := jsArray.Length()
sum := 0
for i := 0; i < length; i++ {
sum += jsArray.Index(i).Int()
}
return sum
}
func main() {
js.Global().Set("goSumArray", js.FuncOf(processArray))
select {}
}
Оптимизация и продвинутые техники
При работе с WebAssembly на Go важно учитывать несколько аспектов для достижения максимальной производительности.
Использование TinyGo для уменьшения размера
# Установка TinyGo
brew install tinygo # macOS
# или скачайте с официального сайта
# Компиляция с TinyGo
tinygo build -o main.wasm -target=wasm ./main.go
Избегайте частых вызовов между Go и JS
Каждый вызов из JS в Go (и наоборот) имеет накладные расходы. Группируйте операции:
- Передавайте большие объемы данных одним массивом, а не по одному элементу
- Используйте буферизацию для частых вызовов
- Рассмотрите возможность выполнения всей логики на стороне Go
Работа с горутинами в WASM
Go поддерживает горутины в WebAssembly, но с ограничениями. Основной поток блокируется через select {}, а горутины могут выполняться параллельно, но не истинно конкурентно (из-за однопоточности WASM).
func heavyComputation(this js.Value, args []js.Value) interface{} {
done := make(chan int)
go func() {
// Тяжелые вычисления
result := 0
for i := 0; i < 1000000; i++ {
result += i
}
done <- result
}()
return <-done
}
Заключение
WebAssembly в сочетании с Go предоставляет мощный инструмент для создания высокопроизводительных веб-при