Пишем кроссплатформенный код с Haxe
Однажды я уже писал про этот язык на DOU. Однако, чтобы раскрыть все его особенности, одной статьи недостаточно. Более того, в комментариях было много интересных вопросов, которые побудили меня продолжить писать о данной технологии.
Кто не читал предыдущую статью, предлагаю ознакомиться. Кому лень читать, вкратце Haxe — это инструментарий для разработки кросс-платформенного ПО. Язык Haxe транслируется в множество других языков и может быть выполнен на разных платформах.
Это первая практическая статья, с которой можно начать свое знакомство с инструментарием Haxe в целом и понять (я надеюсь), что он из себя представляет.
Установка и настройка Haxe
- Качаем установщик для своей ОС.
- Качаем VSCode:
- Устанавливаем расширение для Haxe: marketplace.
Готово :) Но, чтобы полностью оценить все возможности, описанные в этой статье, вам также не помешает установленный NodeJS и VisualStudio (для компиляции C# и С++ кода под Windows), установленная Java 7+ в системе и Python 3.
Если же вам лень устанавливать все эти зависимости, то вы можете их смело опустить, просто прочитав статью. Однако, я бы рекомендовал установить хотя бы NodeJS, чтобы вы могли поэкспериментировать с языком лично.
Практика
После того, как вы установили сам Haxe, редактор и плагин к нему, давайте приступим сразу к практике.
Создайте файл MyApp.hx следующего содержания:
package; class MyApp { public static function main():Void { trace("Hello world!"); } }
Откройте терминал и выполните следующую команду:
haxe -main MyApp --interp
В случае успешного выполнения эта команда выведет на экран строку:
MyApp.hx:4: Hello world!
Давайте остановимся на этом этапе подробнее.
Как и во многих других объектно-ориентированных языках, основная единица в Haxe — класс. Он же, в свою очередь, объявляется внутри пакета (package, подобно Java), который является своего рода пространством имен. Пакет также должен соответствовать пути к файлу. То есть если мы объявили пакет package com.cosmething
, то наш класс должен лежать в папке com/something
. Имя класса объявляется с большой буквы.
В нашем примере есть публичная статическая функция main
, которая является точкой входа для всех программ, написанных на языке Haxe (в некоторых случаях эта функция не обязательна). Внутри этой точки входа мы вызываем функцию trace
, которая может принимать любой объект, который она попытается преобразовать в строку и вывести на экран. Помимо объекта, функция выведет имя текущего класса и номер строки, где была вызвана данная функция. Как вы поняли, это очень удобно для отладки.
Вызвав команду компиляции, мы указали наш основной класс -main MyApp
, который содержит main функцию и передали туда флаг --interp
, который интерпретирует наш код на лету без его компиляции.
JavaScript
Теперь попробуем создать JavaScript-файл из нашего Haxe-класса. Поменяйте условия компиляции следующим образом:
haxe -main MyApp -js ./myapp.js
После успешного выполнения команды рядом с вашим классом должен появиться сгенерированный myapp.js
файл. Его можно открыть через редактор и посмотреть, что же нам сгенерировал Haxe компилятор.
Если у вас уже установлен NodeJS, то вы можете выполнить полученный JS следующим образом:
node ./myapp.js
В случае успешного выполнения NodeJS выведет на экран строку:
MyApp.hx:4: Hello world!
Мы получили то же самое, что и в первом примере. Разница лишь в том, что в условиях компиляции мы заменили флаг интерпретации кода на компиляцию (по факту трансляцию) под JS, указав в качестве значения имя выходного файла.
Теперь везде, где вам доступен JavaScript, вы можете выполнять свой код.
Справка. Когда вы пишете на современном JS, вам приходится использовать дополнительные инструменты вроде Webpack’a для склеивания ваших JS классов и файлов. Haxe делает это автоматически, и вся программа будет слита в один монолитный JS файл. Стоит, однако, упомянуть, что если есть такая необходимость, то можно генерировать по JS файлу на класс при помощи сторонней библиотеки, описание которой выходит за рамки данной статьи.
Java
Продолжим. Сила Haxe в том, что вы можете генерировать код для множества языков и выполнить его, таким образом, практически под любой платформой. Давайте проверим это на практике и попробуем собрать наш код под Java. Для этого нам нужно установить соответствующую библиотеку — hxjava.
Справка. Библиотека hxjava будет установлена глобально и будет доступна во всех Haxe проектах. За ее установку отвечает библиотечный менеджер haxelib. По умолчанию установка библиотек производится в папку установки Haxe (а там в папку lib). Изменить эту директорию можно вызвав команду haxelib setup.
Итак, выполним установку зависимости:
haxelib install hxjava
А затем вызовем компиляцию:
haxe -main MyApp -java ./java
Вместо -js
флага мы теперь используем -java
. В отличие от предыдущего флага, в качестве значения целевого языка мы указали директорию, а не выходной файл. Таким образом, у нас должна была появиться директория java, в которой лежит скомпилированный MyApp.jar. Перейдите в эту директорию и выполните его:
java -jar ./MyApp.jar
И консоль снова выдаст вам:
MyApp.hx:4: Hello world!
Теперь везде, где вам доступна Java 7+, вы можете выполнять свой код.
C#
Для этого целевого языка, подобно Java, нам нужно установить библиотеку hxcs:
haxelib install hxcs
Далее, как мы уже делали это раньше, скомпилируем наш код под C#:
haxe -main MyApp -cs ./csharp
Перейдя в директорию csharp/bin
, вы сможете обнаружить ваш MyApp.exe. Запустив его с консоли, вы увидите уже привычный ответ:
MyApp.hx:4: Hello world!
Теперь везде, где вам доступен C#, вы можете выполнять свой код.
Python
Очередь за Python. Компилируем:
haxe -main MyApp -python ./myapp.py
Выполняем:
python ./myapp.py
И получаем:
Hello world!
Вы могли заметить, что у полученного результата отсутствует имя класса и строка вызова функции trace. Для Python таргета это сделанно специально в качестве оптимизации. Однако, если это действительно необходимо, то можно добавить следующий код перед вызовом trace:
package; class MyApp { public static function main():Void { haxe.Log.trace = haxe.Log.trace; //заставит Haxe использовать свою реализацию вывода отладочной информации, вместо нативного в python print trace("Hello world!"); } }
Теперь вы можете выполнять свой код везде, где вам доступен Python3.
Справка. Очень часто слышу вопрос по поводу Python таргета — зачем он нужен? Ответ на поверхности и в целом применим и к другим таргетам. Haxe — это ООП язык, с мощной системой типов и макросов, кросс-компиляцией и оптимизациями на уровне генерации кода. Таким образом, если вам по какой-то причине не нравится или не хочется учить Python, то Haxe можно рассмотреть как его замену. Так же, как и TypeScript является заменой чистого JS. Вы можете взять любую Python-библиотеку, например TensorFlow, подключить ее к Haxe и писать код на строго типизированном ООП языке.
C++
Далее для компиляции C++ нам понадобится установленный в системе VisualStudio (под линуксом — GCC, под маком — Xcode). Подробнее о компиляторах — Getting started with Haxe/C++.
По примеру с Java и C#, необходимо установить библиотеку hxcpp, которая позволит нам компилировать сгенерированный код:
haxelib install hxcpp
После успешной установки мы можем скомпилировать наш код:
haxe -main MyApp -cpp ./cpp
В папке cpp должен был появиться файлик MyApp.exe, выполнив который мы уже традиционно получим:
MyApp.hx:4: Hello world!
Теперь вы можете выполнять свой код (почти) везде, где вам доступен C++.
Справка. С++ — очень многогранный таргет. Код может быть сгенерирован по С++11 стандарту, скомпилирован под разные архитектуры (x86, x64, arm), под разные платформы: Linux, Mac, Windows, WinRT, Android, iOS, QNX, консоли, etc. Hxcpp поддерживает статическую и динамическую линковки, как внешних библиотек, так и кода, написанного на Haxe (для использования в качестве библиотеки в существующем проекте, например). Также полученный код может быть использован через emscripten.
Файл сборки
Каждый раз писать множество аргументов (а их может быть много) в командной строке для того, чтобы скомпилировать код — напряжно. У Haxe для этого предусмотрены файлы с расширением hxml, которые описывают инструкции компилятора.
Удалите все, кроме MyApp.hx, и создайте файт build.hxml, а файл MyApp.hx перенесите в папку src так, чтобы у вас получилась следующая иерархия:
./build.hxml
./src/MyApp.hx
В файле build.hxml добавьте следующие команды:
# This is our build file -cp src -main MyApp.hx -js ./deploy/out.js -dce full -D analyzer-optimize -debug
Теперь нажмите Ctrl+Shift+B, и VSCode откроет вам в ниспадающем списке доступные файлы сборки:
Выберете наш build.hxml, и тогда VSCode скомпилирует вам Haxe код в JS с выходным файлом ./deploy/out.js. Теперь мы можем перестать использовать терминал для компиляции кода. Стоит сказать, что этот процесс можно еще больше упростить, добавив задачи для сборки через настройки VSCode. Подробнее — Build Tasks.
Вернемся к коду. Как вы, наверное, заметили, у нас появились какие-то новые, непонятные флаги компилятора в файле build.hxml. Давайте разберем их:
-dce full,
— это исключение мертвого, неиспользуемого кода. Таким образом, все классы, которые не используются, не будут включены в сборку (в том числе стандартная библиотека Haxe). Это позволяет генерировать более компактный код. Подробнее.-D analyzer-optimize,
— включает статический анализатор кода и старается оптимизировать то, что мы получаем на выходе. Подробнее.-debug,
— включает дебаг-режим и генерирует карты кода (source map). Подробнее.
И напоследок давайте соберем еще один маленький пример.
Создайте в папке deploy файл index.hxml со следующим содержанием:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello, Haxe!</title> <script src="out.js"></script> </head> <body> </body> </html>
А MyApp класс измените следующим образом:
package; import haxe.Json; import js.Browser.console; import js.Browser.document; import js.Browser.window; class MyApp { public static function main():Void { //парсим JSON, типизировав объект (типизировать необязательно): var o:{my_value:Int} = Json.parse('{"my_value": 100}'); //так что, поле my_value доступно через автодополнение кода: console.log(o.my_value); //выведем сообщением значение my_value: window.alert('my_value is ${o.my_value}'); //подождем загрузки страницы //используем анонимную функцию document.addEventListener('DOMContentLoaded', function():Void { //приведем к строке распарсенный ранее объект: document.body.innerHTML = Json.stringify(o); throw "Something happened."; }); } }
Скомпилируйте Haxe код (Ctrl+Shift+B) и откройте index.html в браузере. Если вы сделали все верно, то браузер должен показать вам сообщение со значением переменной my_value. Если вы откроете консоль разработчика в браузере, то увидите, что console.log также отработал успешно. И напоследок, чтобы показать интеграцию с целевыми платформами, мы изменяем HTML код прямо из Haxe.
Если вы обратите внимание на консоль разработчика, то увидите, что отработало брошенное нами исключение, которое при помощи дебаг-режима отправляет нас прямо в Haxe код (а не в сгенерированный JS) для отладки:
Справка. Стоит понимать, что код из последнего примера не может быть скомпилирован, например, в Java, т. к. там нет HTML объектов. Для разрешения таких ситуаций у Haxe есть препроцессор, при помощи которого можно обернуть платформо-зависимые части кода которые не будут включены в сборку там, где не надо. Например:
#js trace("This code is available on JavaScript target."); #elseif java trace("This code is available on Java target."); #else trace(“This code is available on all (other) targets”); #end
Haxe поддерживает и другие языки, например PHP, Lua, ActionScript. Рассматривать мы их уже не станем, чтобы не повторяться, так как кодогенерация происходит идентично вышеописанным языкам.
Также у Haxe есть хорошо оптимизированная виртуальная машина HashLink, которая может либо выполнять сгенерированный байткод aka JIT, либо конвертироваться в Си с последующей компиляцией и выполнением без виртуальной машины.
Выводы
Опираясь на все вышесказанное, подытожу, что Haxe — это мощное средство для кроссплатформенной разработки, которое может быть использовано как самостоятельный инструментарий для разработки вашего приложения, так и как средство для создания общей кодовой базы между разными платформами и языками.
Интересные факты:
- Все целевые платформы Haxe могут использовать нативные библиотеки.
- Компилировать сгенерированный код в исполняемый файл необязательно (Java, C#, C++). Вы можете генерировать только код, а компиляцию производить своими инструментами (либо включать сгенерированный код в уже существующие проекты). В этом вам поможет флаг -D no-compilation.
- Генерируя JavaScript, вы можете использовать его как самостоятельное приложение, так и включать его в свои JS проекты и использовать напрямую из JS кода. Для того, чтобы Haxe классы стали доступны из JS кода, используйте для них @:expose мету. Подробнее.
- Сгенерированный код Haxe может быть использован напрямую в целевых языках. Так же, как и эти языки могут быть использованы прямо в коде Haxe.
Ссылки
Чат: gitter.im/HaxeFoundation/haxe
Форум: community.haxe.org
Мануал: haxe.org/...duction-what-is-haxe.html
Сравнение Haxe, TypeScript, Dart и Wasm: github.com/damoebius/HaxeBench
Связь со мной: [email protected]
В следующей статье мы посмотрим на интересные конструкции языка, его особенности и преимущества.
39 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.