Начало работы с WebAssembly, используя только 14 строк на JavaScript

Александр Ергин
devSchacht
Published in
7 min readNov 16, 2018

Перевод статьи Daniel Simmons: Get started with WebAssembly — using only 14 lines of JavaScript.

WebAssembly — это новая веб-технология с огромным потенциалом. Она окажет существенное влияние на то, как веб-приложения будут разрабатываться в будущем.

Но иногда мне кажется, что эта технология просто не хочет, чтобы люди её поняли… Можно сказать, даже в странной пассивно-агрессивной манере.

Когда я смотрю документацию или обучающие материалы, которых уже целая куча, то не могу не чувствовать себя фермером, который молил о дожде, чтобы потом утонуть в потопе. Технически я получил то, что хотел… просто не так, как надеялся. «Ты хочешь дождь?! О, я дам тебе дождь!»

Так происходит потому, что WebAssembly даёт очень много новых возможностей, которые могут быть реализованы множеством разных способов. Но он настолько сильно изменился по пути к официальному MVP в феврале, что когда вы впервые узнаете о нём, то легко можете утонуть в море деталей.

В продолжение метафоры дождя, эта статья — моя попытка предоставить лёгкий душ из введения в WebAssembly. Никакой концепции или болтов и гаек, а фактическая реализация скрипта с использованием этой технологии.

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

Давайте разобьём на части

Всё станет намного понятнее, если мы сделаем шаг назад и рассмотрим список действий для внедрения WebAssembly в проект.

При первом взгляде на WebAssembly он может показаться просто большим комком опций и процессов. Разделение его на отдельные шаги поможет нам сформировать чёткое представление о том, что происходит:

  1. написание: напишите что-нибудь (или возьмите существующий проект) на C, C++ или Rust;
  2. компиляция: скомпилируйте этот код в WebAssembly (это даст вам бинарный wasm-файл);
  3. подключение: перенесите wasm-файл в проект;
  4. инициализация: напишите на JavaScript немного асинхронного кода, который скомпилирует и инициализирует wasm-файл так, чтобы с ним можно было взаимодействовать.

В целом, это всё. Само собой, есть разные реализации этого процесса, но в этом и суть.

В общем случае это не так уж сложно, но, тем не менее, может быть чрезвычайно трудным, поскольку большинство шагов допускает разную степень сложности. Во всяком случае, я собираюсь сделать всё на самом базовом уровне, чтобы избежать ошибок.

Для нашего проекта мы напишем простую функцию на C++ (не беспокойтесь, если вы не знакомы с этим языком, код будет максимально простым). Функция будет возвращать квадрат заданного числа.

Затем мы скомпилируем это в wasm-файл с помощью онлайн-компилятора (вам не понадобится скачивать утилиты или использовать командную строку). Далее мы инициализируем эту функцию с помощью 14 строк кода на JavaScript.

Когда мы закончим, вы сможете вызвать функцию, написанную на C++ так, как если бы она была JavaScript-функцией, и вы будете поражены!

Огромное количество возможностей, которые открывает WebAssembly, просто взрывает мозг.

Написание

Давайте начнём с нашего кода на C++. Помните, что мы не будем использовать локальное окружение для написания или компиляции кода.

Вместо этого мы будем использовать онлайн-инструмент, который называется WebAssembly Explorer. Он похож на CodePen для WebAssembly и позволяет скомпилировать ваш код на C или C++ прямо в браузере и скачать wasm-файл.

После того как вы открыли WebAssembly Explorer, введите этот код на C++ в самое левое окно:

int squarer(int num) {
return num * num;
}

Как я и говорил, мы используем очень простой пример. Даже если вы никогда раньше не видели C или C++, то всё равно не сложно догадаться, что здесь происходит.

Компиляция

Далее кликните по кнопке compile на красной панели над вашим C++ кодом. Вот что вы увидите:

Средняя колонка содержит человеко-читаемую версию бинарного wasm-файла, которую вы только что создали. Это называется «WAT», или WebAssembly Text Format.

Справа — получившийся ассемблерный код. Довольно круто.

Я не буду разбирать результат компиляции подробно, но вы должны знать хотя бы немного о WAT-файле, чтобы двигаться дальше.

WAT существует потому, что мы, люди, обычно испытываем трудности с пониманием чистого двоичного кода. Это, по сути, слой абстракции, который помогает понимать WebAssembly-код и взаимодействовать с ним.

В нашем случае мы хотим понять, как WebAssembly ссылается на функцию, которую мы только что создали. Это нужно для того, чтобы узнать точное имя, на которое мы будем ссылаться в JavaScript-файле позже.

Любые функции, которые вы написали на C++, будут доступны в WebAssembly как нечто называемое export. Мы поговорим об этом немного позже, ну а сейчас вам нужно знать лишь то, что export — это то, с чем можно взаимодействовать.

Взгляните на WAT-файл и найдите там слово export. Вы увидите его дважды: в первый раз рядом со словом memoryи затем снова рядом со словом _Z7squareri. О memory пока необязательно ничего знать, а вот _Z7squareri нас определённо интересует.

В коде на C++ мы назвали функцию squarer, но теперь она почему-то называется _Z7squareri. В первый раз это может сбить с толку.

Насколько я могу судить, префикс «_Z7» и суффикс «i» — это отладочные маркеры, добавленные компилятором C++. В это необязательно глубоко вникать. Вам просто нужно знать, что это произойдет, т.к. вам понадобится точное имя в JavaScript-файле, чтобы вызвать функцию, написанную на C++.

