Привет всем, Алекс здесь. В первой части этой мини-серии о WebAssembly я расскажу, как запустить базовое приложение React + WebAssembly. Статьи, которые я нашел по этой теме, были либо устаревшими, либо требовали, чтобы пользователь извлек приложение Create-React-App (CRA), чтобы изменить конфигурацию веб-пакета. Я избегаю извлечения веб-пакета любой ценой, чтобы не застрять с настройками веб-пакета.

Независимо от того, используете ли вы неоткрытый CRA или нет, пример кода в этой статье все равно должен работать. Сегодня наш код WebAssembly будет скомпилирован из C.

Предпосылки

Поскольку мы создаем приложение React / WebAssembly, ваш компьютер должен уметь:

  1. создавать приложения React и
  2. скомпилировать код WebAssembly.

Начнем с №2

TLDR; в том, что вам нужен emsdk (я считаю, что это сокращение от emscriptem sdk). По сути, вам нужен инструмент, который может скомпилировать ваш код в WebAssembly, и Emscriptem пока что является лучшим инструментом для этой работы. Установка emsdk должна быть довольно простой, следуя их инструкциям здесь. Если у вас есть компьютер Apple, на котором используется чип M1, возможно, вам придется поискать в Интернете дополнительные шаги по установке emsdk. В противном случае инструкции должны работать должным образом.

Что касается №1, если на вашем компьютере еще не настроен набор инструментов create-react-app, найдите инструкции здесь.

Давайте запустим приложение React

На своем терминале создайте приложение React:

npx create-react-app wasm-react

Для простоты я избегаю использования Typescript в этом примере, но если вы хотите использовать Typescript, продолжайте!

Напишем C

Мы собираемся создать базовую функцию сложения на C. Этот файл будет скомпилирован в WebAssembly, а затем мы вызовем его из нашего приложения React. Создайте новый файл src / add.c и скопируйте и вставьте в него следующее:

#include <emscripten/emscripten.h>
#include <stdlib.h>
EMSCRIPTEN_KEEPALIVE int add(int a, int b)
{
return a + b;
}

Я знаю, что это не очень интересно, но уже есть несколько хороших моментов, о которых следует помнить. В частности, обратите внимание на текст «EMSCRIPTEN_KEEPALIVE» в начале функции. Без этого текста компилятор Emscriptem сочтет это мертвым кодом и удалит его.

Сделайте Makefile

В корне вашего проекта создайте файл с названием Makefile. В этом файле скопируйте и вставьте следующий код

src/add.mjs: src/add.c
emcc --no-entry src/add.c -o src/add.mjs  \
-s ENVIRONMENT='web'  \
-s SINGLE_FILE=1  \
-s EXPORT_NAME='createModule'  \
-s USE_ES6_IMPORT_META=0  \
-s EXPORTED_FUNCTIONS='["_add", "_malloc", "_free"]'  \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'  \
-O3

Этот Makefile скомпилирует код C в WebAssembly. Давайте разберем этот код на части:

  • emcc: первая примечательная часть кода - emcc. Это инструмент от emsdk, который мы установили ранее. Сделайте быструю проверку работоспособности и убедитесь, что при запуске
emcc

в вашем терминале вы получите сообщение, которое не «команда не найдена: emcc». Если вы действительно получили сообщение «команда не найдена», вернитесь в папку emsdk и запустите:

source ./emsdk_env.sh

