Решил я тут на днях попробовать соорудить что нибудь на wasm, поскольку ранее начитался про него и выбрал Rust. Это рассказ про то как я затащил wasm на фронтенд без боли.
В чём заключается упомянутая боль?
Я люблю когда в моём проекте чисто, каждая директория за что-то обязательно несёт ответственность. Я не хочу тянуть всякие lerna
в проект и прочие штуки из которых мне может понадобится всего одна фича. Возможно то что я описал для некоторых читателей и не проблема вовсе.
Излишний конвейер
Чтобы прийти к возможности просто «импортировать» rust нужно преодолеть много разных препятствий. К счастью это уже решено за нас и нужно лишь использовать.
-
Соберите ваш крейт под wasm32 используя
cargo build --target wasm32-unknown-unknown
-
Сгенерируйте JS и объявления типов TS при помощи wasm-bindgen
-
Оптимизируйте wasm для продакшена если это требуется используя binaryen
Это инструменты с которыми я знаком, их вероятно больше. Как бы вы разместили их у себя в проекте? Вам нужно скачать каждый инструмент локально, указать мусорную dist папку для хранения артефактов сборки и произвести всю магию по очереди. Почему я должен думать об этом. Похоже создатели wasm-pack тоже так подумали.
wasm-pack и мусорный workspace
Отличный инструмент, он сделает всё выше перечисленное одной командой. Но увы мне он не понравился, вам всё ещё нужно требовать его установку от других, объявить скрипт сборки в вашем package.json. Одной из проблем wasm-pack является то, что он создаёт готовый к публикации npm пакет, требуя readme, лицензию и прочую лишнюю (если вы не собираетесь публиковать) информацию. Чтобы лучше описать проблему, я распишу структуру проекта
/my-project --/Cargo.toml (cargo workspaces root) --/package.json (yarn workspaces root) --/app (приложение) ----/.. ----/package.json --/super-crate (крейт wasm) ----/Cargo.toml ----/..
И куда вы дадите wasm-pack собрать ваш крейт? dist в super-crate? Хорошо, если вы публикуете пакет, но точно не когда вы собираетесь им постоянно пользоваться и пересобирать. Так что у вас всегда будет мусорный dist-workspace для собранных артефактов. И прежде чем смириться с этим я вспомнил…
У меня есть Yarn
А у вас?). PnP для разрешения зависимостей и не использует node_modules, а вместо них использует кэш, который может быть расположен глобально или у вас в проекте. К тому же вы можете коммитить кэш в удалённый репозиторий, чтобы ускорить установку пакетов в CI пайплайне.
Эта информация пригодится чуть позже, самое главное — yarn поддерживает плагины, а значит его функционал можно расширить. Можно было бы упаковать условный «wasm-pack» в такой плагин, а результат сборки хранить в кэше минуя dist.
Расскажу поверхностно, потому что yarn плагины потянут на целую статью
О плагинах
Прежде чем раскрыть магию я должен рассказть о том что вы можете расширять плагинами. Я привёл самое основное, что пригодится
-
Команда (Command) — позволяет расширить yarn добавлением новых команд.
-
Резольвер (Resolver) — расширяет механизм разрешения зависимостей. Именно резольвер преобразует
"react": "^18.2.0"
в вашем package.json в конкретное дерево зависимостей в yarn.lock -
Фетчер (Fetcher) — отвечает за загрузку пакета из удалённого репозитория или локальной директории. В случае с yarn должен упаковать пакет в tgz архив для хранения в кэше.
Более подробно можно почитать на сайте yarn.
Роль каждого в решении проблемы
Как же эти три термина помогут решить проблему?
Резольвер
Прочитает package.json, найдёт нужный крейт в проекте и свяжет путь к нему с дескриптором
{ "name": "app", "main": "src/index.ts", "dependencies": { "@crate/super-crate": "crate:*" } }
В данном случае имя крейта извлекается из дескриптора — «super-crate». Cкоуп @crate
я ввел для семантики, чтобы отличать обычные пакеты от крейтов.
На выходе получается путь к целевому крейту, если он указан как member в Cargo.toml в корне проекта.
Фетчер
Получит путь к крейту из резольвера и соберёт его в кэш. Тут поподробней опишу действия
-
Создаётся временная директория для артефактов сборки (yarn предгает xfs, с возможностью создать временную директорию нативно из yarn).
-
Запускается
cargo build
с флагом--out-dir
чтобы направить артефакты во временную директорию. -
Выполняется
bindgen
, чтобы создать типы .d.ts и привязки js. -
Опционально запускается оптимизатор
wasm-opt
из binaryen. -
Результат упаковывается в tgz и отправляется в кэш.
-
Временная директория удаляется.
Команда (rebuild)
Всё выше описанное происходит при запуске yarn install
команды и только один раз. Чтобы пересобрать крейт, нужно просто пометить кэш как не актуальный и запустить yarn install
снова. У меня это делает команда yarn repack rebuild имя_крейта
*. Если не указать крейт, то будут пересобраны все.
*repack — это название плагина
Команда (install)
yarn repack install
загрузит бинарники wasm-bindgen и binaryen в проект. Это требуется только при инициализации проекта или обновлении плагина, т.к. эти бинарники тоже можно коммитить.
Результат
Всё что мне теперь осталось сделать, это запустить yarn install
и импортировать «крейт». Вау, а ведь это просто зависимость в package.json. И мне не приходиться беспокоиться о мусоре в моём проекте, а если мне понадобиться собрать крейт заново, я просто запущу yarn repack rebuild super-crate
. К тому же результат сборки кэшируется, а значит я могу использовать его без проблем в CI без пересборки.
import init, { hello_world } from '@crate/super-crate' await init() console.log(hello_world())
Для тех кто дочитал
Примеры использования и сам плагин на github (он на стадии proof of concept, так что я расчитываю на фидбэк): https://github.com/LIMPIX31/plugin-repack
ссылка на оригинал статьи https://habr.com/ru/articles/734046/
Добавить комментарий