Работа с WebAssembly и Go: от А до Я — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в 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 предоставляет мощный инструмент для создания высокопроизводительных веб-при

Блоги

Рекомендаций по книгам