Подключение

Теперь просто нажмите на кнопку «download» в фиолетовой секции. Вы должны получить бинарный wasm-файл. Переименуйте его в squarer.wasm. Затем создайте новую папку и переместите туда файл squarer.wasm вместе с двумя другими файлами:

  • index.html (шаблонный код),
  • scripts.js (пока что пустой).

Инициализация

Теперь сложная часть. Или, по крайней мере, часть, которая ввела меня в замешательство, когда я в первый раз начал просеивать документацию.

Хотя, в конечном итоге, вы сможете подключать wasm-модули, как старые добрые ES6-модули (используя <script type='module'>), пока что вам нужно научиться подключать «вручную». Это делается путём создания асинхронных вызовов WebAssembly API, вот три шага:

  • загрузите ваш бинарный wasm-файл в буфер массива*;
  • скомпилируйте бинарные данные в модуль WebAssembly*;
  • инициализируйте* модуль WebAssembly;

Если вы поняли, о чём идёт речь, то можете пропустить следующую часть. Но если вы обнаружили себя почёсывающим голову и хотите получить подробное объяснение, то продолжайте читать.

*Буфер массива

Буфер — это временное хранилище данных, до тех пор, пока они не будут обработаны. Как правило, он используется, когда получение и обработка происходят с разной скоростью.

Например, когда видео буферизуется, данные принимаются медленнее, чем видеоплеер их воспроизводит. Одна из вещей, которой занимается наш буфер массива, — это упорядочивание двоичных данных, чтобы их было проще скомпилировать.

Но здесь есть ещё один важный момент. В JavaScript любой буфер массива — это типизированный массив, который используется специально для хранения двоичных данных.

Тот факт, что он явно типизирован, означает, что JavaScript-движок может интерпретировать буфер массива гораздо быстрее, чем обычный массив, потому что он уже знает тип данных и не должен проводить процесс его определения.

*Модуль WebAssembly

Как только вы поместите двоичные данные в буфер массива, вы сможете скомпилировать его в модуль. Модуль WebAssembly сам по себе является инертным. Это просто скомпилированный двоичный файл, ожидающий каких-нибудь действий.

Вы можете думать о модуле, как о рецепте торта. Рецепт — это просто формат хранения информации о том, как приготовить торт. Если вы на самом деле хотите торт, то должны создать экземпляр торта, описанный в рецепте (инициализировать торт).

Вы делаете это, следуя описанным в рецепте инструкциям. Кроме того, вы можете отправить рецепт кому-то другому (service worker) или сохранить его и использовать потом (закешировать). Оба варианта гораздо удобнее делать с рецептом, чем с самим тортом.

*Инициализация

Последнее, что вам нужно сделать, — это создать экземпляр модуля WebAssembly, который «оживляет» его и делает действительно полезным.

Экземпляр даёт вам доступ к экспорту модуля (помните, это из нашего WAT-файла?). Это объект, который содержит:

  • Память (сейчас нас не интересует, но вы можете прочитать подробнее тут).
  • Любые функции, которые присутствуют в вашем коде на C++. Это то, что вы будете использовать.

Заканчивайте и запускайте!

Это код, выполняющий все шаги, которые мы только что прошли (он должен находиться в вашем файле scripts.js):

Функция loadWebAssembly() загружает ваш wasm-файл, а потом выполняет операции, описанные выше. Затем возвращает новый экземпляр модуля WebAssembly.

Наша функция на C++ (помните, это которая с замысловатым именем, мы о ней упоминали раньше: _z7squareri) живёт в экспорте экземпляра модуля WebAssembly. Вы можете видеть, что на 12-й строке она назначена глобальной переменной squarer. Теперь мы можем использовать squarer() как обычную JavaScript-функцию.

После того, как вы поместите код в свой файл scripts.js, сохраните и запустите на локальном хосте, вы должны увидеть сообщение «Finished compiling...» в консоли браузера. Теперь просто вызовите функцию c каким-нибудь аргументом. Попробуйте что-нибудь вроде squarer(9). Нажмите Enter, и увидите 81. Работает! Вы вызываете функцию, написанную на C++!

Это фантастика

Вы и представить себе не можете, сколько возможностей это открывает.

Во-первых, JavaScript больше не единственный вариант для того, чтобы «сделать чего-нибудь» в браузере, теперь их огромное количество.

Во-вторых, получаем улучшение производительности, так как WebAssembly, в отличие от JavaScript, выполняется со скоростью, близкой к нативной.

И теперь весь устаревший код в вашем распоряжении. C и C++ существуют уже довольно давно, и за это время с их помощью множество выдающихся людей создали удивительные проекты с открытым исходным кодом. Эти проекты теперь могут быть интегрированы в сайты или приложения.

Уже сейчас вы можете написать более сложный код на C, C++ или Rust, или даже адаптировать существующий проект и «свебасемблить» его в веб-проект.

Однако есть одно предостережение: если вы хотите функции, которые принимают аргументы или возвращают значения, не являющиеся числами, то всё будет немного сложнее. И вот тогда вам нужно побольше узнать про атрибут memory в экспорте wasm-файла.

Этот проект доступен на GitHub, если вы просто хотите клонировать рабочую копию в дополнение к статье.

Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.

--

--