Введение в WebAssembly и Go
WebAssembly (Wasm) — это низкоуровневый бинарный формат инструкций, который выполняется в браузере с почти нативной скоростью. В сочетании с языком Go (Golang) вы получаете мощный инструмент для создания высокопроизводительных веб-приложений. Go компилируется в WebAssembly, что позволяет переносить сложные вычисления, алгоритмы и даже игры прямо в браузер, не жертвуя производительностью.
В этой статье мы разберём, как настроить среду, написать простой модуль на Go, скомпилировать его в Wasm и интегрировать с JavaScript. Вы узнаете о ключевых пакетах, ограничениях и лучших практиках.
Настройка окружения для компиляции Go в Wasm
Установка Go и WebAssembly-зависимостей
Прежде всего, убедитесь, что у вас установлен Go версии 1.11 или новее (рекомендуется 1.21+). WebAssembly поддерживается из коробки. Для компиляции используется переменная окружения GOOS=wasm GOARCH=wasm.
Также понадобится файл поддержки wasm_exec.js, который идёт в комплекте с Go. Скопируйте его в корень проекта:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Структура проекта
Создадим простую структуру:
my-wasm-app/
├── main.go
├── wasm_exec.js
├── index.html
└── main.wasm (будет создан после компиляции)
Написание модуля на Go для WebAssembly
Простой пример: функция сложения
Создайте файл main.go со следующим содержимым:
package main
import (
"syscall/js"
)
func add(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return 0
}
a := args[0].Int()
b := args[1].Int()
return a + b
}
func main() {
c := make(chan struct{}, 0)
js.Global().Set("goAdd", js.FuncOf(add))
<-c
}
Здесь мы используем пакет syscall/js для взаимодействия с JavaScript. Функция add принимает два аргумента и возвращает их сумму. Канал c блокирует завершение программы, чтобы модуль оставался активным.
Компиляция в Wasm
Выполните команду:
GOOS=wasm GOARCH=wasm go build -o main.wasm main.go
В результате получится файл main.wasm размером около 1–2 МБ (для простого примера). В продакшене используйте флаг -ldflags="-s -w" для уменьшения размера.
Интеграция с HTML и JavaScript
Создание HTML-страницы
Создайте файл index.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Go + WebAssembly</title>
<script src="wasm_exec.js"></script>
</head>
<body>
<h2>Калькулятор на Go в браузере</h2>
<input type="number" id="num1" value="5">
<input type="number" id="num2" value="10">
<button onclick="calculate()">Сложить</button>
<p>Результат: <span id="result">0</span></p>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => {
go.run(result.instance);
console.log("WebAssembly модуль загружен");
});
function calculate() {
const a = parseInt(document.getElementById('num1').value);
const b = parseInt(document.getElementById('num2').value);
const sum = goAdd(a, b);
document.getElementById('result').innerText = sum;
}
</script>
</body>
</html>
Обратите внимание: функция goAdd становится глобальной благодаря вызову js.Global().Set("goAdd", ...) в Go-коде.
Запуск локального сервера
Для корректной загрузки .wasm файла нужен HTTP-сервер с правильным MIME-типом. Используйте, например, Python:
python3 -m http.server 8080
Откройте браузер по адресу http://localhost:8080. Если всё сделано верно, при нажатии на кнопку вы увидите сумму чисел.
Продвинутые возможности и ограничения
Работа с памятью и строками
В отличие от JavaScript, Go-модули работают с собственной линейной памятью. Для передачи строк используйте вспомогательные функции:
func greet(this js.Value, args []js.Value) interface{} {
if len(args) < 1 {
return "Hello, World!"
}
name := args[0].String()
return "Привет, " + name + "!"
}
Пакет syscall/js автоматически преобразует строки туда и обратно, но для больших данных это может быть дорого. Рекомендуется минимизировать передачу строк через границу Wasm/JS.
Ограничения Go в WebAssembly
- Нет goroutine в браузере — все горутины выполняются в одном потоке, но планировщик Go работает корректно.
- Ограниченный доступ к DOM — напрямую манипулировать DOM из Go нельзя, только через JavaScript-прокси.
- Размер бинарника — даже простой модуль весит ~1-2 МБ из-за рантайма Go. Используйте
-ldflags="-s -w"и gzip-сжатие. - Нет доступа к системным вызовам — например, работа с файловой системой (кроме браузерного Storage).
Оптимизация производительности
Для тяжёлых вычислений (криптография, обработка изображений, симуляции) Go в Wasm показывает отличные результаты, часто превосходя чистый JavaScript. Используйте:
- Пакет