Перевод статьи 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.

devSchacht

Подкаст. Переводы. Веб-разработка.

Александр Ергин

Written by

devSchacht

Подкаст. Переводы. Веб-разработка.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade