В своем выступлении Филипп рассказал об основах работы с JavaScript в Haxe, о том как вызывать JavaScript-код из Haxe и наоборот и немного о том, как все это устроено изнутри.
Его выступление состояло из нескольких блоков:
Все эти вопросы рассматривались на простейшего простого модульного приложения, состоящего из:
В олдскульном JavaScript порядок подключения скриптов важен, все находится в глобальной области видимости (global scope) - как если бы вы писали не var Menu = ...
, а window.Menu = ...
.
Но такой код быстро становится запутанным. Да, он будет работать, и в таком стиле написано много проектов, но сейчас так писать - это плохой тон.
Чтобы избежать “загрязнения” глобальной области видимости, код помещают в приватную область видимости - в функции:
А функция, в свою очередь, может автоматически выполняться:
И весь код внутри этой функции будет полностью приватным.
Таким образом, можно реализовать инкапсуляцию, скрывая функции и переменные из глобальной области видимости, помещая код в анонимные автоматически исполняемые функции:
Если же в таком коде необходимо обратиться к переменной из глобальной области видимости, то для этого необходимо явно указать, что вы обращаетесь к свойству глобального объекта window
.
Рабочая группа CommonJS создала спецификацию модулей CommonJS - API для инкапсуляции приватного кода и описания публичного API (используя объект exports
).
В приведенном примере весь код является приватным за исключением явно указанного значения value
и функции action
:
При загрузке такого модуля (с помощью метода require()
), его код исполняется один раз, и публичные поля из модуля (в модуле thing.js
это поля value
и action
) сохраняются в объекте thing
и доступны в коде запрашивающего модуля:
То есть метод require()
возвращает вам объект exports
запрашиваемого модуля.
Такой подход используется в Node.js, где CommonJS является встроенной системой модулей.
Но он не работает в браузерах. Для того, чтобы такой код заработал в браузере, его необходимо специально подготовить. Процесс такого преобразования кода получил название “бандлинг” (bundling). В рассматриваемом примере результатом бандлинга модулей menu.js и app.js является файл bundle.js
Еще раз: процесс бандлинга начинается с входной точки приложения (в примере это app.js), проходим по всем запрашиваемым модулям и осуществляем их трансформацию. В результате получаем bundle.js
Для бандлинга существует множество инструментов:
(технически бандлинг не такое уж и сложное дело. Если вам интересно как устроена работа такого инструмента, то вы можете сами написать сбой бандлер и свою реализацию функции require())
Ну а как из множества Haxe-классов получается JavaScript?
В Haxe мы указываем компилятору точку входа (в примере это класс App
), компилятор проходит по всем импортированным зависимостям, преобразует весь код и объединяется его в бандл (output.js):
И это практически тот же самый процесс бандлинга, что мы видели в JavaScript.
Пониманию того, как Haxe-код преобразуется в JavaScript, лучше всего способствует изучение кода, полученного на выходе из компилятора:
Компилятор создает вполне классический код в соответствии со стандартом ES5, где конструктор - это функция, статические поля инициализируются после объявления всех полей класса, а свойства и методы объекта определены в прототипе класса.
Все ваши классы помещаются в анонимную автоматически исполняемую функцию:
Весь код в итоге становится приватным (за исключением кода, для которого явно не указано, что он доступен извне).
Структура полученного бандла следующая:
И интересно то, что полученный бандл универсален - он может работать как в браузере, так и в Node.js (если, конечно, вы не используете какой-либо специфичный код).
Итак, имеем следующее:
И как же нам теперь настроить взаимодействие кода, написанного на JavaScript и на Haxe?
Давайте вернемся к самому первому примеру с модулем Menu и попробуем переписать его на Haxe.
У нас есть статическая функция render()
:
В Haxe этот код станет классом Menu со статическим публичным методом render()
:
Так, уже хорошо! Но для того, чтобы код из нашего класса Menu можно было вызывать из JavaScript, нужно использовать мету @:expose
(иначе класс будет недоступен извне полученного на выходе из компилятора бандла).
На следующем слайде показан немного измененный (для краткости) код, который получается после компиляции класса Menu:
Здесь мы видим анонимную автоматически исполняемую функцию, но с дополнительным кодом для объявления публичного API:
exports
доступен (на Node.js), то публичный код будет доступен из него
exports
недоступен, то проверяется доступность глобального объекта window
(доступного при работе в браузере), и если он есть, то публичное API будет доступно из него
self
(он будет доступен, если код работает в веб-воркере)
this
После этого мы можем “прикрепить” публичное API к выбранному объекту:
Следующий вопрос: как использовать JavaScript-код из Haxe?
Здесь нужно рассмотреть два случая:
Код из обоих случаев можно использовать!
Для классического JS-кода можно просто вызывать untyped
код:
Можно также написать свою обертку с красивым API.
А еще можно написать экстерны (но сегодня мы не будем их касаться).
Использование npm-модулей - более интересный случай.
Для запроса npm-модуля можно использовать inline
-синтаксис:
Если же вы хотите использовать классы, то, скорее всего, вам понадобятся экстерны - метаданные, используемые для преобразования вашего Haxe-кода в соответствующий код платформы (в нашем случае - в CommonJS код). Если скомпилированный код будет использоваться в браузере, то его будет необходимо дополнительно преобразовать с помощью бандлера, т.к. компилятор Haxe подготовит только тот код, который он скомпилировал, с исходным JavaScript-кодом он ничего не сделает.
Стоить также отметить, что стандарт CommonJS уже устаревает, и сегодня все больше кода использует более новый синтаксис стандарта ES6, который может показаться запутанным даже для опытного JavaScript-программиста:
Филипп предложил просто запомнить правила преобразования ES6 синтаксиса в ES5:
1. import * as React from 'react';
преобразуется в экспорт объекта:
2. Использование фигурных скобок означает использование деконструкции из ES6 для получения значений из объекта:
преобразуется в
3. и последний (непонятный даже Филиппу) случай, когда не используются ни *, ни фигурные скобки, означает, что из запрашиваемого модуля извлекается свойство default
:
можно преобразовать в
Это лишь попытка пробежаться по верхам, и для полного понимания того, как использовать существующий JavaScript-код в Haxe, лучше всего подсматривать технические решения и приемы в коде библиотек-экстернов с haxelib.
Хотя это и конец презентации, но это только начало для тех, кто собирается заниматься разработкой на Haxe под JavaScript, т.к. эта тема намного шире.
В частности для бандлера Webpack есть загрузчик Haxe, который избавляет программиста от ручного вызова компилятора Haxe.
Также стоит обратить внимание на проекты Haxe Modular и hxgenjs, которые позволяют получать на выходе компилятора несколько js-файлов (а не один большой), таким образом обеспечивая возможность скачивать приложение по частям.