Введение в WebAssembly и TypeScript
WebAssembly (Wasm) — это низкоуровневый бинарный формат инструкций, который выполняется в браузере с почти нативной скоростью. В сочетании с TypeScript, статически типизированным надмножеством JavaScript, разработчики получают мощный инструмент для создания высокопроизводительных веб-приложений. В этой статье мы разберём полный цикл работы с WebAssembly и TypeScript: от теории до практических примеров.
WebAssembly позволяет запускать код, написанный на C, C++, Rust и других языках, в браузере. TypeScript, в свою очередь, добавляет строгую типизацию и улучшает читаемость кода. Вместе они открывают новые возможности для обработки видео, 3D-графики, криптографии и других ресурсоёмких задач.
Основные концепции WebAssembly
Что такое WebAssembly?
WebAssembly — это виртуальная машина с бинарным форматом инструкций. Код компилируется в файлы .wasm, которые затем загружаются и выполняются в браузере. Основные преимущества:
- Производительность: выполнение близко к нативной скорости
- Безопасность: изолированное выполнение в песочнице
- Переносимость: работает во всех современных браузерах
- Интеграция: легко вызывается из JavaScript/TypeScript
Форматы WebAssembly
Существует два основных формата:
- Двоичный (.wasm): компактный бинарный формат для выполнения
- Текстовый (.wat): человекочитаемое представление Wasm
Обычно разработчики работают с .wasm файлами, а текстовый формат используется для отладки.
Настройка окружения для работы с WebAssembly и TypeScript
Необходимые инструменты
Для начала работы потребуется:
- Node.js (версия 16+)
- TypeScript компилятор
- AssemblyScript (если пишем на TypeScript-подобном синтаксисе)
- Браузер с поддержкой WebAssembly
Установка AssemblyScript
AssemblyScript — это язык, который компилирует TypeScript-подобный код в WebAssembly. Установка:
npm init -y
npm install --save-dev assemblyscript
npx asinit .
После этого создаётся структура проекта с файлами assembly/index.ts и конфигурацией.
Написание первого модуля на AssemblyScript
Создадим простую функцию сложения в файле assembly/index.ts:
export function add(a: i32, b: i32): i32 {
return a + b;
}
Обратите внимание на тип i32 — это целое 32-битное число, специфичное для AssemblyScript.
Компиляция в WebAssembly
Выполните команду для компиляции:
npx asc assembly/index.ts -o build/optimized.wasm --optimize
Флаг --optimize включает оптимизацию размера и производительности. На выходе получаем файл build/optimized.wasm.
Интеграция WebAssembly с TypeScript
Загрузка Wasm модуля
Создадим TypeScript файл для загрузки и вызова Wasm функций:
// wasm-loader.ts
export async function loadWasmModule(): Promise<WebAssembly.Module> {
const response = await fetch('build/optimized.wasm');
const bytes = await response.arrayBuffer();
return WebAssembly.compile(bytes);
}
export async function instantiateModule(): Promise<WebAssembly.Instance> {
const module = await loadWasmModule();
const instance = await WebAssembly.instantiate(module);
return instance;
}
Вызов Wasm функций из TypeScript
Теперь создадим основной файл приложения:
// main.ts
import { instantiateModule } from './wasm-loader';
async function main() {
try {
const instance = await instantiateModule();
const { add } = instance.exports as { add: (a: number, b: number) => number };
const result = add(5, 7);
console.log(`Результат сложения: ${result}`); // 12
} catch (error) {
console.error('Ошибка загрузки Wasm:', error);
}
}
main();
Работа с памятью и строками
WebAssembly работает с линейной памятью. Для передачи строк используйте буферы:
// assembly/string-utils.ts
export function greet(namePtr: usize, nameLen: i32): usize {
// Чтение строки из памяти
const name = String.UTF8.decode(
memory.buffer.slice(namePtr, namePtr + nameLen)
);
const greeting = `Hello, ${name}!`;
// Кодирование и возврат указателя
return String.UTF8.encode(greeting);
}
В TypeScript для работы с памятью используйте TextEncoder и TextDecoder:
// string-handler.ts
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function callGreet(name: string): string {
const encoded = encoder.encode(name);
const ptr = instance.exports.allocate(encoded.length);
// Копируем данные в Wasm память
const memory = instance.exports.memory as WebAssembly.Memory;
const buffer = new Uint8Array(memory.buffer);
buffer.set(encoded, ptr);
const resultPtr = instance.exports.greet(ptr, encoded.length);
// Читаем результат
const resultLen = new Uint32Array(memory.buffer, resultPtr - 4, 1)[0];
const resultBytes = new Uint8Array(memory.buffer, resultPtr, resultLen);
return decoder.decode(resultBytes);
}
Оптимизация производительности
Советы по оптимизации
- Минимизируйте передачу данных: передавайте большие объёмы данных через общую память
- Используйте
WebAssembly.instantiateStreaming: для потоковой загрузки - Кэшируйте скомпилированные модули: используйте
IndexedDB - Избегайте частых вызовов: группируйте операции в одну функцию
Пример с потоковой загрузкой
// streaming-loader.ts
async function loadStreaming(): Promise<WebAssembly.Instance> {
const response = await fetch('build/optimized.wasm');
const result = await WebAssembly.