Использование существующих WASM библиотек

WebAssembly (WASM) предоставляет возможность запускать высокопроизводительный код в браузере, а фреймворк Fresh на JavaScript упрощает интеграцию таких модулей в веб-приложения. Работа с существующими WASM библиотеками требует понимания структуры модуля, способов его импорта и взаимодействия с JavaScript.


Импорт WASM модуля

Существует два основных способа подключения WASM-библиотеки: динамический и статический импорт.

Динамический импорт

const wasmModule = await import("./my_module.wasm");

Этот метод удобен, когда модуль загружается по запросу, например, при взаимодействии пользователя с интерфейсом. Динамический импорт возвращает объект, содержащий экспортированные функции WASM, которые можно вызывать напрямую.

Статический импорт

import wasmModuleUrl from "./my_module.wasm?url";

const response = await fetch(wasmModuleUrl);
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);

Статический импорт подходит для случаев, когда модуль нужен при инициализации страницы. Этот подход позволяет получить полный контроль над памятью и экспортами модуля.


Инициализация и работа с памятью

WASM использует собственную память, поэтому часто необходимо создавать объекты типа Uint8Array для передачи данных между JavaScript и модулем.

const memory = new WebAssembly.Memory({ initial: 256, maximum: 512 });
const importObject = { env: { memory } };
const { instance } = await WebAssembly.instantiate(bytes, importObject);

Передача строк или сложных структур требует преобразования данных:

function passStringToWasm(str, memory, malloc) {
  const encoder = new TextEncoder();
  const encoded = encoder.encode(str);
  const ptr = malloc(encoded.length);
  const memBuffer = new Uint8Array(memory.buffer, ptr, encoded.length);
  memBuffer.set(encoded);
  return ptr;
}

Взаимодействие с экспортированными функциями

Экспортированные функции WASM доступны через объект instance.exports. При вызове функций необходимо учитывать типы данных — WASM поддерживает только базовые числовые типы (i32, i64, f32, f64).

const result = instance.exports.add(5, 3); // пример вызова функции add

Если функция возвращает указатель на память, необходимо преобразовать данные обратно в Jav * aScript:

function getStringFromWasm(ptr, memory, length) {
  const memBuffer = new Uint8Array(memory.buffer, ptr, length);
  const decoder = new TextDecoder();
  return decoder.decode(memBuffer);
}

Интеграция с Fresh

В Fresh используется рендеринг на сервере и клиенте, поэтому WASM можно подключать как в компоненте на сервере, так и на клиентской части. Для клиента важно загружать модуль асинхронно, чтобы не блокировать интерфейс:

export default function MyComponent() {
  const [result, setResult] = useState(null);

  useEffect(() => {
    (async () => {
      const wasm = await import("../wasm/my_module.wasm");
      setResult(wasm.add(10, 20));
    })();
  }, []);

  return <div>Результат: {result}</div>;
}

Для серверного использования WASM модуль можно импортировать синхронно в обработчике рендеринга страницы:

const { instance } = await WebAssembly.instantiate(
  await Deno.readFile("./wasm/my_module.wasm")
);
const value = instance.exports.compute(42);

Оптимизация и рекомендации

  • Минимизировать пересылку больших массивов. WASM работает быстрее при работе с памятью напрямую, чем при частых копированиях между JS и WASM.
  • Использовать подходящие типы данных. Не следует передавать сложные объекты напрямую — лучше сериализовать их в строки или массивы чисел.
  • Асинхронная загрузка. Для клиентской части всегда загружать модуль асинхронно, чтобы избежать блокировки UI.
  • Следить за памятью. WASM не управляет сборкой мусора JavaScript, поэтому важно освобождать выделенные блоки памяти вручную через функции free из модуля, если они предоставлены.

Работа с существующими библиотеками

Многие популярные библиотеки имеют WASM-сборки, например, ffmpeg.wasm, opencv.js, zstd.wasm. Для их интеграции необходимо:

  1. Установить пакет через npm или скачать WASM-файл.
  2. Проверить, какие функции экспортируются.
  3. Настроить объект импорта, если библиотека требует передачи памяти или вспомогательных функций.
  4. Использовать функции через instance.exports или предоставленные обёртки JavaScript.

Пример интеграции ffmpeg.wasm с Fresh

import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";

const ffmpeg = createFFmpeg({ log: true });

export default function VideoProcessor({ file }) {
  useEffect(() => {
    (async () => {
      await ffmpeg.load();
      ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(file));
      await ffmpeg.run('-i', 'input.mp4', 'output.mp4');
      const data = ffmpeg.FS('readFile', 'output.mp4');
      const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
      console.log(url);
    })();
  }, [file]);

  return <div>Видео обрабатывается...</div>;
}

Эта схема показывает, как существующие WASM-библиотеки могут быть полностью интегрированы в Fresh, сохраняя высокую производительность и асинхронность работы.