Это должно вернуть команду emcc.

  • - no-entry: мы говорим, что у нас нет точки входа (например, main ()).
  • ENVIRONMENT = ’web’: скомпилированная WebAssembly может работать в таких средах, как веб-воркеры, Node.js, webview, но в нашем примере мы просто хотим «web».
  • SINGLE_FILE = 1: это гарантирует, что мы сгенерировали только файл add.mjs, а не ненужный файл add.wasm. Можно использовать двоичный файл .wasm, а не файл .mjs, и есть преимущества использования памяти при использовании двоичной версии кода WebAssembly, но в этом руководстве мы избегаем извлечения CRA, поэтому давайте продолжим.
  • EXPORT_NAME = ’createModule’: по какой-либо причине Emscriptem по умолчанию создает функцию с именем Module, которую вы затем вызываете для получения экземпляра модуля. Это сбивает с толку, поэтому, если назвать его createModule, станет понятнее, что это вызываемая функция, а не экземпляр модуля (подробнее здесь).
  • USE_ES6_IMPORT_META = 0: в идеале мы могли бы оставить эту функцию равной 1. Она должна помочь с автоматическим определением пути к модулю WASM (исходный код здесь). Веб-пакет моего React не поддерживает import.meta.url, поэтому я его отключаю.
  • EXPORTED_FUNCTIONS = ’[« _ add »,« _malloc »,« _free »]’: укажите, какие функции вы экспортируете. _add - это созданная нами функция, а _malloc и _free - внутренние функции, необходимые нашему коду add.mjs.
  • EXPORTED_RUNTIME_METHODS = ’[« ccall »,« cwrap »]’: как следует из названия, это функции, которые мы хотим экспортировать и которые могут выполняться во время выполнения. ccall позволяет сразу вызвать функцию WebAssembly, тогда как cwrap позволяет сначала сохранить функцию в памяти, а затем вызывать ее столько раз, сколько захотите.
  • -03: этот флаг указывает, как вы хотите оптимизировать код WebAssembly. 03 - наиболее оптимизированный вариант за счет более длительного времени компиляции и потенциально большего размера кода (подробнее здесь).

Как видите, с этим Makefile много чего происходит. Однако важно ознакомиться с флагами Emscriptem, потому что, когда что-то идет не так, этот Makefile обычно является одним из первых мест, куда вы попадете при отладке.

Идите вперед и запустите Makefile. В вашем терминале запустите

make

В случае успеха вы должны увидеть новый файл src / add.mjs. Если это так, похлопайте себя по плечу, и давайте закончим.

Собираем все вместе

Замените src / App.js следующим кодом:

import React, { useState, useEffect } from "react";
import createModule from "./add.mjs";
function App() {
  const [add, setAdd] = useState();

  useEffect(
    () => {
    createModule().then((Module) => {
    setAdd(() => Module.cwrap("add", "number", ["number", "number"]));
    });
  }, []);
  if (!add) {
    return "Loading webassembly...";
  }
  return (
    <div className="App">
      <p>Let's do some basic addition:</p>
      <div>123 + 234 = {add(123, 234)}</div>
    </div>
  );
}
export default App;

Сначала посмотрите на функцию useEffect. Что мы делаем, так это вызываем функцию createModule (помните это из нашего Makefile?) И ждем выполнения ее обещания. Его обещание - это модуль, в котором есть наши функции времени выполнения. В этом примере мы использовали cwrap, а не ccall, чтобы мы могли преобразовать наш хук [add, setAdd] в функцию, которая вызывает нашу функцию WebAssembly.

Наконец, посмотрите, что возвращает наша функция React:

return (
  <div className="App">
    <p>Let's do some basic addition:</p>
    <div>123 + 234 = {add(123, 234)}</div>
  </div>
);

Используя обработчик состояния для хранения нашей функции WebAssembly, теперь очень легко вызвать эту функцию «добавления».

Перед запуском сервера перейдите в package.json и добавьте следующий JSON в свой eslintConfig:

"ignorePatterns": [
  "src/add.mjs"
]

Запустите свой сервер! Вы должны увидеть, как наша основная математическая функция работает в браузере.

Престижность! Надеюсь, эта статья была полезной, и желаю удачи в вашем пути к WebAssembly. Полный исходный код можно найти здесь.

Хотите узнать, как скомпилировать библиотеку C в WebAssembly, а затем использовать ее в React? Ознакомьтесь с частью 2.