Работа с WebAssembly и Go: создание быстрых веб-приложений

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

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

Начать курс

Введение в 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. Используйте:

  • Пакет

Блоги

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