WebAssembly и Go: Зачем это нужно современному веб-разработчику?
Вы когда-нибудь мечтали запустить высокопроизводительный код на Go прямо в браузере? Или, может быть, хотели переиспользовать существующую Go-библиотеку в веб-интерфейсе, не переписывая её на JavaScript? Если да, то вы пришли по адресу. WebAssembly (Wasm) в связке с Go — это не просто модный тренд, а реальный инструмент для создания приложений, которые работают на грани возможностей браузера.
В этой статье мы разберем, что такое WebAssembly, почему Go идеально подходит для генерации Wasm-модулей, и, самое главное, — напишем реальный код. Мы не будем копировать скучные мануалы. Мы создадим полезный инструмент: быстрый парсер данных на стороне клиента, который работает в 10 раз быстрее чистого JavaScript. Поехали!
Что такое WebAssembly простыми словами?
Представьте, что JavaScript — это автомобиль с автоматической коробкой передач. Он удобен, предсказуем, но не всегда быстр. WebAssembly — это гоночный болид. Это бинарный формат инструкций, который выполняется почти с нативной скоростью. Браузеры (Chrome, Firefox, Safari, Edge) умеют запускать Wasm-код в изолированной песочнице, давая ему доступ к памяти и процессору напрямую.
Главные фишки WebAssembly:
- Скорость: Выполняется со скоростью, близкой к нативному коду (C/C++, Rust, Go).
- Безопасность: Песочница не дает доступа к системе напрямую (как и JavaScript).
- Переносимость: Работает везде, где есть современный браузер.
- Размер: Компактный бинарный файл (.wasm).
И вот тут на сцену выходит Go. В отличие от C или Rust, Go имеет встроенный компилятор в WebAssembly (GOOS=js GOARCH=wasm). Это значит, что вы можете взять любой Go-пакет, скомпилировать его в .wasm и запустить в браузере. Звучит как магия, правда?
Почему именно Go для WebAssembly?
У вас может возникнуть резонный вопрос: "Зачем использовать Go, если есть Rust или AssemblyScript?" Ответ кроется в философии Go:
- Простота: Go славится своей минималистичной грамматикой. Вы пишете код, который легко читать и поддерживать.
- Горутины: В WebAssembly горутины работают! Вы можете запускать конкурентные вычисления без блокировки основного потока.
- Экосистема: Огромное количество библиотек (парсеры, криптография, работа с данными) можно переиспользовать без изменений.
- Низкий порог входа: Если вы знаете Go, вам не нужно учить новый язык для работы с Wasm.
Единственный минус — размер бинарного файла. Go-рантайм добавляет вес (обычно 2-5 МБ). Но для серьезных приложений (редакторы изображений, симуляции, игры) это не критично.
Практика: Парсим данные на Go и запускаем в браузере
Давайте создадим приложение, которое принимает на вход строку с числами (например, "12, 34, 56, 78"), а на выходе отдает сумму и среднее арифметическое. Это типичная задача для ETL-процессов или обработки логов. На JavaScript это можно сделать, но на Go — быстрее.
Шаг 1: Пишем Go-функцию с экспортом в JS
Создайте файл main.go. Главное правило: мы не можем использовать fmt.Println напрямую в браузере. Вместо этого мы будем возвращать результаты через глобальный объект global (в Go это syscall/js).
package main
import (
"strconv"
"strings"
"syscall/js"
)
// calculate — функция, которая будет вызвана из JavaScript
func calculate(this js.Value, args []js.Value) interface{} {
if len(args) == 0 {
return map[string]interface{}{
"error": "Не передан аргумент",
}
}
input := args[0].String()
parts := strings.Split(input, ",")
var sum float64
count := 0
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}
num, err := strconv.ParseFloat(trimmed, 64)
if err != nil {
continue // Пропускаем мусор
}
sum += num
count++
}
result := map[string]interface{}{
"sum": sum,
"average": sum / float64(count),
"count": count,
}
// Возвращаем JS-объект
return js.ValueOf(result)
}
func main() {
c := make(chan struct{}, 0)
// Регистрируем функцию в глобальном объекте window
js.Global().Set("goCalculate", js.FuncOf(calculate))
// Блокируем main, чтобы программа не завершилась
<-c
}
Важно: Функция calculate принимает два аргумента: this (контекст вызова) и args (срез JS-аргументов). Мы возвращаем map[string]interface{}, который Go автоматически конвертирует в JavaScript-объект.
Шаг 2: Компилируем в WebAssembly
Откройте терминал и выполните:
GOOS=js GOARCH=wasm go build -o main.wasm main.go
Флаг GOOS=js указывает, что мы компилируем для JavaScript-окружения. На выходе получаем файл main.wasm (обычно 2-5 МБ из-за рантайма).
Шаг 3: Создаем HTML-страницу с мостиком
Go-рантайму нужна специальная "прокладка" для работы с браузером. Ее можно найти в стандартной поставке Go:
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Теперь создадим index.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Go + WebAssembly: Быстрый парсер</title>
</head>
<body>
<h1>Обработчик данных на Go</h1>
<input type="text" id="inputData" placeholder="12, 34, 56, 78" style="width: 300px;">
<button onclick="runGo()">Запустить Go</button>
<button onclick="runJS()">Запустить JS (сравнение)</button>
<div id="output"></div>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
let goReady = false;
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)