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