Как обстоят дела у HappyX, а в среде фронтенд-разработки сейчас?
Я расскажу вам об этом в полной статье.
Вспоминая о Nim
HappyX - веб фреймворк, написанный на языке Nim. Благодаря возможностям языка мы можем компилировать наше приложение как в JS, так и в C. Таким образом HappyX поддерживает и frontend и backend, впрочем, я здесь для того, чтобы поведать новости разработки HappyX.
HappyX и функциональные компоненты
В одной из предыдущих статей, в которых рассказывалось о веб-фреймворках Karax и HappyX, я показал, что Karax использует функции для создания VDOM:
include karax / prelude
proc createDom(): VNode =
result = buildHtml(tdiv):
text "Hello, world!"
setRenderer createDom
В то же время я упомянул HappyX компоненты. Однако до последнего времени для создания даже самой простой кнопки приходилось терять в производительности. За занавесом банальная кнопка расширялась до нескольких десятков строчек кода. Так происходило из-за того, что компоненты предоставляли ООП возможности, в том числе использование методов и свойств, наследование и прочие довольно тяжелые и порой ненужные вещи. Давайте вспомним один из примеров из статьи про сравнение HappyX и Karax:
import happyx
component Button:
buttonText: string
html:
tButton:
{self.buttonText}
@click:
echo "Клик по кнопке!"
appRoutes "app":
"/":
Button("Нажми на меня")
В этом примере мы можем видеть свойство buttonText
, а также его использование как текста для кнопки. Ниже, на последней строке находится использование компонента. Может показаться, что все довольно-таки просто и однозначно, но нет. Вот, какая функция одного только создания кнопки получается после компиляции в JS:
function initButton_536871130(uniqCompId_536871131, buttonText_536871132) {
function HEX3Aanonymous_536871156(self_536871157, ev_536871158) {}
function HEX3Aanonymous_536871159(self_536871160, ev_536871161) {}
function HEX3Aanonymous_536871162(self_536871163, ev_536871164) {}
function HEX3Aanonymous_536871165(self_536871166, ev_536871167) {}
function HEX3Aanonymous_536871168(self_536871169, ev_536871170) {}
function HEX3Aanonymous_536871171(self_536871172, ev_536871173) {}
function HEX3Aanonymous_536871174(self_536871175, ev_536871176) {}
var result_536871133 = null;
BeforeRet: {
var self_536871155 = {uniqCompId: nimCopy(null, uniqCompId_536871131, NTI33554449), buttonText: remember_536871134(buttonText_536871132), m_type: NTI536870994, isCreated: false, slot: null, slotData: null, created: null, exited: null, rendered: null, pageHide: null, pageShow: null, beforeUpdated: null, updated: null};
self_536871155.beforeUpdated = HEX3Aanonymous_536871156;
self_536871155.pageShow = HEX3Aanonymous_536871159;
self_536871155.pageHide = HEX3Aanonymous_536871162;
self_536871155.rendered = HEX3Aanonymous_536871165;
self_536871155.exited = HEX3Aanonymous_536871168;
self_536871155.created = HEX3Aanonymous_536871171;
self_536871155.updated = HEX3Aanonymous_536871174;
createdComponentsList_1946158656[0].push(self_536871155);;
result_536871133 = self_536871155;
break BeforeRet;
};
return result_536871133;
}
И это еще без учета отрисовки и объявления самой кнопки! Именно тут и приходят на помощь функциональные компоненты:
import happyx
proc Button*(buttonText: string): TagRef =
buildHtml:
tButton:
{buttonText}
@click:
echo "Клик по кнопке!"
appRoutes "app":
"/":
Button("Нажми на меня")
Как видите, поменялось только объявление компонента. Его использование никак не поменялось. И вот, что мы получили после компиляции:
function Button_536870914(buttonText_536870915) {
var result_536870916 = null;
var __el0_536871126 = initTag_1996489074([98,117,116,116,111,110], [initTag_1996489108(buttonText_536870915, true, [], false)], false);
__el0_536871126.addEventListener('click', (event) => {
Label1: {
var ev_536871127 = null;
ev_536871127 = event;
rawEcho([208,154,208,187,208,184,208,186,32,208,191,208,190,32,208,186,208,189,208,190,208,191,208,186,208,181,33]);
};
});
result_536870916 = initTag_1996489074([100,105,118], [__el0_536871126], true);
return result_536870916;
}
И это все! Таким образом мы получили и прирост в производительности и уменьшение кода на выходе.
Из возможностей обычных компонентов функциональные компоненты сохранили аргументы и слоты. В примере ниже показано, как можно передавать HTML в слот функционального компонента, используя при этом аргументы:
import happyx
proc Button*(x: int, stmt: TagRef): TagRef =
buildHtml:
tButton:
stmt
tDiv:
"x is {x}"
@click:
echo "Клик по кнопке!"
appRoutes "app":
"/":
Button(100):
"Нажми на меня"
Будет интересно узнать, что вы думаете по поводу функциональных компонентов.
HappyX Native
HappyX также обзавелся собственной небольшой библиотекой для компиляции в натив (на десктопах используется браузер по умолчанию, на Android используется WebView, на iOS пока нет возможности компиляции).
Работает оно следующим образом — в корне проекта создается основной серверный файл, который служит для взаимодействия устройства и приложения. Выглядит он следующим образом:
import happyx_native
callback:
# HappyX Native helloWorld callback
proc helloWorld() =
echo "Hello from Nim"
nativeApp("/assets", resizeable = false, title = "hpx_tests")
Здесь мы регистрируем коллбек с именем helloWorld
. Никаких аргументов он не принимает. После регистрации коллбеков мы запускаем сервер с помощью функции nativeApp
.
Далее в папке /assets находится файл main.nim. Выглядит он следующим образом:
import happyx
import assets/native # working with happyx native
var x = remember 0
proc helloWorld() =
hpxNative.callNim("helloWorld")
x->inc()
appRoutes "app":
"/":
tDiv:
tH1:
"hpx_tests"
tDiv:
"x is {x}"
tButton:
"increase"
@click:
helloWorld()
Со стороны JS мы по нажатию кнопки отправляет событие на сервер, которые принимает и обрабатывает его. В нашем случае мы отправляем вызов helloWorld
на сервер. На самом сервере при этом выводится "Hello from Nim"
.
При этом стоит упомянуть, что на стороне сервера можно напрямую обращаться к приложению и устройству, в котором запущен сервер. Например, при компиляции в Android мы можем отловить сервером событие из JS приложения, при котором мы сможем отобразить нативный Android диалог. Выглядит это следующим образом:
# Main native file
import happyx_native
callback:
proc showDialog() =
when defined(export2android):
Log.d("create dialog")
runOnUiThread:
var dialog = AlertDialogBuilder.new(
appContext
).setTitle(
cast[CharSequence](String.new("title"))
).setMessage(
cast[CharSequence](String.new("text"))
).setCancelable(
JVM_TRUE
).show()
Log.d($dialog.toString())
nativeApp("/assets", resizeable = false, title = "android_tests")
И снова хочется узнать, что вы думаете об этом.
HappyX в проде
С недавних пор одна российская компания стала использовать HappyX в продакшене, через некоторое время выйдет статья об этом.
Vadiok
Далее статья про HappyX. А в конце:
Так что же это за зверь такой, этот ваш Nim?