Как обстоят дела у 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 в продакшене, через некоторое время выйдет статья об этом.

Комментарии (9)


  1. Vadiok
    15.05.2024 17:23
    +1

    Nim в мире дикого фронтенда

    Далее статья про HappyX. А в конце:

    Использовали Nim в своей компании, будь у вас такая возможность?

    Так что же это за зверь такой, этот ваш Nim?