WebAssembly и TypeScript: ускорение веба без боли

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

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

Начать курс

WebAssembly и TypeScript: когда JavaScript пасует

Вы когда-нибудь чувствовали, что ваш сайт тормозит, хотя написали всё на современном TypeScript? Обработка видео, 3D-рендеринг, сложные математические расчёты — браузерный JavaScript начинает задыхаться. Именно здесь на сцену выходит WebAssembly (или просто Wasm) — технология, которая позволяет запускать в браузере код, написанный на C++, Rust или Go, с почти нативной скоростью. Но как это связано с TypeScript? Спойлер: напрямую — никак, но вместе они образуют мощный тандем.

В этой статье мы не просто дадим сухие определения. Мы покажем, как TypeScript и WebAssembly могут работать в команде, где каждый отвечает за своё: TypeScript — за удобство и типизацию, а Wasm — за производительность. Вы узнаете, как написать простой Wasm-модуль и вызвать его из TypeScript, а главное — поймёте, стоит ли овчинка выделки.

Что такое WebAssembly простыми словами

WebAssembly — это бинарный формат инструкций, который выполняется в виртуальной машине браузера. Если JavaScript — это интерпретируемый язык, который парсится и выполняется "на лету", то Wasm — это уже скомпилированный байт-код. Он загружается быстрее, выполняется почти как нативный код и не даёт браузеру "задумываться".

Ключевые особенности WebAssembly:

  • Скорость — до 10-20 раз быстрее JavaScript в вычислительных задачах.
  • Безопасность — работает в изолированной среде (sandbox), не имеет прямого доступа к DOM.
  • Переносимость — один и тот же .wasm файл работает в Chrome, Firefox, Safari и Edge.
  • Не замена JS — а дополнение. DOM, события, асинхронность остаются за JavaScript/TypeScript.

Wasm не предназначен для написания логики интерфейса. Его стихия — тяжёлые вычисления: шифрование, обработка изображений, физические симуляции, игры.

А где тут TypeScript?

Сам TypeScript не компилируется в WebAssembly (хотя есть экспериментальные проекты, но они не для продакшена). Однако TypeScript становится идеальным "клеем" между Wasm-модулями и пользовательским интерфейсом. Представьте: у вас есть быстрый алгоритм на C++, собранный в Wasm. Вы пишете на TypeScript аккуратную обёртку с типами, и ваша IDE начинает подсказывать, какие параметры ожидает функция, какие типы она возвращает. Никакой магии — просто строгость типов и автодополнение.

Вот типичная архитектура:

  • Wasm-модуль — чёрный ящик, который делает сложную работу.
  • TypeScript-код — загружает модуль, вызывает его функции и обновляет UI.
  • Браузер — счастлив, потому что тяжёлая работа не блокирует главный поток.

Пишем пример: генерация простых чисел на WebAssembly и TypeScript

Давайте создадим реальный проект. Мы возьмём алгоритм поиска простых чисел (решето Эратосфена) на C++, скомпилируем его в Wasm, а потом вызовем из TypeScript. Не пугайтесь C++ — код будет простой, а для сборки используем Emscripten.

Шаг 1. Исходный код на C++

Создадим файл primes.cpp:

#include <emscripten.h>

extern "C" {
    EMSCRIPTEN_KEEPALIVE
    int count_primes(int limit) {
        if (limit < 2) return 0;
        bool* sieve = new bool[limit + 1];
        for (int i = 0; i <= limit; i++) sieve[i] = true;
        sieve[0] = sieve[1] = false;
        for (int i = 2; i * i <= limit; i++) {
            if (sieve[i]) {
                for (int j = i * i; j <= limit; j += i) {
                    sieve[j] = false;
                }
            }
        }
        int count = 0;
        for (int i = 0; i <= limit; i++) {
            if (sieve[i]) count++;
        }
        delete[] sieve;
        return count;
    }
}

EMSCRIPTEN_KEEPALIVE — макрос, который говорит компилятору не выкидывать эту функцию при оптимизации.

Шаг 2. Компиляция в Wasm

Установите Emscripten и выполните команду:

emcc primes.cpp -o primes.js -s WASM=1 -s EXPORTED_FUNCTIONS='["_count_primes"]'

На выходе получите два файла: primes.wasm и primes.js — вспомогательный JavaScript-загрузчик.

Шаг 3. TypeScript-обёртка

Теперь самое интересное. Создадим файл wasm-loader.ts:

// Типизируем глобальный объект, который создаёт Emscripten
declare var Module: any;

export async function loadWasmModule(): Promise<any> {
    // Используем динамический импорт для загрузки JS-загрузчика
    const module = await import('./primes.js');
    // Ждём инициализацию Wasm
    return new Promise((resolve) => {
        Module.onRuntimeInitialized = () => {
            resolve(Module);
        };
    });
}

// Функция-обёртка с типами
export function countPrimes(wasmModule: any, limit: number): number {
    if (limit < 2) return 0;
    return wasmModule._count_primes(limit);
}

Шаг 4. Использование в React-компоненте (на TypeScript)

import React, { useState, useEffect } from 'react';
import { loadWasmModule, countPrimes } from './wasm-loader';

const PrimeCounter: React.FC = () => {
    const [wasmReady, setWasmReady] = useState(false);
    const [wasmModule, setWasmModule] = useState<any>(null);
    const [limit, setLimit] = useState(100000);
    const [result, setResult] = useState<number | null>(null);

    useEffect(() => {
        loadWasmModule().then(mod => {
            setWasmModule(mod);
            setWasmReady(true);
        });
    }, []);

    const handleCalculate = () => {
        if (wasmModule) {
            const start = performance.now();
            const primes = countPrimes(wasmModule, limit);
            const end = performance.now();
            setResult(primes);
            console.log(`Время выполнения: ${(end - start).toFixed(2)} мс`);
        }
    };

    return (
        <div>
            <h3>Поиск простых чисел через WebAssembly</h3>
            <input
                type="number"
                value={limit}
                onChange={(e) => setLimit(Number(e.target.value))}
            />
            <button onClick={handleCalculate} disabled={!wasmReady}>
                {wasmReady ? 'Посчитать' : 'Загрузка Wasm...'}
            </button>
            {result !== null && <p>Найдено простых чисел: {result}</p>}
        </div>
    );
};

export default PrimeCounter;

Заметьте: TypeScript да

Блоги

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