WebAssembly и TypeScript: практическое руководство для веб-разработчиков

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

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

Начать курс

Введение: почему WebAssembly и TypeScript — идеальная пара

Современная веб-разработка требует всё большей производительности, особенно в таких областях, как обработка изображений, 3D-графика, криптография и игры. WebAssembly (Wasm) стал революционным решением, позволяющим выполнять код со скоростью, близкой к нативной. Однако работа с Wasm напрямую на низком уровне может быть сложной. Здесь на помощь приходит TypeScript — строго типизированный язык, который отлично сочетается с WebAssembly.

В этой статье мы разберём, как эффективно работать с WebAssembly в TypeScript-проектах. Вы узнаете о компиляции C/C++ и Rust в Wasm, использовании AssemblyScript для написания Wasm-модулей на TypeScript-подобном синтаксисе, а также о лучших практиках интеграции.

Основы WebAssembly и его взаимодействие с TypeScript

Что такое WebAssembly?

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

Типичные сценарии использования Wasm в TypeScript

  • Вычислительно интенсивные задачи — обработка видео, аудио, изображений
  • Криптографические операции — хеширование, шифрование
  • Игровые движки — физика, рендеринг
  • Компиляторы и парсеры — синтаксический анализ больших файлов

Архитектура взаимодействия

TypeScript-код (скомпилированный в JavaScript) выступает в роли "клея" между браузером и Wasm-модулем. JS-код загружает модуль, вызывает его функции и обрабатывает результаты. Wasm-модуль, в свою очередь, может вызывать JS-функции через импорты.

Способы создания WebAssembly-модулей для TypeScript

1. Компиляция C/C++ через Emscripten

Emscripten — самый зрелый инструмент для компиляции C/C++ в Wasm. Он генерирует как сам бинарный файл (.wasm), так и JS-обёртку для удобного вызова из TypeScript.

Пример C-функции:

// math.c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

Компиляция:

emcc math.c -o math.js -s EXPORTED_FUNCTIONS='["_add"]' -s EXPORTED_RUNTIME_METHODS='["ccall"]'

Использование в TypeScript:

// math.ts
interface EmscriptenModule {
    ccall: (name: string, returnType: string, argTypes: string[], args: any[]) => any;
}

const Module = await import('./math.js') as unknown as EmscriptenModule;
const result = Module.ccall('add', 'number', ['number', 'number'], [5, 3]);
console.log(result); // 8

2. Использование Rust с wasm-pack

Rust — современный язык системного программирования, который отлично компилируется в Wasm. Инструмент wasm-pack автоматизирует создание npm-пакетов из Rust-кода.

Пример Rust-функции:

// lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

Подключение в TypeScript:

// app.ts
import init, { fibonacci } from 'my-wasm-package';

await init();
console.log(fibonacci(10)); // 55

3. AssemblyScript — TypeScript для WebAssembly

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

Пример AssemblyScript-модуля:

// assembly/index.ts
export function factorial(n: i32): i32 {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

Компиляция:

npx asc assembly/index.ts --outFile build/optimized.wasm --optimize

Использование в TypeScript:

// main.ts
import { instantiate } from './build/optimized.js';

const wasm = await instantiate();
console.log(wasm.exports.factorial(5)); // 120

Оптимизация производительности и управление памятью

Работа с линейной памятью

WebAssembly использует линейную память — непрерывный массив байтов. TypeScript может читать и записывать данные напрямую через объект WebAssembly.Memory и типизированные массивы.

// memory.ts
async function loadWasm() {
    const response = await fetch('module.wasm');
    const bytes = await response.arrayBuffer();
    const { instance } = await WebAssembly.instantiate(bytes, {
        env: {
            memory: new WebAssembly.Memory({ initial: 1 })
        }
    });
    
    // Чтение данных из памяти Wasm
    const memory = instance.exports.memory as WebAssembly.Memory;
    const buffer = new Uint8Array(memory.buffer);
    console.log('Первый байт:', buffer[0]);
}

loadWasm();

Передача сложных структур данных

Для передачи массивов и объектов между JS и Wasm используйте сериализацию через Uint8Array и копирование в общую память.

// data-transfer.ts
function copyToWasmMemory(data: Float32Array, wasmMemory: WebAssembly.Memory, offset: number): void {
    const view = new Float32Array(wasmMemory.buffer, offset, data.length);
    view.set(data);
}

function readFromWasmMemory(wasmMemory: WebAssembly.Memory, offset: number, length: number): Float32Array {
    return new Float32Array(wasmMemory.buffer, offset, length);
}

Избегайте частых вызовов между JS и Wasm

Каждый вызов функции из JS в Wasm имеет накладные расходы. Для максимальной производительности старайтесь передавать большие объёмы данных за один вызов, а не выполнять множество мелких операций.

Лучшие практики интеграции

Асинхронная загрузка модулей

Всегда используйте асинхронную за

Блоги

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